122 Commits

Author SHA1 Message Date
7e5ebabe94 avoid compiler's fury
IDK what's going on but compiler said:

```
In static member function ‘static constexpr std::char_traits<char>::char_type* std::char_traits<char>::copy(char_type*, const char_type*, std::size_t)’,
    inlined from ‘static constexpr void std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::_S_copy(_CharT*, const _CharT*, size_type) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’ at /usr/include/c++/12.2.0/bits/basic_string.h:423:21,
    inlined from ‘constexpr std::__cxx11::basic_string<_CharT, _Traits, _Allocator>& std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::_M_replace(size_type, size_type, const _CharT*, size_type) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’ at /usr/include/c++/12.2.0/bits/basic_string.tcc:532:22,
    inlined from ‘constexpr std::__cxx11::basic_string<_CharT, _Traits, _Alloc>& std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::assign(const _CharT*) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’ at /usr/include/c++/12.2.0/bits/basic_string.h:1647:19,
    inlined from ‘constexpr std::__cxx11::basic_string<_CharT, _Traits, _Alloc>& std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::operator=(const _CharT*) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’ at /usr/include/c++/12.2.0/bits/basic_string.h:815:28,
    inlined from ‘void nf7::File::MakeAsRoot()’ at /home/user/nf7/nf7.cc:91:11:
/usr/include/c++/12.2.0/bits/char_traits.h:431:56: error: ‘void* __builtin_memcpy(void*, const void*, long unsigned int)’ accessing 9223372036854775810 or more bytes at offsets -4611686018427387902 and [-4611686018427387903, 4611686018427387904] may overlap up to 9223372036854775813 bytes at offset -3 [-Werror=restrict]
  431 |         return static_cast<char_type*>(__builtin_memcpy(__s1, __s2, __n));
      |                                        ~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~
```
2022-11-25 23:41:38 +09:00
e3ccfcda3e update README 2022-11-25 23:08:17 +09:00
7f442e3acb fix an issue that inserting socket collapses the links in Node/ZipTie 2022-11-25 21:33:51 +09:00
c6c8bf96b6 improve Node/ExprTk to detect input type by the value dynamically 2022-11-25 20:56:28 +09:00
a6fdc76333 optimize Node/ExprTk by caching the expression 2022-11-25 00:51:59 +09:00
deee4f1e98 add load/store function to Node/ExprTk 2022-11-24 23:32:27 +09:00
3b3bdf834d fix reference of invalid memory in nf7::NodeRootLambda 2022-11-24 23:32:27 +09:00
86d0e2ed0b disable instruction limit of LuaJIT since speed costs a proper calling of hook 2022-11-24 22:19:55 +09:00
48e684b891 fix typo 2022-11-24 17:55:38 +09:00
a9345da3c4 fix wrong method to specify SYSTEM in target_include_directories 2022-11-24 17:55:29 +09:00
fce36b2c8f fix an issue that could refer an object already destructed 2022-11-24 13:21:29 +09:00
a08a02d71c fix System/Event to abort a lambda before dropping 2022-11-24 13:20:36 +09:00
6bca7d7909 fix compiler errors in old g++ 2022-11-24 13:04:08 +09:00
9b2ea8703e remove Value/Expr
Node/ExprTk is an alternative
2022-11-24 00:58:54 +09:00
dcc3668d6c add Node/ExprTk 2022-11-24 00:56:22 +09:00
1978d28316 implement full features of Node/ZipTie
I forgot to implement some cases for the switch
2022-11-23 14:21:58 +09:00
3346e1f9ba fix an issue that hot reloading doesn't occur after once GLSL compile failed 2022-11-23 14:03:19 +09:00
93312c6d04 add codes for profiling 2022-11-20 16:40:32 +09:00
d7fe51d946 fix an issue that thirdparty options are not applied 2022-11-20 16:07:42 +09:00
14db20fe67 fix an issue that cache is always dropped on LuaJIT/Node 2022-11-20 16:00:40 +09:00
482796bf60 decrease how often LuaJIT GC works 2022-11-20 15:59:57 +09:00
4f94ea4e3b fix nf7::Thread wasting SyncWorker 2022-11-20 14:15:09 +09:00
0c29b828c4 overload global new/delete to observe allocations 2022-11-20 13:21:02 +09:00
74207a0b63 fix wrong condition of cv 2022-11-20 12:59:27 +09:00
31924ce5b2 use tracy profiler's features 2022-11-20 12:30:40 +09:00
d33330b6c3 add new thirdparty lib, tracy 2022-11-20 11:37:06 +09:00
7f3e3e2064 add Value/Expr 2022-11-19 17:42:15 +09:00
8b9b7f669f add new thirdparty lib, ExprTk 2022-11-19 17:09:46 +09:00
5bd00c97d2 fix Node/Network sanitization 2022-11-19 13:24:38 +09:00
2cbf0035f4 improve performance of nf7::Thread 2022-11-19 13:24:14 +09:00
cef93dabbc fix an issue that a file of unknown type destructs its parent 2022-11-19 12:20:03 +09:00
704d8c93ca forbid nf7:import() while handling inputs in LuaJIT/Node 2022-11-19 12:07:09 +09:00
80d33bd5d4 fix compiler errors on MSVC 2022-11-18 08:33:12 -08:00
5c1f41d874 stablize FPS by calculating suitable sleep duration 2022-11-18 08:33:12 -08:00
c5c3ec769a improve LuaJIT/Node to run tasks synchronizedly 2022-11-18 23:06:23 +09:00
ceff117781 allow nf7::Thread to work by a task of other types 2022-11-18 16:28:04 +09:00
8a78450bcf fix compiler errors 2022-11-18 16:28:04 +09:00
b24e7d0ca5 add nf7::Stopwatch 2022-11-18 16:28:04 +09:00
3901179b51 fix an issue that pulse can't be generated manually when 'emit on change' is disabled on Value/Imm 2022-11-18 14:53:43 +09:00
4851f1eb28 remove Node/Imm and Node/Network/Initiator 2022-11-18 13:11:10 +09:00
49f106a951 fit style vars to current zoom factor before drawing Node/Network canvas 2022-11-18 12:53:27 +09:00
4917cd367e add new algorithms to Node/ZipTie 2022-11-18 12:40:13 +09:00
27a28d6c0d fix an issue that Value/Curve destroys an editor of Node/Network 2022-11-18 12:14:50 +09:00
61a97e6a32 fix an issue that Value/Imm emits a value even if 'emit on change' is disabled 2022-11-18 12:10:01 +09:00
02d6814eaf improve Node/Network to ignore broken links 2022-11-17 23:39:29 +09:00
49565e657c add Value/Imm to replace Node/Imm 2022-11-17 23:39:04 +09:00
eeb7dabd1f fix an issue that the first element of tuple array is stored at zero index of table on LuaJIT 2022-11-17 21:39:36 +09:00
3c2ed1731a add Node/ZipTie 2022-11-16 14:21:44 +09:00
06400d4ea4 make LuaJIT/Context perform GC after each task done 2022-11-15 20:02:04 +09:00
de0f2a4a8a fix context leak by yielding LuaJIT thread 2022-11-15 19:47:11 +09:00
5b566acd10 enhance context leak detector 2022-11-15 18:58:37 +09:00
881704fc49 fix lambda leaks 2022-11-15 18:58:37 +09:00
63dd28ab76 fix an issue that Nf7 could be finished before all tasks done 2022-11-15 18:58:37 +09:00
744e8e2506 fix an issue that lua_State is not deleted after destruction of Queue of LuaJIT/Context 2022-11-15 18:58:37 +09:00
d284221f2c fix leaks of luajit::Thread 2022-11-15 18:58:37 +09:00
00d9697b9d add leak checker for nf7::Context 2022-11-15 18:58:37 +09:00
77ac2e95c0 fix errors while building default root 2022-11-15 18:58:37 +09:00
3c67497229 fix an issue that active LuaJIT thread prevents Nf7 from shutting down 2022-11-15 18:58:37 +09:00
5c74c5cc40 fix an issue that I/O sockets are not copied by cloning Node/Network 2022-11-15 18:58:37 +09:00
7cd818fff8 add race-condition warning of Node/Mutex 2022-11-15 18:58:37 +09:00
e7d37b0adb add Node/Mutex 2022-11-15 11:44:34 +09:00
7f6fd26c71 fix an issue that a lambda of Node/Singleton could refer expired parent 2022-11-14 23:28:21 +09:00
d72ade7b37 fix linker error in freetype 2022-11-14 23:04:57 +09:00
fb05b5a7d8 fix MSVC warnings 2022-11-13 12:15:27 -08:00
7489bad3f8 fix use of invalid memory in GL/Framebuffer 2022-11-13 12:15:04 -08:00
e87c746e65 fix an issue that GL/Program doesn't emit 'done' output 2022-11-14 04:21:34 +09:00
646863170e enhance Lua std library 2022-11-14 01:20:25 +09:00
27b594ee54 fix an issue that sockets are not synchronized automatically on changing target of Node/Ref 2022-11-14 00:42:42 +09:00
4de67f65e6 add System/Node/Time 2022-11-14 00:40:48 +09:00
b6f0f9fa0a add Codec/StbImage 2022-11-13 17:42:06 +09:00
9e51470b11 add new thirdparty library, stb 2022-11-13 17:42:06 +09:00
90de6bf3f4 add an option to show all file types while adding new item of System/Dir 2022-11-13 17:42:06 +09:00
1a7b4fc632 rename vec to buf in input tuple interface of GL/Texture Node 2022-11-13 17:42:06 +09:00
14066c1256 change a method to specify file type description 2022-11-13 17:42:06 +09:00
6887410e19 replace System/Node file with System/Dir 2022-11-13 17:42:06 +09:00
9038511525 enable alpha blending in drawing by GL/Program 2022-11-12 22:41:10 +09:00
c5a357c10c fix OpenGL objects to drop cache when dependency is removed 2022-11-12 21:53:39 +09:00
935a6f5660 improve exception handling of nf7::GenericDir 2022-11-12 21:42:47 +09:00
3720893946 fix an issue that broken link remains after undoing Node/Ref on Node/Network 2022-11-12 21:28:04 +09:00
5894a303dd implement init event on System/Event 2022-11-12 16:33:29 +09:00
afa26d36d4 remove flags from some files 2022-11-12 13:01:33 +09:00
c9d1cd40f3 fix an issue that rebuild of LuaJIT/Node doesn't run after build failure 2022-11-12 12:57:48 +09:00
b15eee5d9c fix deadlock caused by double locking of GL objects 2022-11-12 11:52:59 +09:00
10946b9b7c fix use of uninitialized value 2022-11-12 11:52:19 +09:00
5ccfc9869e fix metrics unit of Font/Face output 2022-11-12 11:51:36 +09:00
6546f6b650 fix an issue of Node/Ref that use old lambda even after target is changed by user 2022-11-11 12:17:45 +09:00
ceb360c7c7 allow System/Dir to restore items removed once 2022-11-11 12:07:09 +09:00
e283e99276 rename System/Call to System/Node and improve file structure 2022-11-11 11:37:52 +09:00
5d79d7631b separate core logic into nf7::GenericDir from an implementation of System/Dir 2022-11-11 10:14:23 +09:00
1f5f46c925 fix possibility of race condition 2022-11-11 10:10:16 +09:00
803d93f3ec enhance UI of LuaJIT/Node 2022-11-10 22:52:09 +09:00
49e3d6c9c5 add Font/Context to default root 2022-11-10 22:40:24 +09:00
6fb6efe9a6 fix an issue that cannot drag and drop any files 2022-11-10 22:32:49 +09:00
53d4f9f107 improve UI visibility of System/Logger 2022-11-10 22:22:51 +09:00
e4b6e86ebf enhance System/Event 2022-11-09 12:14:50 +09:00
78dc01be2f fix an issue that nf7::File::Isolate() and nf7::File::MoveUnder() don't send events 2022-11-09 12:14:05 +09:00
c02d9e3b10 improve nf7::Env::Watcher interface 2022-11-09 11:29:34 +09:00
7a2ead6e6f add 'clone' option to item menu of System/Dir 2022-11-08 22:49:14 +09:00
4b61edd53e add Node/Singleton 2022-11-08 22:23:53 +09:00
728f85328b fix an issue that internal node types of Node/Network are not shown in a type list while adding new Node 2022-11-08 11:46:34 +09:00
df56eb3462 add nf7::Node::Meta and improve nf7::Node interface 2022-11-08 11:40:14 +09:00
173edff4a3 allow user to use custom font in UI 2022-11-06 23:21:45 +09:00
5894acda8c improve nf7::FileBase interface to prevent from forgetting calling super method 2022-11-06 23:04:38 +09:00
61865f4d26 improve nf7::GenericMemento to make a commit automatically when nf7::File::Event::kAdd event 2022-11-06 22:31:12 +09:00
f6be39f719 fix an issue that calling Touch() right before file deletion causes use-after-free 2022-11-06 21:55:00 +09:00
3a4d801f95 commonize file menu and tooltip 2022-11-06 13:16:45 +09:00
0d60b2401a replace nf7::gui::Config to nf7::gui::ConfigEditor 2022-11-06 12:26:22 +09:00
2ec4422c56 add nf7::Config interface 2022-11-06 12:07:44 +09:00
7ee26d431e tidy GUI codes 2022-11-06 11:30:28 +09:00
b463e112aa remove nf7::FileHolder 2022-11-06 11:13:07 +09:00
3e2d162d65 fix ImGui theme to improve visibility 2022-11-06 01:37:28 +09:00
267c25f798 implement sub-dockspace feature on System/ImGui 2022-11-06 01:04:25 +09:00
e8e0322e66 enforce System/ImGui is updated prior than others 2022-11-05 23:33:24 +09:00
bb799adfb4 improve nf7::gui::Window 2022-11-05 22:55:05 +09:00
dd14217f5b improve nf7::FileBase::Feature to install itself automatically by its constructor 2022-11-05 22:05:47 +09:00
245884fae7 simplify code of config UI 2022-11-05 15:26:47 +09:00
69690f2e29 fix an issue about buffer padding in GL/Texture 2022-11-05 11:31:25 +09:00
2e0f0a2303 fix an issue that cannot handle freetype errors properly 2022-11-05 11:30:59 +09:00
532fd141e3 add Font/Face 2022-11-05 00:04:27 +09:00
17d57ea3e5 add Font/Context 2022-11-04 14:30:09 +09:00
beb67589ef simplify code of Audio/Context 2022-11-04 14:29:56 +09:00
c5337f69c2 add new thirdparty library, freetype 2022-11-04 14:28:57 +09:00
84 changed files with 6169 additions and 3913 deletions

View File

@@ -6,6 +6,15 @@ 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) option(NF7_SANITIZE_THREAD "use thread sanitizer" OFF)
option(NF7_SANITIZE "use various sanitizer" OFF)
option(NF7_PROFILE "profiler" OFF)
if (NF7_SANITIZE_THREAD AND NF7_PROFILE)
message(FATAL_ERROR "NF7_SANITIZE_THREAD cannot be enabled with NF7_PROFILE")
endif()
if (NF7_SANITIZE AND NF7_SANITIZE_THREAD)
message(FATAL_ERROR "NF7_SANITIZE_THREAD cannot be enabled with NF7_SANITIZE")
endif()
set(NF7_OPTIONS_WARNING set(NF7_OPTIONS_WARNING
$<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>: $<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>:
@@ -15,16 +24,18 @@ set(NF7_OPTIONS_WARNING
$<$<CXX_COMPILER_ID:MSVC>: $<$<CXX_COMPILER_ID:MSVC>:
/W4 /WX /Zc:__cplusplus /external:anglebrackets /external:W0> /W4 /WX /Zc:__cplusplus /external:anglebrackets /external:W0>
) )
if (NF7_SANITIZE)
set(NF7_OPTIONS_SANITIZE
$<$<CONFIG:Debug>:$<$<CXX_COMPILER_ID:GNU>:
-fsanitize=address -fsanitize=undefined -fsanitize=leak -fno-omit-frame-pointer>>
)
endif()
if (NF7_SANITIZE_THREAD) if (NF7_SANITIZE_THREAD)
set(NF7_OPTIONS_SANITIZE set(NF7_OPTIONS_SANITIZE
$<$<CONFIG:Debug>:$<$<CXX_COMPILER_ID:GNU>: $<$<CONFIG:Debug>:$<$<CXX_COMPILER_ID:GNU>:
-fsanitize=thread -fno-omit-frame-pointer>> -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() endif()
set(NF7_GENERATED_INCLUDE_DIR "${PROJECT_BINARY_DIR}/include/generated") set(NF7_GENERATED_INCLUDE_DIR "${PROJECT_BINARY_DIR}/include/generated")
@@ -67,14 +78,17 @@ target_sources(nf7
common/aggregate_command.hh common/aggregate_command.hh
common/aggregate_promise.hh common/aggregate_promise.hh
common/audio_queue.hh common/audio_queue.hh
common/config.hh
common/context_owner.hh
common/dir.hh common/dir.hh
common/dir_item.hh common/dir_item.hh
common/factory.hh common/factory.hh
common/file_base.hh common/file_base.hh
common/file_holder.hh common/font_queue.hh
common/file_holder.cc
common/future.hh common/future.hh
common/generic_config.hh
common/generic_context.hh common/generic_context.hh
common/generic_dir.hh
common/generic_history.hh common/generic_history.hh
common/generic_memento.hh common/generic_memento.hh
common/generic_type_info.hh common/generic_type_info.hh
@@ -84,19 +98,15 @@ target_sources(nf7
common/gl_obj.hh common/gl_obj.hh
common/gl_obj.cc common/gl_obj.cc
common/gl_shader_preproc.hh common/gl_shader_preproc.hh
common/gui_config.hh common/gui.hh
common/gui_context.hh common/gui.cc
common/gui_dnd.hh common/gui_dnd.hh
common/gui_file.hh
common/gui_file.cc
common/gui_node.hh
common/gui_popup.hh
common/gui_popup.cc
common/gui_timeline.hh common/gui_timeline.hh
common/gui_timeline.cc common/gui_timeline.cc
common/gui_value.hh common/gui_value.hh
common/gui_value.cc common/gui_value.cc
common/gui_window.hh common/gui_window.hh
common/gui_window.cc
common/history.hh common/history.hh
common/life.hh common/life.hh
common/logger.hh common/logger.hh
@@ -110,7 +120,6 @@ target_sources(nf7
common/luajit_thread.cc common/luajit_thread.cc
common/memento.hh common/memento.hh
common/memento_recorder.hh common/memento_recorder.hh
common/mutable_memento.hh
common/mutex.hh common/mutex.hh
common/nfile.hh common/nfile.hh
common/nfile_watcher.hh common/nfile_watcher.hh
@@ -118,16 +127,18 @@ target_sources(nf7
common/node_link_store.hh common/node_link_store.hh
common/node_root_lambda.hh common/node_root_lambda.hh
common/ptr_selector.hh common/ptr_selector.hh
common/pure_node_file.hh
common/queue.hh common/queue.hh
common/ring_buffer.hh common/ring_buffer.hh
common/sequencer.hh common/sequencer.hh
common/squashed_history.hh common/squashed_history.hh
common/stopwatch.hh
common/task.hh common/task.hh
common/thread.hh common/thread.hh
common/timed_queue.hh common/timed_queue.hh
common/util_algorithm.hh common/util_algorithm.hh
common/util_string.hh
common/value.hh common/value.hh
common/yaml_nf7.hh
common/yas_enum.hh common/yas_enum.hh
common/yas_imgui.hh common/yas_imgui.hh
common/yas_imnodes.hh common/yas_imnodes.hh
@@ -141,26 +152,35 @@ target_sources(nf7
file/audio_context.cc file/audio_context.cc
file/audio_device.cc file/audio_device.cc
file/codec_stbimage.cc
file/font_context.cc
file/font_face.cc
file/gl_obj.cc file/gl_obj.cc
file/luajit_context.cc file/luajit_context.cc
file/luajit_node.cc file/luajit_node.cc
file/node_imm.cc file/node_exprtk.cc
file/node_mutex.cc
file/node_network.cc file/node_network.cc
file/node_ref.cc file/node_ref.cc
file/node_singleton.cc
file/node_ziptie.cc
file/sequencer_adaptor.cc file/sequencer_adaptor.cc
file/sequencer_call.cc file/sequencer_call.cc
file/sequencer_timeline.cc file/sequencer_timeline.cc
file/system_call.cc
file/system_dir.cc file/system_dir.cc
file/system_event.cc file/system_event.cc
file/system_imgui.cc file/system_imgui.cc
file/system_logger.cc file/system_logger.cc
file/system_nfile.cc file/system_nfile.cc
file/system_node.cc
file/value_curve.cc file/value_curve.cc
file/value_imm.cc
file/value_plot.cc file/value_plot.cc
) )
target_link_libraries(nf7 target_link_libraries(nf7
PRIVATE PRIVATE
exprtk
freetype
glew glew
glfw glfw
imgui imgui
@@ -171,6 +191,8 @@ target_link_libraries(nf7
magic_enum magic_enum
miniaudio miniaudio
source_location source_location
stb
TracyClient
yas yas
yaml-cpp yaml-cpp
) )

View File

@@ -1,8 +1,67 @@
nf7 nf7
==== ====
node-based programming language portable visual programming platform
## REQUIREMENTS
- **OS**: Windows 10, Linux, and Chrome OS
- **CPU**: x86_64
- **GPU**: OpenGL 3.3 or higher (except Intel HD Graphics)
- **RAM**: 512 MB or more (depends on what you do)
- **Storage**: 100 MB or more (depends on what you do)
## INSTALLING
Build Nf7 by yourself, or download the binary from releases (unavailable on mirror repo).
It's expected to copy and put the executable on each of your projects to prevent old works from corruption by Nf7's update.
## BUILDING
### Succeeded Environments
- Windows 10 / CMake 3.20.21032501-MSVC_2 / cl 19.29.30137
- Arch Linux / CMake 3.24.2 / g++ 12.2.0
- Ubuntu (Chrome OS) / CMake 3.18.4 / g++ 10.2.1
### Windows
```
PS> mkdir build
PS> cd build
PS> cmake .. # add build options before the double dots
PS> cmake --build . --config Release
```
### Linux
```
$ mkdir build
$ cd build
$ cmake -DCMAKE_BUILD_TYPE=Release .. # add build options before the double dots
$ make
```
### CMake build options
|name|default|description|
|--|--|--|
| `NF7_STATIC` | `ON` | links all libraries statically |
| `NF7_SANITIZE_THREAD` | `OFF` | enables thread-sanitizer (g++ only) |
| `NF7_SANITIZE` | `OFF` | enables address/undefined/leak sanitizers (g++ only) |
| `NF7_PROFILE` | `OFF` | enables Tracy features |
The following condition must be met:
```
!(NF7_SANITIZE_THREAD && NF7_SANITIZE) && // address-sanitizer cannot be with thread-sanitizer
!(NF7_SANITIZE_THREAD && NF7_PROFILE) // TracyClient causes error because of thread-sanitizer
```
## DEPENDENCIES
see `thirdparty/CMakeLists.txt`
## LICENSE ## LICENSE
WTFPLv2 Do What The Fuck You Want To Public License v2
*-- expression has nothing without the real free --*

View File

@@ -22,7 +22,6 @@ class Queue : public nf7::File::Interface {
Queue& operator=(Queue&&) = delete; Queue& operator=(Queue&&) = delete;
// thread-safe // thread-safe
// WARNING: when failed to create ma_context, nullptr is passed
virtual void Push(const std::shared_ptr<nf7::Context>&, Task&&) noexcept = 0; virtual void Push(const std::shared_ptr<nf7::Context>&, Task&&) noexcept = 0;
virtual std::shared_ptr<Queue> self() noexcept = 0; virtual std::shared_ptr<Queue> self() noexcept = 0;

22
common/config.hh Normal file
View File

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

50
common/context_owner.hh Normal file
View File

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

View File

@@ -9,13 +9,20 @@ namespace nf7 {
class DirItem : public File::Interface { class DirItem : public File::Interface {
public: public:
enum Flag : uint8_t { enum Flag : uint16_t {
kNone = 0, kNone = 0,
kTree = 1 << 0, kTree = 1 << 0,
kMenu = 1 << 1, kMenu = 1 << 1,
kTooltip = 1 << 2, kTooltip = 1 << 2,
kWidget = 1 << 3, kWidget = 1 << 3,
kDragDropTarget = 1 << 4, kDragDropTarget = 1 << 4,
// Update() will be called earlier than other items.
// This is used by some system files and meaningless in most of cases.
kEarlyUpdate = 1 << 5,
// suggests to forbid to move/remove/clone through UI
kImportant = 1 << 6,
}; };
using Flags = uint8_t; using Flags = uint8_t;

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <algorithm>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
@@ -12,26 +13,27 @@ class FileBase : public nf7::File {
public: public:
class Feature { class Feature {
public: public:
Feature() = default; Feature() = delete;
Feature(nf7::FileBase& f) noexcept {
f.feats_.push_back(this);
}
virtual ~Feature() = default; virtual ~Feature() = default;
Feature(const Feature&) = delete; Feature(const Feature&) = delete;
Feature(Feature&&) = delete; Feature(Feature&&) = delete;
Feature& operator=(const Feature&) = delete; Feature& operator=(const Feature&) = delete;
Feature& operator=(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 nf7::File* Find(std::string_view) const noexcept { return nullptr; }
virtual void Handle(const nf7::File::Event&) noexcept { } virtual void Handle(const nf7::File::Event&) noexcept { }
virtual void Update() noexcept { } virtual void Update() noexcept { }
}; };
FileBase(const nf7::File::TypeInfo& t, using nf7::File::File;
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 { nf7::File* Find(std::string_view name) const noexcept final {
if (auto ret = PreFind(name)) {
return ret;
}
for (auto feat : feats_) { for (auto feat : feats_) {
if (auto ret = feat->Find(name)) { if (auto ret = feat->Find(name)) {
return ret; return ret;
@@ -39,21 +41,29 @@ class FileBase : public nf7::File {
} }
return nullptr; return nullptr;
} }
void Handle(const nf7::File::Event& ev) noexcept override { void Handle(const nf7::File::Event& ev) noexcept final {
PreHandle(ev);
for (auto feat : feats_) { for (auto feat : feats_) {
feat->Handle(ev); feat->Handle(ev);
} }
PostHandle(ev);
} }
void Update() noexcept override { void Update() noexcept final {
PreUpdate();
for (auto feat : feats_) { for (auto feat : feats_) {
feat->Update(); feat->Update();
} }
PostUpdate();
} }
protected: protected:
void Install(Feature& f) noexcept { virtual nf7::File* PreFind(std::string_view) const noexcept { return nullptr; }
feats_.push_back(&f);
} virtual void PreHandle(const nf7::File::Event&) noexcept { }
virtual void PostHandle(const nf7::File::Event&) noexcept { }
virtual void PreUpdate() noexcept { }
virtual void PostUpdate() noexcept { }
private: private:
std::vector<Feature*> feats_; std::vector<Feature*> feats_;

View File

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

View File

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

57
common/font_face.hh Normal file
View File

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

41
common/font_queue.hh Normal file
View File

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

52
common/generic_config.hh Normal file
View File

@@ -0,0 +1,52 @@
#pragma once
#include <functional>
#include <string>
#include "nf7.hh"
#include "common/config.hh"
#include "common/generic_memento.hh"
namespace nf7 {
template <typename T>
concept ConfigData = requires (T& x) {
{ x.Stringify() } -> std::convertible_to<std::string>;
x.Parse(std::string {});
};
class GenericConfig : public nf7::Config {
public:
GenericConfig() = delete;
template <ConfigData T>
GenericConfig(nf7::GenericMemento<T>& mem) noexcept {
stringify_ = [&mem]() {
return mem->Stringify();
};
parse_ = [&mem](auto& str) {
mem->Parse(str);
mem.Commit();
};
}
GenericConfig(const GenericConfig&) = delete;
GenericConfig(GenericConfig&&) = delete;
GenericConfig& operator=(const GenericConfig&) = delete;
GenericConfig& operator=(GenericConfig&&) = delete;
std::string Stringify() const noexcept override {
return stringify_();
}
void Parse(const std::string& str) override {
parse_(str);
}
private:
std::function<std::string()> stringify_;
std::function<void(const std::string&)> parse_;
};
} // namespace nf7

173
common/generic_dir.hh Normal file
View File

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

View File

@@ -7,22 +7,22 @@
#include "nf7.hh" #include "nf7.hh"
#include "common/mutable_memento.hh" #include "common/file_base.hh"
#include "common/generic_context.hh"
#include "common/memento.hh"
namespace nf7 { namespace nf7 {
template <typename T> template <typename T>
class GenericMemento : public nf7::MutableMemento { class GenericMemento : public nf7::FileBase::Feature, public nf7::Memento {
public: public:
class CustomTag; class CustomTag;
GenericMemento(T&& data, nf7::File* f = nullptr) noexcept : GenericMemento(nf7::FileBase& f, T&& data) noexcept :
nf7::FileBase::Feature(f),
file_(f), initial_(T(data)), data_(std::move(data)) { file_(f), initial_(T(data)), data_(std::move(data)) {
} }
GenericMemento(T&& data, nf7::File& f) noexcept :
GenericMemento(std::move(data), &f) {
}
~GenericMemento() noexcept { ~GenericMemento() noexcept {
tag_ = nullptr; tag_ = nullptr;
last_ = nullptr; last_ = nullptr;
@@ -50,20 +50,26 @@ class GenericMemento : public nf7::MutableMemento {
tag_ = tag; tag_ = tag;
last_ = tag; last_ = tag;
onRestore(); onRestore();
if (file_) file_->Touch(); file_.Touch();
} }
void Commit() noexcept override { void Commit(bool quiet = false) noexcept {
tag_ = nullptr; tag_ = nullptr;
onCommit(); onCommit();
if (file_) file_->Touch(); if (!quiet) file_.Touch();
} }
void CommitAmend() noexcept override { void CommitQuiet() noexcept {
Commit(true);
}
void CommitAmend(bool quiet = false) noexcept {
if (!tag_) return; if (!tag_) return;
auto itr = map_.find(tag_->id()); auto itr = map_.find(tag_->id());
assert(itr != map_.end()); assert(itr != map_.end());
itr->second = data_; itr->second = data_;
onCommit(); onCommit();
if (file_) file_->Touch(); if (!quiet) file_.Touch();
}
void CommitAmendQuiet() noexcept {
CommitAmend(true);
} }
T& data() noexcept { return data_; } T& data() noexcept { return data_; }
@@ -81,7 +87,7 @@ class GenericMemento : public nf7::MutableMemento {
std::function<void()> onCommit = [](){}; std::function<void()> onCommit = [](){};
private: private:
nf7::File* const file_; nf7::File& file_;
const T initial_; const T initial_;
T data_; T data_;
@@ -91,6 +97,19 @@ class GenericMemento : public nf7::MutableMemento {
std::shared_ptr<nf7::Memento::Tag> tag_; std::shared_ptr<nf7::Memento::Tag> tag_;
std::shared_ptr<nf7::Memento::Tag> last_; std::shared_ptr<nf7::Memento::Tag> last_;
void Handle(const nf7::File::Event& e) noexcept override {
switch (e.type) {
case nf7::File::Event::kAdd:
file_.env().ExecMain(
std::make_shared<nf7::GenericContext>(file_),
[this]() { CommitQuiet(); });
return;
default:
return;
}
}
}; };
template <typename T> template <typename T>

View File

@@ -14,47 +14,40 @@
namespace nf7 { namespace nf7 {
template <typename T>
concept GenericTypeInfo_UpdateTooltip_ = requires() { T::UpdateTypeTooltip(); };
template <typename T>
concept GenericTypeInfo_Description_ = requires() { T::kTypeDescription; };
template <typename T> template <typename T>
class GenericTypeInfo : public nf7::File::TypeInfo { class GenericTypeInfo : public nf7::File::TypeInfo {
public: public:
GenericTypeInfo(const std::string& name, std::unordered_set<std::string>&& v) noexcept : GenericTypeInfo(const std::string& name,
TypeInfo(name, AddFlags(std::move(v))) { std::unordered_set<std::string>&& v,
const std::string& desc = "(no description)") noexcept :
TypeInfo(name, AddFlags(std::move(v))), desc_(desc) {
} }
std::unique_ptr<nf7::File> Deserialize(nf7::Deserializer& ar) const override std::unique_ptr<nf7::File> Deserialize(nf7::Deserializer& ar) const override
try { try {
return std::make_unique<T>(ar); if constexpr (std::is_constructible<T, nf7::Deserializer&>::value) {
} catch (nf7::Exception&) { return std::make_unique<T>(ar);
throw nf7::DeserializeException {"deserialization failed"}; } else {
throw nf7::Exception {name() + " is not a deserializable"};
}
} catch (std::exception&) { } catch (std::exception&) {
throw nf7::DeserializeException {"deserialization failed"}; throw nf7::DeserializeException {"deserialization failed ("+name()+")"};
} }
std::unique_ptr<File> Create(nf7::Env& env) const override { std::unique_ptr<File> Create(nf7::Env& env) const override {
if constexpr (std::is_constructible<T, nf7::Env&>::value) { if constexpr (std::is_constructible<T, nf7::Env&>::value) {
return std::make_unique<T>(env); return std::make_unique<T>(env);
} else { } else {
throw nf7::Exception {name()+" has no factory without parameters"}; throw nf7::Exception {name()+" has no default factory"};
} }
} }
void UpdateTooltip() const noexcept override { void UpdateTooltip() const noexcept override {
if constexpr (nf7::GenericTypeInfo_UpdateTooltip_<T>) { ImGui::TextUnformatted(desc_.c_str());
T::UpdateTypeTooltip();
} else if constexpr (nf7::GenericTypeInfo_Description_<T>) {
ImGui::TextUnformatted(T::kTypeDescription);
} else {
ImGui::TextUnformatted("(no description)");
}
} }
private: private:
std::string desc_;
static std::unordered_set<std::string> AddFlags( static std::unordered_set<std::string> AddFlags(
std::unordered_set<std::string>&& flags) noexcept { std::unordered_set<std::string>&& flags) noexcept {
if (std::is_constructible<T, nf7::Env&>::value) { if (std::is_constructible<T, nf7::Env&>::value) {

View File

@@ -7,8 +7,11 @@
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <sstream> #include <sstream>
#include <unordered_set>
#include <vector> #include <vector>
#include <tracy/Tracy.hpp>
#include "common/aggregate_promise.hh" #include "common/aggregate_promise.hh"
#include "common/factory.hh" #include "common/factory.hh"
#include "common/future.hh" #include "common/future.hh"
@@ -60,6 +63,7 @@ nf7::Future<std::shared_ptr<Obj<Obj_BufferMeta>>> Obj_BufferMeta::Create(
const std::shared_ptr<nf7::Context>& ctx) const noexcept { const std::shared_ptr<nf7::Context>& ctx) const noexcept {
nf7::Future<std::shared_ptr<Obj<Obj_BufferMeta>>>::Promise pro {ctx}; nf7::Future<std::shared_ptr<Obj<Obj_BufferMeta>>>::Promise pro {ctx};
ctx->env().ExecGL(ctx, [=, *this]() mutable { ctx->env().ExecGL(ctx, [=, *this]() mutable {
ZoneScopedN("create buffer");
GLuint id; GLuint id;
glGenBuffers(1, &id); glGenBuffers(1, &id);
pro.Return(std::make_shared<Obj<Obj_BufferMeta>>(ctx, id, *this)); pro.Return(std::make_shared<Obj<Obj_BufferMeta>>(ctx, id, *this));
@@ -72,6 +76,8 @@ nf7::Future<std::shared_ptr<Obj<Obj_TextureMeta>>> Obj_TextureMeta::Create(
const std::shared_ptr<nf7::Context>& ctx) const noexcept { const std::shared_ptr<nf7::Context>& ctx) const noexcept {
nf7::Future<std::shared_ptr<Obj<Obj_TextureMeta>>>::Promise pro {ctx}; nf7::Future<std::shared_ptr<Obj<Obj_TextureMeta>>>::Promise pro {ctx};
ctx->env().ExecGL(ctx, [=, *this]() mutable { ctx->env().ExecGL(ctx, [=, *this]() mutable {
ZoneScopedN("create texture");
GLuint id; GLuint id;
glGenTextures(1, &id); glGenTextures(1, &id);
@@ -107,6 +113,8 @@ nf7::Future<std::shared_ptr<Obj<Obj_ShaderMeta>>> Obj_ShaderMeta::Create(
const std::string& src) const noexcept { const std::string& src) const noexcept {
nf7::Future<std::shared_ptr<Obj<Obj_ShaderMeta>>>::Promise pro {ctx}; nf7::Future<std::shared_ptr<Obj<Obj_ShaderMeta>>>::Promise pro {ctx};
ctx->env().ExecGL(ctx, [=, *this]() mutable { ctx->env().ExecGL(ctx, [=, *this]() mutable {
ZoneScopedN("create shader");
const auto t = gl::ToEnum(type); const auto t = gl::ToEnum(type);
const auto id = glCreateShader(t); const auto id = glCreateShader(t);
if (id == 0) { if (id == 0) {
@@ -118,10 +126,13 @@ nf7::Future<std::shared_ptr<Obj<Obj_ShaderMeta>>> Obj_ShaderMeta::Create(
"#version 330\n" "#version 330\n"
"#extension GL_ARB_shading_language_include: require\n"; "#extension GL_ARB_shading_language_include: require\n";
const GLchar* str[] = {kHeader, src.c_str()}; {
glShaderSource(id, 2, str, nullptr); ZoneScopedN("compile");
glCompileShader(id); const GLchar* str[] = {kHeader, src.c_str()};
assert(0 == glGetError()); glShaderSource(id, 2, str, nullptr);
glCompileShader(id);
assert(0 == glGetError());
}
GLint status; GLint status;
glGetShaderiv(id, GL_COMPILE_STATUS, &status); glGetShaderiv(id, GL_COMPILE_STATUS, &status);
@@ -154,6 +165,8 @@ nf7::Future<std::shared_ptr<Obj<Obj_ProgramMeta>>> Obj_ProgramMeta::Create(
nf7::Future<std::shared_ptr<Obj<Obj_ProgramMeta>>>::Promise pro {ctx}; 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&) { apro.future().Chain(nf7::Env::kGL, ctx, pro, [*this, ctx, shs = std::move(shs)](auto&) {
ZoneScopedN("create program");
// check all shaders // check all shaders
for (auto& sh : shs) { sh.value(); } for (auto& sh : shs) { sh.value(); }
@@ -167,7 +180,11 @@ nf7::Future<std::shared_ptr<Obj<Obj_ProgramMeta>>> Obj_ProgramMeta::Create(
for (auto& sh : shs) { for (auto& sh : shs) {
glAttachShader(id, (*sh.value())->id()); glAttachShader(id, (*sh.value())->id());
} }
glLinkProgram(id);
{
ZoneScopedN("link");
glLinkProgram(id);
}
// check status // check status
GLint status; GLint status;
@@ -192,8 +209,14 @@ void Obj_ProgramMeta::ApplyState() const noexcept {
glDepthRange(depth->near, depth->far); glDepthRange(depth->near, depth->far);
glDepthFunc(gl::ToEnum(depth->func)); glDepthFunc(gl::ToEnum(depth->func));
} }
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
} }
void Obj_ProgramMeta::RevertState() const noexcept { void Obj_ProgramMeta::RevertState() const noexcept {
glBlendFunc(GL_ONE, GL_ZERO);
glDisable(GL_BLEND);
if (depth) { if (depth) {
glDisable(GL_DEPTH_TEST); glDisable(GL_DEPTH_TEST);
} }
@@ -215,6 +238,8 @@ try {
LockAttachments(ctx).Chain( LockAttachments(ctx).Chain(
nf7::Env::kGL, ctx, pro, nf7::Env::kGL, ctx, pro,
[*this, ctx, pro](auto& bufs) mutable { [*this, ctx, pro](auto& bufs) mutable {
ZoneScopedN("create va");
// check all buffers // check all buffers
if (index) { if (index) {
assert(bufs.index); assert(bufs.index);
@@ -278,8 +303,7 @@ try {
LockedAttachmentsFuture::Promise pro {ctx}; LockedAttachmentsFuture::Promise pro {ctx};
// lock array buffers // lock array buffers
std::vector<gl::Buffer::Factory::Product> attrs_fu; std::unordered_map<nf7::File::Id, gl::Buffer::Factory::Product> attrs_fu_map;
attrs_fu.reserve(attrs.size());
for (size_t i = 0; i < attrs.size(); ++i) { for (size_t i = 0; i < attrs.size(); ++i) {
const auto& attr = attrs[i]; const auto& attr = attrs[i];
@@ -292,8 +316,20 @@ try {
static_cast<size_t>(attr.stride) * (vhint.instances-1) + attr.offset: static_cast<size_t>(attr.stride) * (vhint.instances-1) + attr.offset:
size_t {0}; size_t {0};
attrs_fu.push_back(Lock(attr.buffer, gl::BufferTarget::Array, required)); if (attrs_fu_map.end() == attrs_fu_map.find(attr.buffer)) {
apro.Add(attrs_fu.back()); auto [itr, add] = attrs_fu_map.emplace(
attr.buffer, Lock(attr.buffer, gl::BufferTarget::Array, required));
(void) add;
apro.Add(itr->second);
}
}
// serialize attrs_fu_map
std::vector<gl::Buffer::Factory::Product> attrs_fu;
for (const auto& attr : attrs) {
auto itr = attrs_fu_map.find(attr.buffer);
assert(itr != attrs_fu_map.end());
attrs_fu.push_back(itr->second);
} }
// lock index buffers (it must be the last element in `fus`) // lock index buffers (it must be the last element in `fus`)
@@ -322,6 +358,8 @@ nf7::Future<std::shared_ptr<Obj<Obj_FramebufferMeta>>> Obj_FramebufferMeta::Crea
nf7::Future<std::shared_ptr<Obj<Obj_FramebufferMeta>>>::Promise pro {ctx}; nf7::Future<std::shared_ptr<Obj<Obj_FramebufferMeta>>>::Promise pro {ctx};
LockAttachments(ctx). LockAttachments(ctx).
Chain(nf7::Env::kGL, ctx, pro, [ctx, *this](auto& k) mutable { Chain(nf7::Env::kGL, ctx, pro, [ctx, *this](auto& k) mutable {
ZoneScopedN("create fb");
GLuint id; GLuint id;
glGenFramebuffers(1, &id); glGenFramebuffers(1, &id);
@@ -367,6 +405,20 @@ Obj_FramebufferMeta::LockedAttachmentsFuture Obj_FramebufferMeta::LockAttachment
try { try {
auto ret = std::make_shared<LockedAttachments>(); auto ret = std::make_shared<LockedAttachments>();
// file duplication check for preventing deadlock by double lock
std::unordered_set<nf7::File::Id> locked;
for (const auto& col : colors) {
if (col && col->tex && !locked.insert(col->tex).second) {
throw nf7::Exception {"attached color texture is duplicated"};
}
}
if (depth && depth->tex && !locked.insert(depth->tex).second) {
throw nf7::Exception {"attached depth texture is duplicated"};
}
if (stencil && stencil->tex && !locked.insert(stencil->tex).second) {
throw nf7::Exception {"attached stencil texture is duplicated"};
}
nf7::AggregatePromise apro {ctx}; nf7::AggregatePromise apro {ctx};
LockedAttachmentsFuture::Promise pro {ctx}; LockedAttachmentsFuture::Promise pro {ctx};

246
common/gui.cc Normal file
View File

@@ -0,0 +1,246 @@
#include "common/gui.hh"
#include <optional>
#include <string>
#include <imgui.h>
#include <imgui_stdlib.h>
#include <ImNodes.h>
#include "nf7.hh"
#include "common/config.hh"
#include "common/dir_item.hh"
#include "common/gui_dnd.hh"
namespace nf7::gui {
void FileMenuItems(nf7::File& f) noexcept {
auto ditem = f.interface<nf7::DirItem>();
auto config = f.interface<nf7::Config>();
if (ImGui::MenuItem("request focus")) {
f.env().Handle({.id = f.id(), .type = nf7::File::Event::kReqFocus});
}
if (ImGui::MenuItem("copy path")) {
ImGui::SetClipboardText(f.abspath().Stringify().c_str());
}
if (ditem && (ditem->flags() & nf7::DirItem::kMenu)) {
ImGui::Separator();
ditem->UpdateMenu();
}
if (config) {
ImGui::Separator();
if (ImGui::BeginMenu("config")) {
static nf7::gui::ConfigEditor ed;
ed(*config);
ImGui::EndMenu();
}
}
}
void FileTooltip(nf7::File& f) noexcept {
auto ditem = f.interface<nf7::DirItem>();
ImGui::TextUnformatted(f.type().name().c_str());
ImGui::SameLine();
ImGui::TextDisabled(f.abspath().Stringify().c_str());
if (ditem && (ditem->flags() & nf7::DirItem::kTooltip)) {
ImGui::Indent();
ditem->UpdateTooltip();
ImGui::Unindent();
}
}
bool PathButton(const char* id, nf7::File::Path& p, nf7::File& base) noexcept {
bool ret = false;
const auto pstr = p.Stringify();
const auto w = ImGui::CalcItemWidth();
ImGui::PushID(id);
// widget body
{
nf7::File* file = nullptr;
try {
file = &base.ResolveOrThrow(p);
} catch (nf7::Exception&) {
}
const auto display = pstr.empty()? "(empty)": pstr;
if (ImGui::Button(display.c_str(), {w, 0})) {
ImGui::OpenPopup("editor");
}
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
if (file) {
FileTooltip(*file);
} else {
ImGui::TextDisabled("(file missing)");
}
ImGui::EndTooltip();
}
if (ImGui::BeginPopupContextItem()) {
if (file) {
nf7::gui::FileMenuItems(*file);
} else {
ImGui::TextDisabled("(file missing)");
}
ImGui::EndPopup();
}
if (ImGui::BeginDragDropTarget()) {
if (auto dp = nf7::gui::dnd::Accept<nf7::File::Path>(nf7::gui::dnd::kFilePath)) {
p = std::move(*dp);
ret = true;
}
ImGui::EndDragDropTarget();
}
if (id[0] != '#') {
ImGui::SameLine();
ImGui::TextUnformatted(id);
}
}
// editor popup
if (ImGui::BeginPopup("editor")) {
static std::string editing_str;
if (ImGui::IsWindowAppearing()) {
editing_str = pstr;
}
bool submit = false;
if (ImGui::InputText("path", &editing_str, ImGuiInputTextFlags_EnterReturnsTrue)) {
submit = true;
}
std::optional<nf7::File::Path> newpath;
try {
newpath = nf7::File::Path::Parse(editing_str);
} catch (nf7::Exception& e) {
ImGui::Text("invalid path: %s", e.msg().c_str());
}
ImGui::BeginDisabled(!newpath);
if (ImGui::Button("ok")) {
submit = true;
}
ImGui::EndDisabled();
if (newpath && submit) {
ImGui::CloseCurrentPopup();
p = std::move(*newpath);
ret = true;
}
ImGui::EndPopup();
}
ImGui::PopID();
return ret;
}
void ContextStack(const nf7::Context& ctx) noexcept {
for (auto p = ctx.parent(); p; p = p->parent()) {
auto f = ctx.env().GetFile(p->initiator());
const auto path = f? f->abspath().Stringify(): "[missing file]";
ImGui::TextUnformatted(path.c_str());
ImGui::TextDisabled("%s", p->GetDescription().c_str());
}
}
void NodeSocket() noexcept {
auto win = ImGui::GetCurrentWindow();
const auto em = ImGui::GetFontSize();
const auto lh = std::max(win->DC.CurrLineSize.y, em);
const auto rad = em/2 / ImNodes::CanvasState().Zoom;
const auto sz = ImVec2(rad*2, lh);
const auto pos = ImGui::GetCursorScreenPos() + sz/2;
auto dlist = ImGui::GetWindowDrawList();
dlist->AddCircleFilled(
pos, rad, IM_COL32(100, 100, 100, 100));
dlist->AddCircleFilled(
pos, rad*.8f, IM_COL32(200, 200, 200, 200));
ImGui::Dummy(sz);
}
void NodeInputSockets(std::span<const std::string> names) noexcept {
ImGui::BeginGroup();
for (auto& name : names) {
if (ImNodes::BeginInputSlot(name.c_str(), 1)) {
ImGui::AlignTextToFramePadding();
nf7::gui::NodeSocket();
ImGui::SameLine();
ImGui::TextUnformatted(name.c_str());
ImNodes::EndSlot();
}
}
ImGui::EndGroup();
}
void NodeOutputSockets(std::span<const std::string> names) noexcept {
float maxw = 0;
for (auto& name : names) {
maxw = std::max(maxw, ImGui::CalcTextSize(name.c_str()).x);
}
ImGui::BeginGroup();
for (auto& name : names) {
const auto w = ImGui::CalcTextSize(name.c_str()).x;
ImGui::SetCursorPosX(ImGui::GetCursorPosX()+maxw-w);
if (ImNodes::BeginOutputSlot(name.c_str(), 1)) {
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted(name.c_str());
ImGui::SameLine();
nf7::gui::NodeSocket();
ImNodes::EndSlot();
}
}
ImGui::EndGroup();
}
void ConfigEditor::operator()(nf7::Config& config) noexcept {
ImGui::PushID(this);
if (ImGui::IsWindowAppearing()) {
text_ = config.Stringify();
msg_ = "";
mod_ = false;
}
mod_ |= ImGui::InputTextMultiline("##config", &text_);
ImGui::BeginDisabled(!mod_);
if (ImGui::Button("apply")) {
try {
config.Parse(text_);
msg_ = "";
mod_ = false;
} catch (nf7::Exception& e) {
msg_ = e.msg();
} catch (std::exception& e) {
msg_ = e.what();
}
}
ImGui::EndDisabled();
ImGui::SameLine();
if (ImGui::Button("restore")) {
text_ = config.Stringify();
msg_ = "";
mod_ = false;
}
if (msg_.size()) {
ImGui::Bullet();
ImGui::TextUnformatted(msg_.c_str());
}
ImGui::PopID();
}
} // namespace nf7::gui

View File

@@ -1,15 +1,40 @@
#pragma once #pragma once
#include <cinttypes> #include <cinttypes>
#include <cstdint>
#include <cstring>
#include <string> #include <string>
#include <imgui.h>
#include "nf7.hh" #include "nf7.hh"
#include "common/config.hh"
namespace nf7::gui { namespace nf7::gui {
// widgets
void FileMenuItems(nf7::File& f) noexcept;
void FileTooltip(nf7::File& f) noexcept;
bool PathButton(const char* id, nf7::File::Path&, nf7::File&) noexcept;
void ContextStack(const nf7::Context&) noexcept;
void NodeSocket() noexcept;
void NodeInputSockets(std::span<const std::string>) noexcept;
void NodeOutputSockets(std::span<const std::string>) noexcept;
struct ConfigEditor {
public:
void operator()(nf7::Config&) noexcept;
private:
std::string text_;
std::string msg_;
bool mod_;
};
// stringify utility
inline std::string GetContextDisplayName(const nf7::Context& ctx) noexcept { inline std::string GetContextDisplayName(const nf7::Context& ctx) noexcept {
auto f = ctx.env().GetFile(ctx.initiator()); auto f = ctx.env().GetFile(ctx.initiator());
@@ -21,7 +46,6 @@ inline std::string GetContextDisplayName(const nf7::Context& ctx) noexcept {
return initiator + " " + buf; return initiator + " " + buf;
} }
inline std::string GetParentContextDisplayName(const nf7::Context& ctx) noexcept { inline std::string GetParentContextDisplayName(const nf7::Context& ctx) noexcept {
if (auto parent = ctx.parent()) { if (auto parent = ctx.parent()) {
return nf7::gui::GetContextDisplayName(*parent); return nf7::gui::GetContextDisplayName(*parent);
@@ -32,15 +56,4 @@ inline std::string GetParentContextDisplayName(const nf7::Context& ctx) noexcept
} }
} }
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 } // namespace nf7::gui

View File

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

View File

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

View File

@@ -1,88 +0,0 @@
#pragma once
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include "nf7.hh"
#include "common/file_base.hh"
#include "common/file_holder.hh"
namespace nf7::gui {
class FileFactory final {
public:
enum Flag : uint8_t {
kNameInput = 1 << 0,
kNameDupCheck = 1 << 1,
};
using Flags = uint8_t;
using Filter = std::function<bool(const nf7::File::TypeInfo&)>;
FileFactory(nf7::File& owner, Filter&& filter, Flags flags = 0) noexcept :
owner_(&owner), filter_(std::move(filter)), flags_(flags) {
}
FileFactory(const FileFactory&) = delete;
FileFactory(FileFactory&&) = default;
FileFactory& operator=(const FileFactory&) = delete;
FileFactory& operator=(FileFactory&&) = delete;
bool Update() noexcept;
std::unique_ptr<nf7::File> Create(nf7::Env& env) noexcept {
return type_? type_->Create(env): nullptr;
}
const std::string& name() const noexcept { return name_; }
const nf7::File::TypeInfo& type() const noexcept { return *type_; }
private:
nf7::File* const owner_;
const Filter filter_;
const Flags flags_;
std::string name_;
const nf7::File::TypeInfo* type_ = nullptr;
std::string type_filter_;
};
class FileHolderEditor final : public nf7::FileBase::Feature {
public:
enum Type {
kOwn,
kRef,
};
FileHolderEditor(nf7::FileHolder& h, FileFactory::Filter&& filter) noexcept :
holder_(&h), factory_(h.owner(), std::move(filter)) {
}
FileHolderEditor(const FileHolderEditor&) = delete;
FileHolderEditor(FileHolderEditor&&) = default;
FileHolderEditor& operator=(const FileHolderEditor&) = delete;
FileHolderEditor& operator=(FileHolderEditor&&) = delete;
std::string GetDisplayText() const noexcept;
void Button(float w = 0, bool = false) noexcept;
void SmallButton() noexcept { Button(0, true); }
void ButtonWithLabel(const char* id) noexcept;
void Tooltip() noexcept;
void ItemWidget(const char*) noexcept;
void Update() noexcept override;
private:
nf7::FileHolder* const holder_;
bool open_emplace_ = false;
Type type_;
FileFactory factory_;
std::string path_;
void UpdateEmplacePopup(const char*) noexcept;
};
} // namespace nf7::gui

View File

@@ -1,66 +0,0 @@
#pragma once
#include <algorithm>
#include <imgui.h>
#include <imgui_internal.h>
#include <ImNodes.h>
namespace nf7::gui {
inline void NodeSocket() noexcept {
auto win = ImGui::GetCurrentWindow();
const auto em = ImGui::GetFontSize();
const auto lh = std::max(win->DC.CurrLineSize.y, em);
const auto rad = em/2 / ImNodes::CanvasState().Zoom;
const auto sz = ImVec2(rad*2, lh);
const auto pos = ImGui::GetCursorScreenPos() + sz/2;
auto dlist = ImGui::GetWindowDrawList();
dlist->AddCircleFilled(
pos, rad, IM_COL32(100, 100, 100, 100));
dlist->AddCircleFilled(
pos, rad*.8f, IM_COL32(200, 200, 200, 200));
ImGui::Dummy(sz);
}
inline void NodeInputSockets(std::span<const std::string> names) noexcept {
ImGui::BeginGroup();
for (auto& name : names) {
if (ImNodes::BeginInputSlot(name.c_str(), 1)) {
ImGui::AlignTextToFramePadding();
nf7::gui::NodeSocket();
ImGui::SameLine();
ImGui::TextUnformatted(name.c_str());
ImNodes::EndSlot();
}
}
ImGui::EndGroup();
}
inline void NodeOutputSockets(std::span<const std::string> names) noexcept {
float maxw = 0;
for (auto& name : names) {
maxw = std::max(maxw, ImGui::CalcTextSize(name.c_str()).x);
}
ImGui::BeginGroup();
for (auto& name : names) {
const auto w = ImGui::CalcTextSize(name.c_str()).x;
ImGui::SetCursorPosX(ImGui::GetCursorPosX()+maxw-w);
if (ImNodes::BeginOutputSlot(name.c_str(), 1)) {
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted(name.c_str());
ImGui::SameLine();
nf7::gui::NodeSocket();
ImNodes::EndSlot();
}
}
ImGui::EndGroup();
}
} // namespacce nf7::gui

View File

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

View File

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

47
common/gui_window.cc Normal file
View File

@@ -0,0 +1,47 @@
#include "common/gui_window.hh"
#include <imgui.h>
#include <imgui_internal.h>
namespace nf7::gui {
bool Window::MenuItem() noexcept {
return ImGui::MenuItem(title_.c_str(), nullptr, &shown_);
}
void Window::Handle(const nf7::File::Event& e) noexcept {
switch (e.type) {
case nf7::File::Event::kReqFocus:
SetFocus();
return;
default:
return;
}
}
void Window::Update() noexcept {
const auto idstr = id();
auto win = ImGui::FindWindowByName(idstr.c_str());
if (std::exchange(set_focus_, false)) {
shown_ = true;
ImGui::SetNextWindowFocus();
// activate parent windows recursively
auto node = win && win->DockNode? win->DockNode->HostWindow: nullptr;
while (node) {
ImGui::SetWindowFocus(node->Name);
node = node->ParentWindow;
}
}
if (!shown_) return;
onConfig();
if (ImGui::Begin(idstr.c_str(), &shown_)) {
onUpdate();
}
ImGui::End();
}
} // namespace nf7::gui

View File

@@ -1,72 +1,54 @@
#pragma once #pragma once
#include <utility> #include <functional>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <utility>
#include <imgui.h>
#include <yas/serialize.hpp> #include <yas/serialize.hpp>
#include <yas/types/utility/usertype.hpp> #include <yas/types/utility/usertype.hpp>
#include "nf7.hh" #include "nf7.hh"
#include "common/file_base.hh"
namespace nf7::gui { namespace nf7::gui {
class Window { class Window : public nf7::FileBase::Feature {
public: public:
static std::string ConcatId(nf7::File& f, const std::string& name) noexcept { static std::string ConcatId(nf7::File& f, const std::string& name) noexcept {
return f.abspath().Stringify() + " | " + std::string {name}; return f.abspath().Stringify() + " | " + std::string {name};
} }
Window() = delete; Window() = delete;
Window(File& owner, std::string_view title, const gui::Window* src = nullptr) noexcept : Window(nf7::FileBase& owner, std::string_view title) noexcept :
owner_(&owner), title_(title), nf7::FileBase::Feature(owner), owner_(&owner), title_(title), shown_(false) {
shown_(src? src->shown_: false) {
} }
Window(const Window&) = delete; Window(const Window&) = delete;
Window(Window&&) = delete; Window(Window&&) = delete;
Window& operator=(const Window&) = delete; Window& operator=(const Window&) = delete;
Window& operator=(Window&&) = delete; Window& operator=(Window&&) = delete;
bool Begin() noexcept { void serialize(auto& ar) {
if (std::exchange(set_focus_, false)) { ar(shown_);
ImGui::SetNextWindowFocus();
shown_ = true;
}
if (!shown_) return false;
need_end_ = true;
return ImGui::Begin(id().c_str(), &shown_);
}
void End() noexcept {
if (need_end_) {
ImGui::End();
need_end_ = false;
}
} }
void Show() noexcept {
shown_ = true;
}
void SetFocus() noexcept { void SetFocus() noexcept {
shown_ = true; shown_ = true;
set_focus_ = true; set_focus_ = true;
} }
template <typename Ar> bool MenuItem() noexcept;
Ar& serialize(Ar& ar) {
ar(shown_);
return ar;
}
std::string id() const noexcept {
return ConcatId(*owner_, title_);
}
bool shownInCurrentFrame() const noexcept {
return shown_ || set_focus_;
}
std::string id() const noexcept { return ConcatId(*owner_, title_); }
bool shown() const noexcept { return shown_; } bool shown() const noexcept { return shown_; }
bool& shown() noexcept { return shown_; }
std::function<void()> onConfig = [](){};
std::function<void()> onUpdate;
private: private:
File* const owner_; File* const owner_;
@@ -77,6 +59,10 @@ class Window {
// persistent params // persistent params
bool shown_; bool shown_;
void Handle(const nf7::File::Event&) noexcept override;
void Update() noexcept override;
}; };
} // namespace nf7::gui } // namespace nf7::gui

View File

@@ -18,8 +18,8 @@ namespace nf7 {
class LoggerRef final : public nf7::FileBase::Feature { class LoggerRef final : public nf7::FileBase::Feature {
public: public:
LoggerRef(nf7::File& f, nf7::File::Path&& p = {"_logger"}) noexcept : LoggerRef(nf7::FileBase& f, nf7::File::Path&& p = {"_logger"}) noexcept :
file_(&f), path_(std::move(p)) { nf7::FileBase::Feature(f), file_(&f), path_(std::move(p)) {
} }
LoggerRef(const LoggerRef&) = default; LoggerRef(const LoggerRef&) = default;
LoggerRef(LoggerRef&&) = default; LoggerRef(LoggerRef&&) = default;

View File

@@ -7,6 +7,7 @@
#include <cctype> #include <cctype>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <variant>
#include <lua.hpp> #include <lua.hpp>
@@ -44,31 +45,30 @@ void PushValue(lua_State* L, const nf7::Value& v) noexcept {
const auto& v = CheckRef<nf7::Value>(L, 1, "nf7::Value"); const auto& v = CheckRef<nf7::Value>(L, 1, "nf7::Value");
struct Visitor final { struct Visitor final {
lua_State* L; lua_State* L;
const nf7::Value& v; auto operator()(const Value::Pulse&) noexcept { lua_pushnil(L); }
auto operator()(Value::Pulse) noexcept { lua_pushnil(L); } auto operator()(const Value::Boolean& v) noexcept { lua_pushboolean(L, v); }
auto operator()(Value::Boolean) noexcept { lua_pushboolean(L, v.boolean()); } auto operator()(const Value::Integer& v) noexcept { lua_pushinteger(L, v); }
auto operator()(Value::Integer) noexcept { lua_pushinteger(L, v.integer()); } auto operator()(const Value::Scalar& v) noexcept { lua_pushnumber(L, v); }
auto operator()(Value::Scalar) noexcept { lua_pushnumber(L, v.scalar()); } auto operator()(const Value::String& v) noexcept { lua_pushstring(L, v.c_str()); }
auto operator()(Value::String) noexcept { lua_pushstring(L, v.string().c_str()); } auto operator()(const Value::ConstVector& v) noexcept { PushVector(L, v); }
auto operator()(Value::ConstVector) noexcept { PushVector(L, v.vector()); } auto operator()(const Value::DataPtr&) noexcept { lua_pushnil(L); }
auto operator()(Value::DataPtr) noexcept { lua_pushnil(L); }
auto operator()(Value::ConstTuple) noexcept { auto operator()(const Value::ConstTuple& v) noexcept {
const auto& tup = *v.tuple(); const auto& tup = *v;
lua_createtable(L, 0, 0); lua_createtable(L, 0, 0);
size_t arridx = 0; size_t arridx = 0;
for (auto& p : tup) { for (auto& p : tup) {
PushValue(L, p.second); PushValue(L, p.second);
if (p.first.empty()) { if (p.first.empty()) {
lua_rawseti(L, -2, static_cast<int>(arridx++)); lua_rawseti(L, -2, static_cast<int>(++arridx));
} else { } else {
lua_setfield(L, -2, p.first.c_str()); lua_setfield(L, -2, p.first.c_str());
} }
} }
} }
}; };
v.Visit(Visitor{.L = L, .v = v}); std::visit(Visitor {.L = L}, v.value());
return 1; return 1;
}); });
lua_setfield(L, -2, "value"); lua_setfield(L, -2, "value");
@@ -390,7 +390,7 @@ void PushNodeRootLambda(
}).template Catch<nf7::Exception>(nullptr, [L, th](nf7::Exception&) { }).template Catch<nf7::Exception>(nullptr, [L, th](nf7::Exception&) {
th->ExecResume(L); th->ExecResume(L);
}); });
return th->Yield(L); return th->Yield(L, la);
} }
}); });
lua_setfield(L, -2, "recv"); lua_setfield(L, -2, "recv");
@@ -426,6 +426,12 @@ void PushImmEnv(lua_State* L) noexcept {
lua_setmetatable(L, -2); lua_setmetatable(L, -2);
} }
} }
void PushImmTable(lua_State* L) noexcept {
if (luaL_newmetatable(L, "nf7::luajit::ImmTable")) {
lua_pushcfunction(L, [](auto L) { return luaL_error(L, "table is immutable"); });
lua_setfield(L, -2, "__newindex");
}
}
template <typename T> template <typename T>

View File

@@ -143,5 +143,6 @@ int PushAll(lua_State* L, T v, Args&&... args) noexcept {
// ---- global table // ---- global table
void PushGlobalTable(lua_State*) noexcept; void PushGlobalTable(lua_State*) noexcept;
void PushImmEnv(lua_State*) noexcept; void PushImmEnv(lua_State*) noexcept;
void PushImmTable(lua_State*) noexcept;
} // namespace nf7 } // namespace nf7

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <chrono>
#include <filesystem> #include <filesystem>
#include <functional> #include <functional>
#include <memory> #include <memory>
@@ -27,15 +28,14 @@ class NFileImporter :
} }
nf7::Future<std::shared_ptr<luajit::Ref>> Import( nf7::Future<std::shared_ptr<luajit::Ref>> Import(
const luajit::Thread& th, std::string_view name) noexcept { const std::shared_ptr<luajit::Thread>& th, std::string_view name) noexcept {
auto self = shared_from_this(); auto self = shared_from_this();
const auto path = base_ / std::string {name}; const auto path = base_ / std::string {name};
auto ljq = th.ljq(); auto ljq = th->ljq();
auto ctx = std::make_shared< auto ctx = std::make_shared<
nf7::GenericContext>(th.env(), th.ctx()->initiator(), nf7::GenericContext>(th->env(), th->initiator(), "imported LuaJIT script", th);
"LuaJIT imported script (nfile)", th.ctx());
nf7::Future<std::shared_ptr<luajit::Ref>>::Promise pro {ctx}; nf7::Future<std::shared_ptr<luajit::Ref>>::Promise pro {ctx};
// create new thread // create new thread
@@ -50,7 +50,7 @@ class NFileImporter :
}); });
auto th_sub = std::make_shared< auto th_sub = std::make_shared<
nf7::luajit::Thread>(ctx, ljq, std::move(handler)); nf7::luajit::Thread>(ctx, ljq, std::move(handler));
th_sub->Install(th); th_sub->Install(*th);
// install new importer for sub thread // install new importer for sub thread
auto dir = path; auto dir = path;
@@ -77,7 +77,7 @@ class NFileImporter :
std::filesystem::file_time_type GetLatestMod() const noexcept { std::filesystem::file_time_type GetLatestMod() const noexcept {
std::unique_lock<std::mutex> _ {mtx_}; std::unique_lock<std::mutex> _ {mtx_};
std::filesystem::file_time_type ret = {}; auto ret = std::filesystem::file_time_type::min();
for (const auto& p : imports_) { for (const auto& p : imports_) {
try { try {
ret = std::max(ret, std::filesystem::last_write_time(p)); ret = std::max(ret, std::filesystem::last_write_time(p));

View File

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

View File

@@ -5,14 +5,15 @@
#include <tuple> #include <tuple>
#include <unordered_set> #include <unordered_set>
#include <tracy/Tracy.hpp>
#include "common/node.hh" #include "common/node.hh"
#include "common/node_root_lambda.hh" #include "common/node_root_lambda.hh"
namespace nf7::luajit { namespace nf7::luajit {
constexpr size_t kInstructionLimit = 10000000; constexpr size_t kInstructionLimit = 100000;
constexpr size_t kBufferSizeMax = 64 * 1024 * 1024;
// Pushes a metatable for Thread object, available on global table as 'nf7'. // Pushes a metatable for Thread object, available on global table as 'nf7'.
@@ -23,7 +24,7 @@ lua_State* Thread::Init(lua_State* L) noexcept {
assert(state_ == kInitial); assert(state_ == kInitial);
th_ = lua_newthread(L); th_ = lua_newthread(L);
th_ref_.emplace(ctx_, ljq_, L); th_ref_.emplace(shared_from_this(), ljq_, L);
state_ = kPaused; state_ = kPaused;
return th_; return th_;
@@ -35,11 +36,6 @@ void Thread::Resume(lua_State* L, int narg) noexcept {
assert(L == th_); assert(L == th_);
assert(state_ == kPaused); assert(state_ == kPaused);
static const auto kHook = [](auto L, auto) {
luaL_error(L, "reached instruction limit (<=1e7)");
};
lua_sethook(L, kHook, LUA_MASKCOUNT, kInstructionLimit);
// set global table // set global table
PushGlobalTable(L); PushGlobalTable(L);
NewUserData<std::weak_ptr<Thread>>(L, weak_from_this()); NewUserData<std::weak_ptr<Thread>>(L, weak_from_this());
@@ -48,30 +44,50 @@ void Thread::Resume(lua_State* L, int narg) noexcept {
lua_setfield(L, -2, "nf7"); lua_setfield(L, -2, "nf7");
lua_pop(L, 1); lua_pop(L, 1);
state_ = kRunning; state_ = kRunning;
k.unlock();
active_ = true; active_ = true;
const auto ret = lua_resume(L, narg); yield_ctx_.reset();
active_ = false;
k.unlock();
int ret;
{
ZoneScopedN("lua_resume");
ret = lua_resume(L, narg);
}
k.lock(); k.lock();
active_ = false;
if (state_ == kAborted) return; if (state_ == kAborted) return;
switch (ret) { switch (ret) {
case 0: case 0:
state_ = kFinished; th_ref_ = std::nullopt;
state_ = kFinished;
break; break;
case LUA_YIELD: case LUA_YIELD:
state_ = kPaused; state_ = kPaused;
break; break;
default: default:
state_ = kAborted; th_ref_ = std::nullopt;
state_ = kAborted;
} }
if (!std::exchange(skip_handle_, false)) { if (!std::exchange(skip_handler_, false)) {
k.unlock();
handler_(*this, L); handler_(*this, L);
} }
} }
void Thread::Abort() noexcept { void Thread::Abort() noexcept {
std::unique_lock<std::mutex> k(mtx_); std::unique_lock<std::mutex> k(mtx_);
state_ = kAborted; state_ = kAborted;
th_ref_ = std::nullopt;
auto wctx = std::move(yield_ctx_);
yield_ctx_.reset();
k.unlock();
if (auto ctx = wctx.lock()) {
if (ctx.get() != this) {
ctx->Abort();
}
}
} }
@@ -136,7 +152,7 @@ static void PushMeta(lua_State* L) noexcept {
return luaL_error(L, "import is not available in the current thread"); return luaL_error(L, "import is not available in the current thread");
} }
if (const auto name = lua_tostring(L, 2)) { if (const auto name = lua_tostring(L, 2)) {
auto fu = im->Import(*th, name); auto fu = im->Import(th, name);
fu.ThenIf([L, th](auto& obj) { fu.ThenIf([L, th](auto& obj) {
th->ExecResume(L, obj); th->ExecResume(L, obj);
}).template Catch<nf7::Exception>([L, th](auto&) { }).template Catch<nf7::Exception>([L, th](auto&) {
@@ -155,10 +171,10 @@ static void PushMeta(lua_State* L) noexcept {
// nf7:resolve(path) // nf7:resolve(path)
lua_pushcfunction(L, [](auto L) { lua_pushcfunction(L, [](auto L) {
auto th = Thread::GetPtr(L, 1); auto th = Thread::GetPtr(L, 1);
auto base = th->ctx()->initiator(); auto base = th->initiator();
std::string path = luaL_checkstring(L, 2); std::string path = luaL_checkstring(L, 2);
th->env().ExecSub(th->ctx(), [th, L, base, path = std::move(path)]() { th->env().ExecSub(th, [th, L, base, path = std::move(path)]() {
try { try {
th->ExecResume(L, th->env().GetFileOrThrow(base).ResolveOrThrow(path).id()); th->ExecResume(L, th->env().GetFileOrThrow(base).ResolveOrThrow(path).id());
} catch (nf7::File::NotFoundException&) { } catch (nf7::File::NotFoundException&) {
@@ -174,7 +190,7 @@ static void PushMeta(lua_State* L) noexcept {
auto th = Thread::GetPtr(L, 1); auto th = Thread::GetPtr(L, 1);
lua_pushvalue(L, 2); lua_pushvalue(L, 2);
auto ref = std::make_shared<nf7::luajit::Ref>(th->ctx(), th->ljq(), L); auto ref = std::make_shared<nf7::luajit::Ref>(th, th->ljq(), L);
PushValue(L, nf7::Value {std::move(ref)}); PushValue(L, nf7::Value {std::move(ref)});
return 1; return 1;
}); });
@@ -186,13 +202,13 @@ static void PushMeta(lua_State* L) noexcept {
const auto id = luaL_checkinteger(L, 2); const auto id = luaL_checkinteger(L, 2);
std::string iface = luaL_checkstring(L, 3); std::string iface = luaL_checkstring(L, 3);
th->env().ExecSub(th->ctx(), [th, L, id, iface = std::move(iface)]() { th->env().ExecSub(th, [th, L, id, iface = std::move(iface)]() {
try { try {
auto& f = th->env().GetFileOrThrow(static_cast<nf7::File::Id>(id)); auto& f = th->env().GetFileOrThrow(static_cast<nf7::File::Id>(id));
if (iface == "node") { if (iface == "node") {
th->ExecResume( th->ExecResume(
L, nf7::NodeRootLambda::Create( L, nf7::NodeRootLambda::Create(
th->ctx(), f.template interfaceOrThrow<nf7::Node>())); th, f.template interfaceOrThrow<nf7::Node>()));
} else { } else {
throw nf7::Exception {"unknown interface: "+iface}; throw nf7::Exception {"unknown interface: "+iface};
} }
@@ -211,7 +227,7 @@ static void PushMeta(lua_State* L) noexcept {
const auto time = nf7::Env::Clock::now() + const auto time = nf7::Env::Clock::now() +
std::chrono::milliseconds(static_cast<uint64_t>(sec*1000)); std::chrono::milliseconds(static_cast<uint64_t>(sec*1000));
th->ljq()->Push(th->ctx(), [th, L](auto) { th->ExecResume(L); }, time); th->ljq()->Push(th, [th, L](auto) { th->ExecResume(L); }, time);
return th->Yield(L); return th->Yield(L);
}); });

View File

@@ -14,6 +14,7 @@
#include "nf7.hh" #include "nf7.hh"
#include "common/context_owner.hh"
#include "common/future.hh" #include "common/future.hh"
#include "common/logger_ref.hh" #include "common/logger_ref.hh"
#include "common/luajit.hh" #include "common/luajit.hh"
@@ -23,7 +24,8 @@
namespace nf7::luajit { namespace nf7::luajit {
class Thread final : public std::enable_shared_from_this<Thread> { class Thread final : public nf7::Context,
public std::enable_shared_from_this<Thread> {
public: public:
static constexpr const char* kTypeName = "nf7::luajit::Thread"; static constexpr const char* kTypeName = "nf7::luajit::Thread";
@@ -60,10 +62,12 @@ class Thread final : public std::enable_shared_from_this<Thread> {
} }
Thread() = delete; Thread() = delete;
Thread(const std::shared_ptr<nf7::Context>& ctx, Thread(const std::shared_ptr<nf7::Context>& parent,
const std::shared_ptr<nf7::luajit::Queue>& ljq, const std::shared_ptr<nf7::luajit::Queue>& ljq,
Handler&& handler) noexcept : Handler&& handler) noexcept :
ctx_(ctx), ljq_(ljq), handler_(std::move(handler)) { nf7::Context(parent->env(), parent->initiator(), parent),
ljq_(ljq),
handler_(std::move(handler)) {
} }
Thread(const Thread&) = delete; Thread(const Thread&) = delete;
Thread(Thread&&) = delete; Thread(Thread&&) = delete;
@@ -93,8 +97,9 @@ class Thread final : public std::enable_shared_from_this<Thread> {
// must be called on luajit thread // must be called on luajit thread
// handler_ won't be called on this yielding // handler_ won't be called on this yielding
int Yield(lua_State* L) { int Yield(lua_State* L, const std::shared_ptr<nf7::Context>& ctx = nullptr) {
skip_handle_ = true; yield_ctx_ = ctx;
skip_handler_ = true;
return lua_yield(L, 0); return lua_yield(L, 0);
} }
@@ -106,20 +111,23 @@ class Thread final : public std::enable_shared_from_this<Thread> {
} }
// thread-safe // thread-safe
void Abort() noexcept; void Abort() noexcept override;
// queue a task that exec Resume() // queue a task that exec Resume()
// thread-safe // thread-safe
template <typename... Args> template <typename... Args>
void ExecResume(lua_State* L, Args&&... args) noexcept { void ExecResume(lua_State* L, Args&&... args) noexcept {
auto self = shared_from_this(); auto self = shared_from_this();
ljq_->Push(ctx_, [this, L, self, args...](auto) mutable { ljq_->Push(self, [this, L, args...](auto) mutable {
Resume(L, luajit::PushAll(L, std::forward<Args>(args)...)); Resume(L, luajit::PushAll(L, std::forward<Args>(args)...));
}); });
} }
nf7::Env& env() const noexcept { return ctx_->env(); }
const std::shared_ptr<nf7::Context>& ctx() const noexcept { return ctx_; } std::string GetDescription() const noexcept override {
return "LuaJIT thread";
}
const std::shared_ptr<nf7::luajit::Queue>& ljq() const noexcept { return ljq_; } const std::shared_ptr<nf7::luajit::Queue>& ljq() const noexcept { return ljq_; }
const std::shared_ptr<nf7::LoggerRef>& logger() const noexcept { return logger_; } const std::shared_ptr<nf7::LoggerRef>& logger() const noexcept { return logger_; }
const std::shared_ptr<Importer>& importer() const noexcept { return importer_; } const std::shared_ptr<Importer>& importer() const noexcept { return importer_; }
@@ -129,7 +137,6 @@ class Thread final : public std::enable_shared_from_this<Thread> {
// initialized by constructor // initialized by constructor
std::mutex mtx_; std::mutex mtx_;
std::shared_ptr<nf7::Context> ctx_;
std::shared_ptr<nf7::luajit::Queue> ljq_; std::shared_ptr<nf7::luajit::Queue> ljq_;
Handler handler_; Handler handler_;
@@ -147,8 +154,9 @@ class Thread final : public std::enable_shared_from_this<Thread> {
// mutable params // mutable params
bool active_ = false; // true while executing lua_resume bool active_ = false; // true while executing lua_resume
bool skip_handle_ = false; // handler_ won't be called on next yield bool skip_handler_ = false;
std::weak_ptr<nf7::Context> yield_ctx_;
}; };
@@ -163,7 +171,7 @@ class Thread::Importer {
// be called on luajit thread // be called on luajit thread
virtual nf7::Future<std::shared_ptr<luajit::Ref>> Import( virtual nf7::Future<std::shared_ptr<luajit::Ref>> Import(
const luajit::Thread&, std::string_view) noexcept = 0; const std::shared_ptr<luajit::Thread>&, std::string_view) noexcept = 0;
}; };

View File

@@ -6,12 +6,15 @@
#include "nf7.hh" #include "nf7.hh"
#include "common/history.hh"
namespace nf7 { namespace nf7 {
class Memento : public File::Interface { class Memento : public File::Interface {
public: public:
class Tag; class Tag;
class RestoreCommand;
class CorruptException; class CorruptException;
Memento() = default; Memento() = default;
@@ -43,6 +46,33 @@ class Memento::Tag {
Id id_; Id id_;
}; };
class Memento::RestoreCommand final : public nf7::History::Command {
public:
RestoreCommand() = delete;
RestoreCommand(Memento& mem,
const std::shared_ptr<Tag>& prev,
const std::shared_ptr<Tag>& next) noexcept :
mem_(mem), prev_(prev), next_(next) {
}
RestoreCommand(const RestoreCommand&) = delete;
RestoreCommand(RestoreCommand&&) = delete;
RestoreCommand& operator=(const RestoreCommand&) = delete;
RestoreCommand& operator=(RestoreCommand&&) = delete;
void Apply() override { Exec(); }
void Revert() override { Exec(); }
private:
Memento& mem_;
std::shared_ptr<Tag> prev_;
std::shared_ptr<Tag> next_;
void Exec() noexcept {
mem_.Restore(next_);
std::swap(prev_, next_);
}
};
class Memento::CorruptException : public Exception { class Memento::CorruptException : public Exception {
public: public:
using Exception::Exception; using Exception::Exception;

View File

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

View File

@@ -42,6 +42,13 @@ class Mutex final {
return k; return k;
} }
const char* status() const noexcept {
return sync_.expired()? "free": ex_? "exlocked": "locked";
}
size_t pendings() const noexcept {
return pends_.size();
}
std::function<void()> onLock = [](){}; std::function<void()> onLock = [](){};
std::function<void()> onUnlock = [](){}; std::function<void()> onUnlock = [](){};

View File

@@ -12,7 +12,9 @@ namespace nf7 {
class NFileWatcher final : public nf7::FileBase::Feature { class NFileWatcher final : public nf7::FileBase::Feature {
public: public:
NFileWatcher() = default; NFileWatcher() = delete;
NFileWatcher(nf7::FileBase& f) noexcept : nf7::FileBase::Feature(f) {
}
NFileWatcher(const NFileWatcher&) = delete; NFileWatcher(const NFileWatcher&) = delete;
NFileWatcher(NFileWatcher&&) = delete; NFileWatcher(NFileWatcher&&) = delete;
NFileWatcher& operator=(const NFileWatcher&) = delete; NFileWatcher& operator=(const NFileWatcher&) = delete;

View File

@@ -22,13 +22,41 @@ class Node : public File::Interface {
class Lambda; class Lambda;
enum Flag : uint8_t { enum Flag : uint8_t {
kNone = 0, kNone = 0,
kCustomNode = 1 << 0, kCustomNode = 1 << 0,
kMenu = 1 << 1, kMenu = 1 << 1,
kMenu_DirItem = 1 << 2, // use DirItem::UpdateMenu() method instead of Node's
}; };
using Flags = uint8_t; using Flags = uint8_t;
struct Meta final {
public:
Meta() = default;
Meta(std::vector<std::string>&& i, std::vector<std::string>&& o) noexcept :
inputs(std::move(i)), outputs(std::move(o)) {
}
Meta(const std::vector<std::string>& i, const std::vector<std::string>& o) noexcept :
inputs(i), outputs(o) {
}
Meta(const Meta&) = default;
Meta(Meta&&) = default;
Meta& operator=(const Meta&) = default;
Meta& operator=(Meta&&) = default;
std::vector<std::string> inputs, outputs;
};
static void ValidateSockets(std::span<const std::string> v) {
for (auto itr = v.begin(); itr < v.end(); ++itr) {
if (v.end() != std::find(itr+1, v.end(), *itr)) {
throw nf7::Exception {"name duplication: "+*itr};
}
}
for (auto& s : v) {
nf7::File::Path::ValidateTerm(s);
}
}
Node(Flags f) noexcept : flags_(f) { } Node(Flags f) noexcept : flags_(f) { }
Node(const Node&) = default; Node(const Node&) = default;
Node(Node&&) = default; Node(Node&&) = default;
@@ -40,13 +68,12 @@ class Node : public File::Interface {
virtual void UpdateNode(Editor&) noexcept { } virtual void UpdateNode(Editor&) noexcept { }
virtual void UpdateMenu(Editor&) noexcept { } virtual void UpdateMenu(Editor&) noexcept { }
// The returned span is alive until next operation to the file. // don't call too often because causes heap allocation
virtual std::span<const std::string> GetInputs() const noexcept = 0; virtual Meta GetMeta() const noexcept = 0;
virtual std::span<const std::string> GetOutputs() const noexcept = 0;
Flags flags() const noexcept { return flags_; } Flags flags() const noexcept { return flags_; }
protected: private:
Flags flags_; Flags flags_;
}; };

View File

@@ -6,6 +6,7 @@
#include <span> #include <span>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <unordered_set>
#include <vector> #include <vector>
#include <yas/serialize.hpp> #include <yas/serialize.hpp>
@@ -65,6 +66,8 @@ class NodeLinkStore {
inline std::unique_ptr<nf7::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; uint64_t id, std::span<const std::string> in, std::span<const std::string> out) noexcept;
inline std::unique_ptr<nf7::History::Command> CreateCommandToRemoveExpired(
const std::unordered_set<uint64_t>& ids) noexcept;
std::span<const Link> items() const noexcept { return links_; } std::span<const Link> items() const noexcept { return links_; }
@@ -111,7 +114,19 @@ std::unique_ptr<nf7::History::Command> NodeLinkStore::CreateCommandToRemoveExpir
const bool rm = const bool rm =
(lk.src_id == id && std::find(out.begin(), out.end(), lk.src_name) == out.end()) || (lk.src_id == id && std::find(out.begin(), out.end(), lk.src_name) == out.end()) ||
(lk.dst_id == id && std::find(in .begin(), in .end(), lk.dst_name) == in .end()); (lk.dst_id == id && std::find(in .begin(), in .end(), lk.dst_name) == in .end());
if (rm) cmds.push_back(SwapCommand::CreateToRemove(*this, Link(lk))); if (rm) cmds.push_back(SwapCommand::CreateToRemove(*this, Link {lk}));
}
if (cmds.empty()) return nullptr;
return std::make_unique<nf7::AggregateCommand>(std::move(cmds));
}
std::unique_ptr<nf7::History::Command> NodeLinkStore::CreateCommandToRemoveExpired(
const std::unordered_set<uint64_t>& ids) noexcept {
std::vector<std::unique_ptr<nf7::History::Command>> cmds;
for (const auto& lk : links_) {
if (!ids.contains(lk.src_id) || !ids.contains(lk.dst_id)) {
cmds.push_back(SwapCommand::CreateToRemove(*this, Link {lk}));
}
} }
if (cmds.empty()) return nullptr; if (cmds.empty()) return nullptr;
return std::make_unique<nf7::AggregateCommand>(std::move(cmds)); return std::make_unique<nf7::AggregateCommand>(std::move(cmds));

View File

@@ -30,16 +30,21 @@ class NodeRootLambda : public nf7::Node::Lambda,
ret->target_ = n.CreateLambda(ret); ret->target_ = n.CreateLambda(ret);
return ret; return ret;
} }
using nf7::Node::Lambda::Lambda; using nf7::Node::Lambda::Lambda;
~NodeRootLambda() noexcept {
Abort();
}
void Handle(const nf7::Node::Lambda::Msg& in) noexcept override { void Handle(const nf7::Node::Lambda::Msg& in) noexcept override {
std::unique_lock<std::mutex> lk {mtx_}; std::unique_lock<std::mutex> lk {mtx_};
if (names_.contains(in.name)) { if (names_.contains(in.name)) {
names_.clear(); names_.clear();
auto pro = *std::exchange(pro_, std::nullopt); if (auto pro = std::exchange(pro_, std::nullopt)) {
lk.unlock(); lk.unlock();
pro.Return({in.name, in.value}); pro->Return({in.name, in.value});
}
} else { } else {
q_.push_back({in.name, in.value}); q_.push_back({in.name, in.value});
} }
@@ -71,6 +76,11 @@ class NodeRootLambda : public nf7::Node::Lambda,
return pro_->future(); return pro_->future();
} }
void Abort() noexcept override {
target_->Abort();
pro_ = std::nullopt;
}
private: private:
std::mutex mtx_; std::mutex mtx_;
std::shared_ptr<nf7::Node::Lambda> target_; std::shared_ptr<nf7::Node::Lambda> target_;

59
common/pure_node_file.hh Normal file
View File

@@ -0,0 +1,59 @@
#pragma once
#include <memory>
#include <typeinfo>
#include "nf7.hh"
#include "common/file_base.hh"
#include "common/logger_ref.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
namespace nf7 {
template <typename T>
concept PureNodeFile_LoggerRef =
requires (T& t, const std::shared_ptr<nf7::LoggerRef>& f) { t.log_ = f; };
template <typename T>
class PureNodeFile final : public nf7::FileBase, public nf7::Node {
public:
PureNodeFile(nf7::Env& env) noexcept :
nf7::FileBase(T::kType, env),
nf7::Node(nf7::Node::kNone) {
if constexpr (PureNodeFile_LoggerRef<T>) {
log_ = std::make_shared<nf7::LoggerRef>(*this);
}
}
PureNodeFile(nf7::Deserializer& ar) : PureNodeFile(ar.env()) {
}
void Serialize(nf7::Serializer&) const noexcept override {}
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<nf7::PureNodeFile<T>>(env);
}
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept override {
auto la = std::make_shared<T>(*this, parent);
if constexpr (PureNodeFile_LoggerRef<T>) {
la->log_ = log_;
}
return la;
}
nf7::Node::Meta GetMeta() const noexcept override {
return T::kMeta;
}
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector<nf7::Node>(t).Select(this);
}
private:
std::shared_ptr<nf7::LoggerRef> log_;
};
} // namespace nf7

View File

@@ -27,6 +27,11 @@ class SquashedHistory : public nf7::GenericHistory {
if (staged_.size() == 0) { if (staged_.size() == 0) {
return false; return false;
} }
if (++tick_ <= 2) {
return false;
}
tick_ = 0;
nf7::GenericHistory::Add( nf7::GenericHistory::Add(
std::make_unique<nf7::AggregateCommand>(std::move(staged_))); std::make_unique<nf7::AggregateCommand>(std::move(staged_)));
return true; return true;
@@ -48,6 +53,8 @@ class SquashedHistory : public nf7::GenericHistory {
private: private:
std::vector<std::unique_ptr<Command>> staged_; std::vector<std::unique_ptr<Command>> staged_;
uint8_t tick_ = 0;
}; };
} // namespace nf7 } // namespace nf7

52
common/stopwatch.hh Normal file
View File

@@ -0,0 +1,52 @@
#pragma once
#include <chrono>
#include <iostream>
#include "nf7.hh"
namespace nf7 {
class Stopwatch final {
public:
struct Benchmark;
static nf7::Env::Time now() noexcept { return nf7::Env::Clock::now(); }
Stopwatch() noexcept : begin_(now()) {
}
Stopwatch(const Stopwatch&) = default;
Stopwatch(Stopwatch&&) = default;
Stopwatch& operator=(const Stopwatch&) = default;
Stopwatch& operator=(Stopwatch&&) = default;
auto dur() const noexcept {
return now() - begin_;
}
private:
nf7::Env::Time begin_;
};
inline std::ostream& operator << (std::ostream& out, const Stopwatch& sw) {
return out << std::chrono::duration_cast<std::chrono::microseconds>(sw.dur()).count() << " usecs";
}
struct Stopwatch::Benchmark final {
public:
Benchmark(const char* name) noexcept : name_(name) {
}
~Benchmark() noexcept {
std::cout << name_ << ": " << sw_ << std::endl;
}
Benchmark(const Benchmark&) = delete;
Benchmark(Benchmark&&) = delete;
Benchmark& operator=(const Benchmark&) = delete;
Benchmark& operator=(Benchmark&&) = delete;
private:
const char* name_;
Stopwatch sw_;
};
} // namespace nf7

View File

@@ -1,29 +1,36 @@
#pragma once #pragma once
#include <atomic> #include <atomic>
#include <chrono>
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <type_traits>
#include <utility> #include <utility>
#include <tracy/Tracy.hpp>
#include "nf7.hh" #include "nf7.hh"
#include "common/stopwatch.hh"
#include "common/timed_queue.hh" #include "common/timed_queue.hh"
namespace nf7 { namespace nf7 {
// a thread emulation using nf7::Env::ExecAsync // a thread emulation by tasks
template <typename Runner, typename Task> template <typename Runner, typename Task>
class Thread final : public nf7::Context, class Thread final : public nf7::Context,
public std::enable_shared_from_this<Thread<Runner, Task>> { public std::enable_shared_from_this<Thread<Runner, Task>> {
public: public:
static constexpr auto kTaskDur = std::chrono::milliseconds {1};
Thread() = delete; Thread() = delete;
Thread(nf7::File& f, Runner&& runner) noexcept : Thread(nf7::File& f, Runner&& runner, nf7::Env::Executor exec = nf7::Env::kAsync) noexcept :
Thread(f.env(), f.id(), std::move(runner)) { Thread(f.env(), f.id(), std::move(runner), exec) {
} }
Thread(nf7::Env& env, nf7::File::Id id, Runner&& runner) noexcept : Thread(nf7::Env& env, nf7::File::Id id, Runner&& runner, nf7::Env::Executor exec = nf7::Env::kAsync) noexcept :
nf7::Context(env, id), env_(&env), runner_(std::move(runner)) { nf7::Context(env, id), runner_(std::move(runner)), exec_(exec) {
} }
Thread(const Thread&) = delete; Thread(const Thread&) = delete;
Thread(Thread&&) = delete; Thread(Thread&&) = delete;
@@ -32,7 +39,11 @@ class Thread final : public nf7::Context,
void Push(const std::shared_ptr<nf7::Context>& ctx, Task&& t, nf7::Env::Time time = {}) noexcept { void Push(const std::shared_ptr<nf7::Context>& ctx, Task&& t, nf7::Env::Time time = {}) noexcept {
q_.Push(time, {ctx, std::move(t)}); q_.Push(time, {ctx, std::move(t)});
HandleNext(true /* = first */); ExecNext(true /* = entry */);
}
void SetExecutor(nf7::Env::Executor exec) noexcept {
exec_ = exec;
} }
size_t tasksDone() const noexcept { return tasks_done_; } size_t tasksDone() const noexcept { return tasks_done_; }
@@ -40,36 +51,49 @@ class Thread final : public nf7::Context,
private: private:
using Pair = std::pair<std::shared_ptr<nf7::Context>, Task>; using Pair = std::pair<std::shared_ptr<nf7::Context>, Task>;
Env* const env_; Runner runner_;
Runner runner_; std::atomic<nf7::Env::Executor> exec_;
nf7::TimedQueue<Pair> q_; nf7::TimedQueue<Pair> q_;
std::mutex mtx_; std::mutex mtx_;
bool working_ = false; bool working_ = false;
nf7::Env::Time scheduled_;
std::atomic<size_t> tasks_done_ = 0; std::atomic<size_t> tasks_done_ = 0;
using std::enable_shared_from_this<Thread<Runner, Task>>::shared_from_this; using std::enable_shared_from_this<Thread<Runner, Task>>::shared_from_this;
void HandleNext(bool first = false) noexcept { void ExecNext(bool entry = false) noexcept {
std::unique_lock<std::mutex> k(mtx_); {
if (std::exchange(working_, true) && first) return; std::unique_lock<std::mutex> k {mtx_};
if (std::exchange(working_, true)) return;
}
auto self = shared_from_this(); auto self = shared_from_this();
if (auto p = q_.Pop()) { if (!entry) {
k.unlock(); ZoneScopedN("thread task execution");
for (nf7::Stopwatch sw; sw.dur() < kTaskDur; ++tasks_done_) {
auto t = q_.Pop();
if (t) {
runner_(std::move(t->second));
} else {
if constexpr (std::is_invocable_v<Runner>) {
runner_(); // idle task
}
break;
}
}
}
env_->ExecAsync(p->first, [this, self, t = std::move(p->second)]() mutable { {
runner_(std::move(t)); std::unique_lock<std::mutex> k {mtx_};
++tasks_done_; if (auto time = q_.next()) {
HandleNext(); if (time <= nf7::Env::Clock::now() || time != scheduled_) {
}); scheduled_ = *time;
} else if (auto time = q_.next()) { env().Exec(exec_, self, [this]() mutable { ExecNext(); }, *time);
working_ = false; }
env_->ExecAsync( }
shared_from_this(), [this, self]() mutable { HandleNext(); }, *time);
} else {
working_ = false; working_ = false;
} }
} }

View File

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

View File

@@ -96,10 +96,6 @@ class Value {
Value(DataPtr&& v) noexcept : value_(std::move(v)) { } Value(DataPtr&& v) noexcept : value_(std::move(v)) { }
Value& operator=(DataPtr&& v) noexcept { value_ = std::move(v); return *this; } Value& operator=(DataPtr&& v) noexcept { value_ = std::move(v); return *this; }
auto Visit(auto visitor) const noexcept {
return std::visit(visitor, value_);
}
bool isPulse() const noexcept { return std::holds_alternative<Pulse>(value_); } bool isPulse() const noexcept { return std::holds_alternative<Pulse>(value_); }
bool isBoolean() const noexcept { return std::holds_alternative<Boolean>(value_); } bool isBoolean() const noexcept { return std::holds_alternative<Boolean>(value_); }
bool isInteger() const noexcept { return std::holds_alternative<Integer>(value_); } bool isInteger() const noexcept { return std::holds_alternative<Integer>(value_); }
@@ -117,6 +113,7 @@ class Value {
const ConstVector& vector() const { return get<ConstVector>(); } const ConstVector& vector() const { return get<ConstVector>(); }
const ConstTuple& tuple() const { return get<ConstTuple>(); } const ConstTuple& tuple() const { return get<ConstTuple>(); }
const DataPtr& data() const { return get<DataPtr>(); } const DataPtr& data() const { return get<DataPtr>(); }
const auto& value() const noexcept { return value_; }
// direct reference accessor // direct reference accessor
Integer& integer() { return get<Integer>(); } Integer& integer() { return get<Integer>(); }
@@ -199,7 +196,7 @@ class Value {
auto operator()(ConstTuple) noexcept { return "tuple"; } auto operator()(ConstTuple) noexcept { return "tuple"; }
auto operator()(DataPtr) noexcept { return "data"; } auto operator()(DataPtr) noexcept { return "data"; }
}; };
return Visit(Visitor{}); return std::visit(Visitor {}, value_);
} }
template <typename Ar> template <typename Ar>
@@ -225,15 +222,6 @@ class Value {
st << "expected " << typeid(T).name() << " but it's " << typeName(); st << "expected " << typeid(T).name() << " but it's " << typeName();
throw IncompatibleException(st.str()); throw IncompatibleException(st.str());
} }
template <typename T>
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 std::const_pointer_cast<typename std::remove_const<typename T::element_type>::type>(v);
} else {
return std::make_shared<typename std::remove_const<typename T::element_type>::type>(*v);
}
}
template <typename R, typename N> template <typename R, typename N>
static R SafeCast(N in) { static R SafeCast(N in) {

26
common/yaml_nf7.hh Normal file
View File

@@ -0,0 +1,26 @@
#pragma once
#include <string>
#include <yaml-cpp/yaml.h>
#include "nf7.hh"
namespace YAML {
template <>
struct convert<nf7::File::Path> {
static bool decode(const Node& node, nf7::File::Path& p)
try {
p = nf7::File::Path::Parse(node.as<std::string>());
return true;
} catch (nf7::Exception&) {
return false;
}
};
inline Emitter& operator<<(Emitter& st, const nf7::File::Path& p) {
return st << p.Stringify();
}
} // namespace nf7

View File

@@ -30,11 +30,10 @@ struct serializer<
static Archive& load(Archive& ar, std::unique_ptr<nf7::File>& f) { static Archive& load(Archive& ar, std::unique_ptr<nf7::File>& f) {
std::string name; std::string name;
ar(name); ar(name);
auto& type = nf7::File::registry(name);
try { try {
typename Archive::ChunkGuard guard {ar}; typename Archive::ChunkGuard guard {ar};
f = type.Deserialize(ar); f = nf7::File::registry(name).Deserialize(ar);
guard.ValidateEnd(); guard.ValidateEnd();
} catch (...) { } catch (...) {
f = nullptr; f = nullptr;
@@ -44,29 +43,6 @@ struct serializer<
} }
}; };
template <size_t F>
struct serializer<
type_prop::not_a_fundamental,
ser_case::use_internal_serializer,
F,
std::shared_ptr<nf7::File>> {
public:
template <typename Archive>
static Archive& save(Archive& ar, const std::shared_ptr<nf7::File>& f) {
std::unique_ptr<nf7::File> uf(f.get());
ar(uf);
uf.release();
return ar;
}
template <typename Archive>
static Archive& load(Archive& ar, std::shared_ptr<nf7::File>& f) {
std::unique_ptr<nf7::File> uf;
ar(uf);
f = std::move(uf);
return ar;
}
};
template <size_t F> template <size_t F>
struct serializer< struct serializer<
type_prop::not_a_fundamental, type_prop::not_a_fundamental,

View File

@@ -2,15 +2,18 @@
#include <atomic> #include <atomic>
#include <cinttypes> #include <cinttypes>
#include <memory> #include <memory>
#include <typeinfo>
#include <imgui.h> #include <imgui.h>
#include <miniaudio.h> #include <miniaudio.h>
#include <tracy/Tracy.hpp>
#include "nf7.hh" #include "nf7.hh"
#include "common/audio_queue.hh" #include "common/audio_queue.hh"
#include "common/dir_item.hh" #include "common/dir_item.hh"
#include "common/generic_context.hh"
#include "common/generic_type_info.hh" #include "common/generic_type_info.hh"
#include "common/ptr_selector.hh" #include "common/ptr_selector.hh"
#include "common/thread.hh" #include "common/thread.hh"
@@ -22,15 +25,7 @@ namespace {
class AudioContext final : public nf7::File, public nf7::DirItem { class AudioContext final : public nf7::File, public nf7::DirItem {
public: public:
static inline const nf7::GenericTypeInfo<AudioContext> kType = { static inline const nf7::GenericTypeInfo<AudioContext> kType = {
"Audio/Context", {"nf7::DirItem",}}; "Audio/Context", {"nf7::DirItem",}, "drives miniaudio context"};
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; class Queue;
@@ -60,81 +55,64 @@ class AudioContext final : public nf7::File, public nf7::DirItem {
private: private:
std::shared_ptr<Queue> q_; std::shared_ptr<Queue> q_;
const char* popup_ = nullptr;
}; };
class AudioContext::Queue final : public nf7::audio::Queue, class AudioContext::Queue final : public nf7::audio::Queue,
public std::enable_shared_from_this<AudioContext::Queue> { public std::enable_shared_from_this<AudioContext::Queue> {
public: public:
enum State { struct SharedData {
kInitial,
kReady,
kBroken,
};
struct ThreadData {
public: public:
std::atomic<State> state = kInitial; std::atomic<bool> broken = false;
ma_context ctx; ma_context ctx;
}; };
struct Runner { struct Runner {
public: public:
Runner(const std::shared_ptr<ThreadData>& tdata) noexcept : tdata_(tdata) { Runner(const std::shared_ptr<SharedData>& d) noexcept : data_(d) {
} }
void operator()(Task&& t) { void operator()(Task&& t) {
if (tdata_->state != kBroken) { if (!data_->broken) {
t(&tdata_->ctx); ZoneScopedN("audio task");
t(&data_->ctx);
} }
} }
private: private:
std::shared_ptr<ThreadData> tdata_; std::shared_ptr<SharedData> data_;
}; };
using Thread = nf7::Thread<Runner, Task>; using Thread = nf7::Thread<Runner, Task>;
Queue() = delete; Queue() = delete;
Queue(AudioContext& f) noexcept : Queue(AudioContext& f) noexcept :
env_(&f.env()), env_(&f.env()),
tdata_(std::make_shared<ThreadData>()), data_(std::make_shared<SharedData>()),
th_(std::make_shared<Thread>(f, Runner {tdata_})) { th_(std::make_shared<Thread>(f, Runner {data_})) {
auto ctx = std::make_shared<nf7::GenericContext>(f.env(), 0, "creating ma_context"); th_->Push(th_, [data = data_](auto) {
th_->Push(ctx, [tdata = tdata_](auto) { if (MA_SUCCESS != ma_context_init(nullptr, 0, nullptr, &data->ctx)) {
if (MA_SUCCESS == ma_context_init(nullptr, 0, nullptr, &tdata->ctx)) { data->broken = true;
tdata->state = kReady;
} else {
tdata->state = kBroken;
} }
}); });
} }
~Queue() noexcept { ~Queue() noexcept {
th_->Push( th_->Push(th_, [](auto ma) { ma_context_uninit(ma); });
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 Push(const std::shared_ptr<nf7::Context>& ctx, Task&& task) noexcept override { void Push(const std::shared_ptr<nf7::Context>& ctx, Task&& task) noexcept override {
th_->Push(ctx, std::move(task)); th_->Push(ctx, std::move(task));
} }
std::shared_ptr<audio::Queue> self() noexcept override { return shared_from_this(); } std::shared_ptr<audio::Queue> self() noexcept override { return shared_from_this(); }
State state() const noexcept { return tdata_->state; } bool broken() const noexcept { return data_->broken; }
size_t tasksDone() const noexcept { return th_->tasksDone(); } size_t tasksDone() const noexcept { return th_->tasksDone(); }
// thread-safe // thread-safe
ma_context* ctx() const noexcept { ma_context* ctx() const noexcept {
return state() == kReady? &tdata_->ctx: nullptr; return broken()? nullptr: &data_->ctx;
} }
private: private:
Env* const env_; Env* const env_;
std::shared_ptr<ThreadData> tdata_; std::shared_ptr<SharedData> data_;
std::shared_ptr<Thread> th_; std::shared_ptr<Thread> th_;
}; };
@@ -203,12 +181,7 @@ void AudioContext::UpdateDeviceListMenu(ma_device_info* ptr, ma_uint32 n) noexce
} }
void AudioContext::UpdateTooltip() noexcept { void AudioContext::UpdateTooltip() noexcept {
const auto state = q_->state(); ImGui::Text("state: %s", q_->broken()? "broken": "running");
const char* state_str =
state == Queue::kInitial? "initializing":
state == Queue::kReady ? "ready":
state == Queue::kBroken ? "broken": "unknown";
ImGui::Text("state: %s", state_str);
} }
} }

View File

@@ -22,13 +22,15 @@
#include "nf7.hh" #include "nf7.hh"
#include "common/audio_queue.hh" #include "common/audio_queue.hh"
#include "common/config.hh"
#include "common/dir_item.hh" #include "common/dir_item.hh"
#include "common/file_base.hh" #include "common/file_base.hh"
#include "common/future.hh" #include "common/future.hh"
#include "common/generic_config.hh"
#include "common/generic_context.hh" #include "common/generic_context.hh"
#include "common/generic_memento.hh" #include "common/generic_memento.hh"
#include "common/generic_type_info.hh" #include "common/generic_type_info.hh"
#include "common/gui_config.hh" #include "common/gui.hh"
#include "common/life.hh" #include "common/life.hh"
#include "common/logger_ref.hh" #include "common/logger_ref.hh"
#include "common/node.hh" #include "common/node.hh"
@@ -44,13 +46,12 @@ using namespace std::literals;
namespace nf7 { namespace nf7 {
namespace { namespace {
class Device final : public nf7::FileBase, public nf7::DirItem, public nf7::Node { class Device final : public nf7::FileBase,
public nf7::GenericConfig, public nf7::DirItem, public nf7::Node {
public: public:
static inline const nf7::GenericTypeInfo<Device> kType = { static inline const nf7::GenericTypeInfo<Device> kType = {
"Audio/Device", {"nf7::DirItem",}}; "Audio/Device", {"nf7::DirItem",},
static void UpdateTypeTooltip() noexcept { "provides a ring buffer to send/receive PCM samples"};
ImGui::TextUnformatted("Provides a ring buffer to send/receive PCM samples.");
}
class Instance; class Instance;
class Lambda; class Lambda;
@@ -82,7 +83,6 @@ class Device final : public nf7::FileBase, public nf7::DirItem, public nf7::Node
Data() noexcept { } Data() noexcept { }
std::string Stringify() const noexcept; std::string Stringify() const noexcept;
void Parse(const std::string&); void Parse(const std::string&);
void serialize(auto& ar) { void serialize(auto& ar) {
ar(ctxpath, mode, devname, fmt, srate, ch); ar(ctxpath, mode, devname, fmt, srate, ch);
} }
@@ -100,10 +100,11 @@ class Device final : public nf7::FileBase, public nf7::DirItem, public nf7::Node
}; };
Device(nf7::Env& env, Data&& data = {}) noexcept : Device(nf7::Env& env, Data&& data = {}) noexcept :
nf7::FileBase(kType, env, {&log_}), nf7::FileBase(kType, env),
nf7::GenericConfig(mem_),
nf7::DirItem(nf7::DirItem::kMenu | nf7::DirItem::kTooltip), nf7::DirItem(nf7::DirItem::kMenu | nf7::DirItem::kTooltip),
nf7::Node(nf7::Node::kNone), nf7::Node(nf7::Node::kNone),
life_(*this), log_(*this), mem_(std::move(data), *this) { life_(*this), log_(*this), mem_(*this, std::move(data)) {
mem_.onCommit = mem_.onRestore = [this]() { cache_ = std::nullopt; }; mem_.onCommit = mem_.onRestore = [this]() { cache_ = std::nullopt; };
} }
@@ -119,13 +120,12 @@ class Device final : public nf7::FileBase, public nf7::DirItem, public nf7::Node
std::shared_ptr<nf7::Node::Lambda> CreateLambda( std::shared_ptr<nf7::Node::Lambda> CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override; const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
std::span<const std::string> GetInputs() const noexcept override { nf7::Node::Meta GetMeta() const noexcept override {
static const std::vector<std::string> kInputs = {"info", "mix", "peek"}; static const std::vector<std::string> kInputs = {};
return kInputs; return {
} {"info", "mix", "peek"},
std::span<const std::string> GetOutputs() const noexcept override { {"result"}
static const std::vector<std::string> kOutputs = {"result"}; };
return kOutputs;
} }
nf7::Future<std::shared_ptr<Instance>> Build() noexcept; nf7::Future<std::shared_ptr<Instance>> Build() noexcept;
@@ -135,7 +135,7 @@ class Device final : public nf7::FileBase, public nf7::DirItem, public nf7::Node
File::Interface* interface(const std::type_info& t) noexcept override { File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector< return nf7::InterfaceSelector<
nf7::DirItem, nf7::Memento, nf7::Node>(t).Select(this, &mem_); nf7::Config, nf7::DirItem, nf7::Memento, nf7::Node>(t).Select(this, &mem_);
} }
private: private:
@@ -399,10 +399,6 @@ try {
void Device::UpdateMenu() noexcept { void Device::UpdateMenu() noexcept {
if (ImGui::BeginMenu("config")) {
nf7::gui::Config(mem_);
ImGui::EndMenu();
}
if (ImGui::MenuItem("build")) { if (ImGui::MenuItem("build")) {
Build(); Build();
} }

94
file/codec_stbimage.cc Normal file
View File

@@ -0,0 +1,94 @@
#include <filesystem>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include <stb_image.h>
#include "nf7.hh"
#include "common/generic_type_info.hh"
#include "common/node.hh"
#include "common/pure_node_file.hh"
namespace nf7 {
namespace {
class StbImage final : public nf7::Node::Lambda,
public std::enable_shared_from_this<StbImage> {
public:
static inline nf7::GenericTypeInfo<nf7::PureNodeFile<StbImage>> kType = {
"Codec/StbImage", {"nf7::DirItem"},
};
static inline const nf7::Node::Meta kMeta = {{"input"}, {"image", "error"},};
using nf7::Node::Lambda::Lambda;
void Handle(const nf7::Node::Lambda::Msg& in) noexcept override
try {
std::string npath;
uint8_t comp = 4;
if (in.value.isTuple()) {
npath = in.value.tuple("npath").string();
comp = in.value.
tupleOr("comp", static_cast<nf7::Value::Integer>(comp)).
integerOrScalar<uint8_t>();
} else {
npath = in.value.string();
}
env().ExecAsync(shared_from_this(), [=, this]() {
std::optional<nf7::Value> ret;
try {
ret = Exec(npath, comp);
} catch (nf7::Exception& e) {
log_->Error(e);
}
env().ExecSub(shared_from_this(), [this, in, ret]() {
if (ret) {
in.sender->Handle("image", *ret, shared_from_this());
} else {
in.sender->Handle("error", nf7::Value::Pulse {}, shared_from_this());
}
});
});
} catch (nf7::Exception& e) {
log_->Error(e);
}
nf7::Value Exec(const std::string& npath, uint8_t comp) {
if (comp <= 0 || 4 < comp) {
throw nf7::Exception {"invalid comp (0~4 are allwoed)"};
}
int w, h, actual_comp;
uint8_t* data = stbi_load(npath.c_str(), &w, &h, &actual_comp, comp);
if (!data) {
throw nf7::Exception {"failed to load image from "+npath};
}
if (comp != 0) {
actual_comp = comp;
}
const auto size =
static_cast<size_t>(w)*
static_cast<size_t>(h)*
static_cast<size_t>(actual_comp);
std::vector<uint8_t> buf(size);
std::memcpy(buf.data(), data, size);
stbi_image_free(data);
return std::vector<nf7::Value::TuplePair> {
{"buf", std::move(buf)},
{"w", static_cast<nf7::Value::Integer>(w)},
{"h", static_cast<nf7::Value::Integer>(h)},
{"comp", static_cast<nf7::Value::Integer>(actual_comp)},
};
;
}
std::shared_ptr<nf7::LoggerRef> log_;
};
static_assert(PureNodeFile_LoggerRef<StbImage>);
}
} // namespace nf7

116
file/font_context.cc Normal file
View File

@@ -0,0 +1,116 @@
#include <atomic>
#include <typeinfo>
#include <memory>
#include <ft2build.h>
#include FT_FREETYPE_H
#include <imgui.h>
#include <tracy/Tracy.hpp>
#include "nf7.hh"
#include "common/dir_item.hh"
#include "common/font_queue.hh"
#include "common/generic_type_info.hh"
#include "common/ptr_selector.hh"
#include "common/thread.hh"
namespace nf7 {
namespace {
class FontContext final : public nf7::File, public nf7::DirItem {
public:
static inline const nf7::GenericTypeInfo<FontContext> kType = {
"Font/Context", {"nf7::DirItem",}, "drives freetype context"};
class Queue;
FontContext(nf7::Env& env) noexcept :
nf7::File(kType, env),
nf7::DirItem(nf7::DirItem::kTooltip),
q_(std::make_shared<Queue>(*this)) {
}
FontContext(nf7::Deserializer& ar) : FontContext(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<FontContext>(env);
}
void UpdateTooltip() noexcept override;
nf7::File::Interface* interface(const std::type_info& t) noexcept {
return nf7::InterfaceSelector<
nf7::DirItem, nf7::font::Queue>(t).Select(this, q_.get());
}
private:
std::shared_ptr<Queue> q_;
};
class FontContext::Queue final : public nf7::font::Queue,
public std::enable_shared_from_this<FontContext::Queue> {
public:
struct SharedData final {
public:
std::atomic<bool> broken = false;
FT_Library ft;
};
struct Runner final {
public:
Runner(const std::shared_ptr<SharedData>& d) noexcept : data_(d) {
}
void operator()(Task&& t) noexcept {
if (!data_->broken) {
ZoneScopedN("font task");
t(data_->ft);
}
}
private:
std::shared_ptr<SharedData> data_;
};
using Thread = nf7::Thread<Runner, Task>;
Queue(FontContext& f) noexcept :
data_(std::make_shared<SharedData>()),
th_(std::make_shared<Thread>(f, Runner {data_})) {
th_->Push(th_, [data = data_](auto) {
if (const auto err = FT_Init_FreeType(&data->ft)) {
data->broken = true;
}
});
}
~Queue() noexcept {
th_->Push(th_, [](auto ft) {
FT_Done_FreeType(ft);
});
}
void Push(const std::shared_ptr<nf7::Context>& ctx, Task&& task) noexcept override {
th_->Push(ctx, std::move(task));
}
std::shared_ptr<nf7::font::Queue> self() noexcept override {
return shared_from_this();
}
bool broken() const noexcept { return data_->broken; }
size_t tasksDone() const noexcept { return th_->tasksDone(); }
private:
std::shared_ptr<SharedData> data_;
std::shared_ptr<Thread> th_;
};
void FontContext::UpdateTooltip() noexcept {
ImGui::Text("status : %s", q_->broken()? "broken": "running");
ImGui::Text("tasks done: %zu", q_->tasksDone());
}
} // namespace
} // namespace nf7

247
file/font_face.cc Normal file
View File

@@ -0,0 +1,247 @@
#include <array>
#include <cassert>
#include <exception>
#include <filesystem>
#include <memory>
#include <span>
#include <string>
#include <vector>
#include <imgui.h>
#include <yaml-cpp/yaml.h>
#include <yas/serialize.hpp>
#include "nf7.hh"
#include "common/dir_item.hh"
#include "common/factory.hh"
#include "common/file_base.hh"
#include "common/font_face.hh"
#include "common/font_queue.hh"
#include "common/future.hh"
#include "common/generic_config.hh"
#include "common/generic_type_info.hh"
#include "common/generic_memento.hh"
#include "common/gui.hh"
#include "common/life.hh"
#include "common/logger_ref.hh"
#include "common/memento.hh"
#include "common/nfile_watcher.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
#include "common/yas_std_filesystem.hh"
namespace nf7 {
namespace {
class FontFace final : public nf7::FileBase,
public nf7::GenericConfig, public nf7::DirItem, public nf7::Node,
public nf7::AsyncFactory<std::shared_ptr<nf7::font::Face>> {
public:
static inline const nf7::GenericTypeInfo<FontFace> kType = {"Font/Face", {"nf7::DirItem",}};
class Lambda;
struct Data {
std::filesystem::path npath;
void serialize(auto& ar) { ar(npath); }
std::string Stringify() const noexcept {
YAML::Emitter st;
st << YAML::BeginMap;
st << YAML::Key << "npath";
st << YAML::Value << npath.generic_string();
st << YAML::EndMap;
return {st.c_str(), st.size()};
}
void Parse(const std::string& str)
try {
const auto yaml = YAML::Load(str);
Data d;
d.npath = yaml["npath"].as<std::string>();
*this = d;
} catch (YAML::Exception& e) {
throw nf7::Exception {e.what()};
}
};
FontFace(nf7::Env& env, Data&& d = {}) noexcept :
nf7::FileBase(kType, env),
nf7::GenericConfig(mem_),
nf7::DirItem(nf7::DirItem::kMenu |
nf7::DirItem::kTooltip),
nf7::Node(nf7::Node::kNone),
life_(*this),
nwatch_(*this),
log_(std::make_shared<nf7::LoggerRef>(*this)),
mem_(*this, std::move(d)) {
mem_.onCommit = mem_.onRestore = nwatch_.onMod = [this]() {
cache_ = std::nullopt;
Touch();
};
}
FontFace(nf7::Deserializer& ar) : FontFace(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<FontFace>(env, Data {mem_.data()});
}
nf7::Future<std::shared_ptr<nf7::font::Face>> Create() noexcept override
try {
if (cache_) return *cache_;
auto& q = ResolveUpwardOrThrow("_font").interfaceOrThrow<nf7::font::Queue>();
auto ctx = std::make_shared<nf7::GenericContext>(*this, "font face factory");
cache_ = nf7::font::Face::Create(ctx, q.self(), mem_->npath);
return *cache_;
} catch (nf7::Exception&) {
return {std::current_exception()};
}
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
nf7::Node::Meta GetMeta() const noexcept override {
return {
{"command"}, {"result"},
};
}
void UpdateMenu() noexcept override;
void UpdateTooltip() noexcept override;
nf7::File::Interface* interface(const std::type_info& t) noexcept {
return nf7::InterfaceSelector<
nf7::Config, nf7::DirItem, nf7::Memento, nf7::Node>(t).Select(this, &mem_);
}
private:
nf7::Life<FontFace> life_;
nf7::NFileWatcher nwatch_;
std::shared_ptr<nf7::LoggerRef> log_;
nf7::GenericMemento<Data> mem_;
std::optional<nf7::Future<std::shared_ptr<nf7::font::Face>>> cache_;
};
class FontFace::Lambda final : public nf7::Node::Lambda,
public std::enable_shared_from_this<FontFace::Lambda> {
public:
Lambda(FontFace& 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();
if (in.name == "command") {
const auto code = in.value.tuple("code").integerOrScalar<FT_ULong>();
const auto& size_tup = in.value.tuple("size");
std::array<FT_UInt, 2> size = {0, 0};
if (size_tup.isInteger() || size_tup.isScalar()) {
size[1] = size_tup.integerOrScalar<FT_UInt>();
} else {
for (size_t i = 0; i < size.size(); ++i) {
size[i] = size_tup.
tupleOr(i, nf7::Value::Integer {0}).
integerOrScalar<FT_UInt>();
}
}
auto self = shared_from_this();
nf7::Future<nf7::Value>::Promise pro {self};
f_->Create().Chain(pro, [=, this](auto& face) mutable {
face->ftq()->Push(self, [=, this](auto) mutable {
pro.Wrap([&]() { return Exec(**face, size, code); });
});
});
pro.future().ThenIf(self, [=](auto& v) {
in.sender->Handle("result", v, self);
}).Catch<nf7::Exception>(self, [log = f_->log_](auto& e) {
log->Error(e);
});
} else {
assert(false);
}
} catch (nf7::ExpiredException&) {
} catch (nf7::Exception& e) {
f_->log_->Error(e);
}
nf7::Value Exec(FT_Face face, const std::array<FT_UInt, 2>& size, FT_ULong code) {
font::Enforce(FT_Set_Pixel_Sizes(face, size[0], size[1]));
font::Enforce(FT_Load_Char(face, code, FT_LOAD_RENDER));
// check the loaded glyph
const auto& g = *face->glyph;
if (g.bitmap.pixel_mode != FT_PIXEL_MODE_GRAY) {
throw nf7::Exception {"unsupported pixel mode (only 8-bit grayscale allowed)"};
}
if (g.format != FT_GLYPH_FORMAT_BITMAP) {
throw nf7::Exception {"unsupported glyph format (only bitmap allowed)"};
}
// copy rendered bitmap
std::vector<uint8_t> dst(g.bitmap.width*g.bitmap.rows);
auto src = g.bitmap.buffer;
for (unsigned int y = 0; y < g.bitmap.rows; ++y) {
std::memcpy(&dst[y*g.bitmap.width], src, g.bitmap.width);
src += g.bitmap.pitch;
}
return nf7::Value { std::vector<nf7::Value::TuplePair> {
{"w", static_cast<nf7::Value::Integer>(g.bitmap.width)},
{"h", static_cast<nf7::Value::Integer>(g.bitmap.rows)},
{"buf", std::move(dst)},
{"hBearX", static_cast<nf7::Value::Scalar>(g.metrics.horiBearingX)/64},
{"hBearY", static_cast<nf7::Value::Scalar>(g.metrics.horiBearingY)/64},
{"hAdv", static_cast<nf7::Value::Scalar>(g.metrics.horiAdvance)/64},
{"vBearX", static_cast<nf7::Value::Scalar>(g.metrics.vertBearingX)/64},
{"vBearY", static_cast<nf7::Value::Scalar>(g.metrics.vertBearingY)/64},
{"vAdv", static_cast<nf7::Value::Scalar>(g.metrics.vertAdvance)/64},
}};
}
private:
nf7::Life<FontFace>::Ref f_;
};
std::shared_ptr<nf7::Node::Lambda> FontFace::CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
return std::make_shared<FontFace::Lambda>(*this, parent);
}
void FontFace::UpdateMenu() noexcept {
if (ImGui::MenuItem("load")) {
Create();
}
}
void FontFace::UpdateTooltip() noexcept {
ImGui::Text("npath : %s", mem_->npath.generic_string().c_str());
const char* status = "unused";
if (cache_) {
status =
cache_->done()? "loaded":
cache_->yet()? "loading":
cache_->error()? "broken": "X(";
}
ImGui::Text("status: %s", status);
}
} // namespace
} // namespace nf7

View File

@@ -15,6 +15,8 @@
#include <magic_enum.hpp> #include <magic_enum.hpp>
#include <tracy/Tracy.hpp>
#include <yaml-cpp/yaml.h> #include <yaml-cpp/yaml.h>
#include <yas/serialize.hpp> #include <yas/serialize.hpp>
@@ -30,6 +32,7 @@
#include "common/dir_item.hh" #include "common/dir_item.hh"
#include "common/factory.hh" #include "common/factory.hh"
#include "common/file_base.hh" #include "common/file_base.hh"
#include "common/generic_config.hh"
#include "common/generic_context.hh" #include "common/generic_context.hh"
#include "common/generic_memento.hh" #include "common/generic_memento.hh"
#include "common/generic_type_info.hh" #include "common/generic_type_info.hh"
@@ -38,7 +41,7 @@
#include "common/gl_fence.hh" #include "common/gl_fence.hh"
#include "common/gl_obj.hh" #include "common/gl_obj.hh"
#include "common/gl_shader_preproc.hh" #include "common/gl_shader_preproc.hh"
#include "common/gui_config.hh" #include "common/gui.hh"
#include "common/gui_window.hh" #include "common/gui_window.hh"
#include "common/life.hh" #include "common/life.hh"
#include "common/logger_ref.hh" #include "common/logger_ref.hh"
@@ -78,7 +81,7 @@ concept HasWindow = requires(T& x) {
template <typename T> template <typename T>
class ObjBase : public nf7::FileBase, class ObjBase : public nf7::FileBase,
public nf7::DirItem, public nf7::Node, public nf7::GenericConfig, public nf7::DirItem, public nf7::Node,
public nf7::AsyncFactory<nf7::Mutex::Resource<std::shared_ptr<typename T::Product>>> { public nf7::AsyncFactory<nf7::Mutex::Resource<std::shared_ptr<typename T::Product>>> {
public: public:
using ThisObjBase = ObjBase<T>; using ThisObjBase = ObjBase<T>;
@@ -90,28 +93,28 @@ class ObjBase : public nf7::FileBase,
struct TypeInfo; struct TypeInfo;
static void UpdateTypeTooltip() noexcept {
T::UpdateTypeTooltip();
}
ObjBase(nf7::Env& env, T&& data = {}) noexcept : ObjBase(nf7::Env& env, T&& data = {}) noexcept :
nf7::FileBase(TypeInfo::kType, env, {}), nf7::FileBase(TypeInfo::kType, env),
nf7::GenericConfig(mem_),
nf7::DirItem(nf7::DirItem::kMenu | nf7::DirItem(nf7::DirItem::kMenu |
nf7::DirItem::kTooltip), nf7::DirItem::kTooltip),
nf7::Node(nf7::Node::kNone), nf7::Node(nf7::Node::kNone),
life_(*this), life_(*this),
log_(std::make_shared<nf7::LoggerRef>(*this)), log_(std::make_shared<nf7::LoggerRef>(*this)),
nwatch_(std::make_shared<nf7::NFileWatcher>()), nwatch_(std::make_shared<nf7::NFileWatcher>(*this)),
mem_(std::move(data), *this) { mem_(*this, std::move(data)) {
nf7::FileBase::Install(*log_);
nf7::FileBase::Install(*nwatch_);
nwatch_->onMod = mem_.onRestore = mem_.onCommit = [this]() { nwatch_->onMod = mem_.onRestore = mem_.onCommit = [this]() {
Drop(); Drop(true /* = quiet */);
Touch();
}; };
if constexpr (HasWindow<T>) { if constexpr (HasWindow<T>) {
win_.emplace(*this, T::kWindowTitle); win_.emplace(*this, T::kWindowTitle);
win_->onConfig = []() {
const auto em = ImGui::GetFontSize();
ImGui::SetNextWindowSize({8*em, 8*em}, ImGuiCond_FirstUseEver);
};
win_->onUpdate = [this]() { mem_->UpdateWindow(fu_); };
} }
} }
@@ -135,11 +138,8 @@ class ObjBase : public nf7::FileBase,
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept override { const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept override {
return std::make_shared<Lambda>(*this, parent); return std::make_shared<Lambda>(*this, parent);
} }
std::span<const std::string> GetInputs() const noexcept override { nf7::Node::Meta GetMeta() const noexcept override {
return T::kInputs; return {T::kInputs, T::kOutputs};
}
std::span<const std::string> GetOutputs() const noexcept override {
return T::kOutputs;
} }
ResourceFuture Create() noexcept final { ResourceFuture Create() noexcept final {
@@ -150,11 +150,14 @@ class ObjBase : public nf7::FileBase,
ResourcePromise pro {ctx}; ResourcePromise pro {ctx};
mtx_.AcquireLock(ctx, ex).ThenIf([this, ctx, pro](auto& k) mutable { mtx_.AcquireLock(ctx, ex).ThenIf([this, ctx, pro](auto& k) mutable {
if (!fu_) { if (!fu_ || fu_->error()) {
watch_ = std::make_shared<nf7::GenericWatcher>(env()); watch_ = std::make_shared<nf7::GenericWatcher>(env());
watch_->AddHandler(nf7::File::Event::kUpdate, [self = life_.ref()](auto&) {
const auto handler = [self = life_.ref()](auto&) {
if (self) self->Drop(); if (self) self->Drop();
}); };
watch_->AddHandler(nf7::File::Event::kUpdate, handler);
watch_->AddHandler(nf7::File::Event::kRemove, handler);
nwatch_->Clear(); nwatch_->Clear();
fu_ = mem_->Create(CreateParam { fu_ = mem_->Create(CreateParam {
@@ -168,27 +171,11 @@ class ObjBase : public nf7::FileBase,
fu_->Chain(pro, [k](auto& obj) { return Resource {k, obj}; }); fu_->Chain(pro, [k](auto& obj) { return Resource {k, obj}; });
}); });
return pro.future(). return pro.future().
template Catch<nf7::Exception>(ctx, [this](auto& e) { template Catch<nf7::Exception>(ctx, [log = log_](auto& e) {
log_->Error(e); log->Error(e);
}); });
} }
void Update() noexcept override {
nf7::FileBase::Update();
if constexpr (HasWindow<T>) {
if (win_->shownInCurrentFrame()) {
const auto em = ImGui::GetFontSize();
ImGui::SetNextWindowSize({8*em, 8*em}, ImGuiCond_FirstUseEver);
}
if (win_->Begin()) {
mem_->UpdateWindow(fu_);
}
win_->End();
}
}
void UpdateMenu() noexcept override { void UpdateMenu() noexcept override {
if (ImGui::BeginMenu("object management")) { if (ImGui::BeginMenu("object management")) {
if (ImGui::MenuItem("create", nullptr, false, !fu_)) { if (ImGui::MenuItem("create", nullptr, false, !fu_)) {
@@ -206,15 +193,9 @@ class ObjBase : public nf7::FileBase,
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("these actions can cause CORRUPTION of running lambdas"); ImGui::SetTooltip("these actions can cause CORRUPTION of running lambdas");
} }
if constexpr (nf7::gui::ConfigData<T>) {
if (ImGui::BeginMenu("config")) {
nf7::gui::Config(mem_);
ImGui::EndMenu();
}
}
if constexpr (HasWindow<T>) { if constexpr (HasWindow<T>) {
ImGui::Separator(); ImGui::Separator();
ImGui::MenuItem(T::kWindowTitle, nullptr, &win_->shown()); win_->MenuItem();
} }
} }
void UpdateTooltip() noexcept override { void UpdateTooltip() noexcept override {
@@ -239,7 +220,7 @@ class ObjBase : public nf7::FileBase,
nf7::File::Interface* interface(const std::type_info& t) noexcept override { nf7::File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector< return nf7::InterfaceSelector<
nf7::DirItem, nf7::Memento, nf7::Node, ThisFactory>(t).Select(this, &mem_); nf7::Config, nf7::DirItem, nf7::Memento, nf7::Node, ThisFactory>(t).Select(this, &mem_);
} }
private: private:
@@ -257,12 +238,12 @@ class ObjBase : public nf7::FileBase,
std::optional<nf7::gui::Window> win_; std::optional<nf7::gui::Window> win_;
void Drop() noexcept { void Drop(bool quiet = false) noexcept {
auto ctx = std::make_shared<nf7::GenericContext>(*this, "dropping OpenGL obj"); auto ctx = std::make_shared<nf7::GenericContext>(*this, "dropping OpenGL obj");
mtx_.AcquireLock(ctx, true /* = exclusive */). mtx_.AcquireLock(ctx, true /* = exclusive */).
ThenIf([this](auto&) { ThenIf([this, quiet](auto&) {
fu_ = std::nullopt; fu_ = std::nullopt;
Touch(); if (!quiet) Touch();
}); });
} }
@@ -305,10 +286,6 @@ class ObjBase : public nf7::FileBase,
struct Buffer { struct Buffer {
public: public:
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("OpenGL buffer");
}
static inline const std::vector<std::string> kInputs = { static inline const std::vector<std::string> kInputs = {
"upload", "upload",
}; };
@@ -376,6 +353,9 @@ struct Buffer {
const auto t = gl::ToEnum(buf.meta().target); const auto t = gl::ToEnum(buf.meta().target);
glBindBuffer(t, buf.id()); glBindBuffer(t, buf.id());
{ {
ZoneScopedN("upload buffer");
ZoneValue(vec->size());
auto& size = buf.param().size; auto& size = buf.param().size;
if (size != vec->size()) { if (size != vec->size()) {
size = vec->size(); size = vec->size();
@@ -409,16 +389,13 @@ struct Buffer {
}; };
template <> template <>
struct ObjBase<Buffer>::TypeInfo final { struct ObjBase<Buffer>::TypeInfo final {
static inline const nf7::GenericTypeInfo<ObjBase<Buffer>> kType = {"GL/Buffer", {"nf7::DirItem"}}; static inline const nf7::GenericTypeInfo<ObjBase<Buffer>> kType = {
"GL/Buffer", {"nf7::DirItem"}, "OpenGL buffer"};
}; };
struct Texture { struct Texture {
public: public:
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("OpenGL texture");
}
static inline const std::vector<std::string> kInputs = { static inline const std::vector<std::string> kInputs = {
"upload", "download", "upload", "download",
}; };
@@ -501,7 +478,7 @@ struct Texture {
if (p.in.name == "upload") { if (p.in.name == "upload") {
const auto& v = p.in.value; const auto& v = p.in.value;
const auto vec = v.tuple("vec").vector(); const auto buf = v.tuple("buf").vector();
auto& tex = **p.obj; auto& tex = **p.obj;
static const char* kOffsetNames[] = {"x", "y", "z"}; static const char* kOffsetNames[] = {"x", "y", "z"};
@@ -522,31 +499,37 @@ struct Texture {
} }
const auto texel = std::accumulate(size.begin(), size.end(), 1, std::multiplies<uint32_t> {}); const auto texel = std::accumulate(size.begin(), size.end(), 1, std::multiplies<uint32_t> {});
const auto vecsz = texel*gl::GetByteSize(ifmt_); const auto bufsz = texel*gl::GetByteSize(ifmt_);
if (vec->size() < static_cast<size_t>(vecsz)) { if (buf->size() < static_cast<size_t>(bufsz)) {
throw nf7::Exception {"vector is too small"}; throw nf7::Exception {"buffer is too small"};
} }
const auto fmt = gl::ToEnum(gl::GetColorComp(ifmt_)); const auto fmt = gl::ToEnum(gl::GetColorComp(ifmt_));
const auto type = gl::ToEnum(gl::GetNumericType(ifmt_)); const auto type = gl::ToEnum(gl::GetNumericType(ifmt_));
p.la->env().ExecGL(p.la, [=, &tex]() { p.la->env().ExecGL(p.la, [=, &tex]() {
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
const auto t = gl::ToEnum(tex.meta().target); const auto t = gl::ToEnum(tex.meta().target);
glBindTexture(t, tex.id()); glBindTexture(t, tex.id());
switch (t) { switch (t) {
case GL_TEXTURE_2D: case GL_TEXTURE_2D:
case GL_TEXTURE_RECTANGLE: case GL_TEXTURE_RECTANGLE: {
ZoneScopedN("glTexSubImage2D");
ZoneValue(buf->size());
glTexSubImage2D(t, 0, glTexSubImage2D(t, 0,
static_cast<GLint>(offset[0]), static_cast<GLint>(offset[0]),
static_cast<GLint>(offset[1]), static_cast<GLint>(offset[1]),
static_cast<GLsizei>(size[0]), static_cast<GLsizei>(size[0]),
static_cast<GLsizei>(size[1]), static_cast<GLsizei>(size[1]),
fmt, type, vec->data()); fmt, type, buf->data());
break; } break;
default: default:
assert(false); assert(false);
break; break;
} }
glBindTexture(t, 0); glBindTexture(t, 0);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
assert(0 == glGetError()); assert(0 == glGetError());
}); });
return true; return true;
@@ -578,23 +561,32 @@ struct Texture {
const auto size = tex.meta().size; const auto size = tex.meta().size;
const auto texel = std::accumulate(size.begin(), size.end(), 1, std::multiplies<uint32_t> {}); const auto texel = std::accumulate(size.begin(), size.end(), 1, std::multiplies<uint32_t> {});
const auto bsize = texel*gl::GetCompCount(comp)*gl::GetByteSize(numtype); const auto bsize = texel*gl::GetCompCount(comp)*gl::GetByteSize(numtype);
glBufferData(GL_PIXEL_PACK_BUFFER, static_cast<GLsizeiptr>(bsize), nullptr, GL_STREAM_READ); const auto t = gl::ToEnum(tex.meta().target);
const auto t = gl::ToEnum(tex.meta().target); {
glBindTexture(t, tex.id()); ZoneScopedN("request to download texture");
glGetTexImage(t, 0, gl::ToEnum(comp), gl::ToEnum(numtype), nullptr); glBufferData(GL_PIXEL_PACK_BUFFER, static_cast<GLsizeiptr>(bsize), nullptr, GL_STREAM_READ);
glBindTexture(t, 0);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); glBindTexture(t, tex.id());
assert(0 == glGetError()); glPixelStorei(GL_PACK_ALIGNMENT, 1);
glGetTexImage(t, 0, gl::ToEnum(comp), gl::ToEnum(numtype), nullptr);
glPixelStorei(GL_PACK_ALIGNMENT, 4);
glBindTexture(t, 0);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
assert(0 == glGetError());
}
nf7::gl::ExecFenceSync(p.la).ThenIf([=, &tex](auto&) { nf7::gl::ExecFenceSync(p.la).ThenIf([=, &tex](auto&) {
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo); glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
auto buf = std::make_shared<std::vector<uint8_t>>(bsize); auto buf = std::make_shared<std::vector<uint8_t>>(bsize);
const auto ptr = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY); {
std::memcpy(buf->data(), ptr, static_cast<size_t>(bsize)); ZoneScopedN("download texture");
glUnmapBuffer(GL_PIXEL_PACK_BUFFER); const auto ptr = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
std::memcpy(buf->data(), ptr, static_cast<size_t>(bsize));
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
glDeleteBuffers(1, &pbo); glDeleteBuffers(1, &pbo);
@@ -602,10 +594,10 @@ struct Texture {
p.la->env().ExecSub(p.la, [=, &tex]() { p.la->env().ExecSub(p.la, [=, &tex]() {
auto v = nf7::Value {std::vector<nf7::Value::TuplePair> { auto v = nf7::Value {std::vector<nf7::Value::TuplePair> {
{"w", static_cast<nf7::Value::Integer>(size[0])}, {"w", static_cast<nf7::Value::Integer>(size[0])},
{"h", static_cast<nf7::Value::Integer>(size[1])}, {"h", static_cast<nf7::Value::Integer>(size[1])},
{"d", static_cast<nf7::Value::Integer>(size[2])}, {"d", static_cast<nf7::Value::Integer>(size[2])},
{"vector", buf}, {"buf", buf},
}}; }};
p.in.sender->Handle("buffer", std::move(v), p.la); p.in.sender->Handle("buffer", std::move(v), p.la);
}); });
@@ -687,16 +679,13 @@ struct Texture {
}; };
template <> template <>
struct ObjBase<Texture>::TypeInfo final { struct ObjBase<Texture>::TypeInfo final {
static inline const nf7::GenericTypeInfo<ObjBase<Texture>> kType = {"GL/Texture", {"nf7::DirItem"}}; static inline const nf7::GenericTypeInfo<ObjBase<Texture>> kType = {
"GL/Texture", {"nf7::DirItem"}, "OpenGL texture"};
}; };
struct Shader { struct Shader {
public: public:
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("OpenGL shader");
}
static inline const std::vector<std::string> kInputs = {}; static inline const std::vector<std::string> kInputs = {};
static inline const std::vector<std::string> kOutputs = {}; static inline const std::vector<std::string> kOutputs = {};
@@ -779,16 +768,13 @@ struct Shader {
}; };
template <> template <>
struct ObjBase<Shader>::TypeInfo final { struct ObjBase<Shader>::TypeInfo final {
static inline const nf7::GenericTypeInfo<ObjBase<Shader>> kType = {"GL/Shader", {"nf7::DirItem"}}; static inline const nf7::GenericTypeInfo<ObjBase<Shader>> kType = {
"GL/Shader", {"nf7::DirItem"}, "OpenGL shader"};
}; };
struct Program { struct Program {
public: public:
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("OpenGL program");
}
static inline const std::vector<std::string> kInputs = { static inline const std::vector<std::string> kInputs = {
"draw", "draw",
}; };
@@ -944,6 +930,8 @@ struct Program {
// execute drawing after successful locking // execute drawing after successful locking
apro.future().ThenIf(nf7::Env::kGL, p.la, [=, tex_fu = std::move(tex_fu)](auto&) { apro.future().ThenIf(nf7::Env::kGL, p.la, [=, tex_fu = std::move(tex_fu)](auto&) {
ZoneScopedN("draw");
const auto& fbo = *fbo_fu.value().first; const auto& fbo = *fbo_fu.value().first;
const auto& vao = *vao_fu.value().first; const auto& vao = *vao_fu.value().first;
const auto& prog = *p.obj; const auto& prog = *p.obj;
@@ -984,8 +972,10 @@ struct Program {
config.ApplyState(); config.ApplyState();
if (vao->meta().index) { if (vao->meta().index) {
const auto numtype = gl::ToEnum(vao->meta().index->numtype); const auto numtype = gl::ToEnum(vao->meta().index->numtype);
ZoneScopedN("glDrawElementsInstanced");
glDrawElementsInstanced(mode, count, numtype, nullptr, inst); glDrawElementsInstanced(mode, count, numtype, nullptr, inst);
} else { } else {
ZoneScopedN("glDrawArraysInstanced");
glDrawArraysInstanced(mode, 0, count, inst); glDrawArraysInstanced(mode, 0, count, inst);
} }
config.RevertState(); config.RevertState();
@@ -1002,6 +992,10 @@ struct Program {
if (status != GL_FRAMEBUFFER_COMPLETE) { if (status != GL_FRAMEBUFFER_COMPLETE) {
p.log->Warn("framebuffer is broken"); p.log->Warn("framebuffer is broken");
} }
p.la->env().ExecSub(p.la, [p]() {
p.in.sender->Handle("done", nf7::Value::Pulse {}, p.la);
});
}).Catch<nf7::Exception>([p](auto& e) { }).Catch<nf7::Exception>([p](auto& e) {
p.log->Error(e); p.log->Error(e);
}); });
@@ -1067,16 +1061,13 @@ struct Program {
}; };
template <> template <>
struct ObjBase<Program>::TypeInfo final { struct ObjBase<Program>::TypeInfo final {
static inline const nf7::GenericTypeInfo<ObjBase<Program>> kType = {"GL/Program", {"nf7::DirItem"}}; static inline const nf7::GenericTypeInfo<ObjBase<Program>> kType = {
"GL/Program", {"nf7::DirItem"}, "OpenGL program"};
}; };
struct VertexArray { struct VertexArray {
public: public:
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("OpenGL Vertex Array Object");
}
static inline const std::vector<std::string> kInputs = { static inline const std::vector<std::string> kInputs = {
}; };
static inline const std::vector<std::string> kOutputs = { static inline const std::vector<std::string> kOutputs = {
@@ -1256,26 +1247,22 @@ struct VertexArray {
private: private:
nf7::File::Path index_; nf7::File::Path index_;
gl::NumericType index_numtype_; gl::NumericType index_numtype_ = gl::NumericType::U16;
std::vector<Attr> attrs_; std::vector<Attr> attrs_;
}; };
template <> template <>
struct ObjBase<VertexArray>::TypeInfo final { struct ObjBase<VertexArray>::TypeInfo final {
static inline const nf7::GenericTypeInfo<ObjBase<VertexArray>> kType = {"GL/VertexArray", {"nf7::DirItem"}}; static inline const nf7::GenericTypeInfo<ObjBase<VertexArray>> kType = {
"GL/VertexArray", {"nf7::DirItem"}, "OpenGL vertex array"};
}; };
struct Framebuffer { struct Framebuffer {
public: public:
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("OpenGL Framebuffer Object");
}
static inline const std::vector<std::string> kInputs = { static inline const std::vector<std::string> kInputs = {
"clear", "blit", "clear", "blit",
}; };
static inline const std::vector<std::string> kOutputs = { static inline const std::vector<std::string> kOutputs = {};
};
using Product = nf7::gl::Framebuffer; using Product = nf7::gl::Framebuffer;
@@ -1388,7 +1375,10 @@ struct Framebuffer {
if (p.in.name == "clear") { if (p.in.name == "clear") {
(**p.obj).meta().LockAttachments(p.la).ThenIf(nf7::Env::kGL, p.la, [=](auto&) { (**p.obj).meta().LockAttachments(p.la).ThenIf(nf7::Env::kGL, p.la, [=](auto&) {
glBindFramebuffer(GL_FRAMEBUFFER, (**p.obj).id()); glBindFramebuffer(GL_FRAMEBUFFER, (**p.obj).id());
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); {
ZoneScopedN("glClear");
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}
glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0);
}); });
return true; return true;
@@ -1417,9 +1407,12 @@ struct Framebuffer {
glBindFramebuffer(GL_READ_FRAMEBUFFER, src.id()); glBindFramebuffer(GL_READ_FRAMEBUFFER, src.id());
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dst.id()); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dst.id());
glBlitFramebuffer(rect[0], rect[1], rect[2], rect[3], {
rect[4], rect[5], rect[6], rect[7], ZoneScopedN("glBlitFramebuffer");
GL_COLOR_BUFFER_BIT, GL_LINEAR); glBlitFramebuffer(rect[0], rect[1], rect[2], rect[3],
rect[4], rect[5], rect[6], rect[7],
GL_COLOR_BUFFER_BIT, GL_LINEAR);
}
glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0);
@@ -1446,7 +1439,8 @@ struct Framebuffer {
}; };
template <> template <>
struct ObjBase<Framebuffer>::TypeInfo final { struct ObjBase<Framebuffer>::TypeInfo final {
static inline const nf7::GenericTypeInfo<ObjBase<Framebuffer>> kType = {"GL/Framebuffer", {"nf7::DirItem"}}; static inline const nf7::GenericTypeInfo<ObjBase<Framebuffer>> kType = {
"GL/Framebuffer", {"nf7::DirItem"}, "OpenGL framebuffer"};
}; };
} }

View File

@@ -1,13 +1,21 @@
#include <atomic>
#include <chrono>
#include <memory> #include <memory>
#include <optional>
#include <utility>
#include <imgui.h> #include <imgui.h>
#include <lua.hpp> #include <lua.hpp>
#include <tracy/Tracy.hpp>
#include "nf7.hh" #include "nf7.hh"
#include "common/dir_item.hh" #include "common/dir_item.hh"
#include "common/file_base.hh"
#include "common/generic_context.hh" #include "common/generic_context.hh"
#include "common/generic_type_info.hh" #include "common/generic_type_info.hh"
#include "common/logger_ref.hh"
#include "common/luajit.hh" #include "common/luajit.hh"
#include "common/luajit_queue.hh" #include "common/luajit_queue.hh"
#include "common/ptr_selector.hh" #include "common/ptr_selector.hh"
@@ -15,35 +23,40 @@
#include "common/thread.hh" #include "common/thread.hh"
using namespace std::literals;
namespace nf7 { namespace nf7 {
namespace { namespace {
class LuaContext final : public nf7::File, public nf7::DirItem { class LuaContext final : public nf7::FileBase, public nf7::DirItem {
public: public:
static inline const nf7::GenericTypeInfo<nf7::LuaContext> kType = { static inline const nf7::GenericTypeInfo<nf7::LuaContext> kType = {
"LuaJIT/Context", {"nf7::DirItem",}}; "LuaJIT/Context", {"nf7::DirItem",},
static void UpdateTypeTooltip() noexcept { "drives LuaJIT thread and task queue"};
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; class Queue;
LuaContext(nf7::Env& env); LuaContext(nf7::Env& env, bool async = false) noexcept :
nf7::FileBase(kType, env),
nf7::DirItem(nf7::DirItem::kMenu |
nf7::DirItem::kTooltip),
log_(*this),
q_(std::make_shared<Queue>(*this, async)),
async_(async) {
}
LuaContext(nf7::Deserializer& ar) : LuaContext(ar.env()) { LuaContext(nf7::Deserializer& ar) : LuaContext(ar.env()) {
ar(async_);
} }
void Serialize(nf7::Serializer&) const noexcept override { void Serialize(nf7::Serializer& ar) const noexcept override {
ar(async_);
} }
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override { std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<LuaContext>(env); return std::make_unique<LuaContext>(env, async_);
} }
void PostHandle(const nf7::File::Event&) noexcept override;
void PostUpdate() noexcept override;
void UpdateMenu() noexcept override; void UpdateMenu() noexcept override;
void UpdateTooltip() noexcept override; void UpdateTooltip() noexcept override;
@@ -53,33 +66,57 @@ class LuaContext final : public nf7::File, public nf7::DirItem {
} }
private: private:
nf7::LoggerRef log_;
std::shared_ptr<Queue> q_; std::shared_ptr<Queue> q_;
bool async_;
}; };
class LuaContext::Queue final : public nf7::luajit::Queue, class LuaContext::Queue final : public nf7::luajit::Queue,
public std::enable_shared_from_this<LuaContext::Queue> { public std::enable_shared_from_this<LuaContext::Queue> {
public: public:
struct SharedData final {
lua_State* L;
std::atomic_flag lock;
std::optional<nf7::Env::Time> begin;
};
struct Runner final { struct Runner final {
Runner(std::weak_ptr<Queue> owner) noexcept : owner_(owner) { Runner(const std::shared_ptr<SharedData>& data) noexcept : data_(data) {
} }
void operator()(Task&& t) { void operator()(Task&& t) {
if (auto k = owner_.lock()) { auto& k = data_->lock;
t(k->L);
while (k.test_and_set());
data_->begin = nf7::Env::Clock::now();
k.clear();
{
ZoneScopedN("LuaJIT task");
t(data_->L);
}
require_gc_ = true;
while (k.test_and_set());
data_->begin = std::nullopt;
k.clear();
}
void operator()() noexcept {
if (data_->L && std::exchange(require_gc_, false)) {
ZoneScopedNC("GC", tracy::Color::Gray);
lua_gc(data_->L, LUA_GCCOLLECT, 0);
} }
} }
private: private:
std::weak_ptr<Queue> owner_; std::shared_ptr<SharedData> data_;
bool require_gc_ = false;
}; };
using Thread = nf7::Thread<Runner, Task>; 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() = delete;
Queue(LuaContext& f) : L(luaL_newstate()), env_(&f.env()) { Queue(LuaContext& f, bool async) {
auto L = luaL_newstate();
if (!L) { if (!L) {
throw nf7::Exception("failed to create new Lua state"); throw nf7::Exception("failed to create new Lua state");
} }
@@ -87,44 +124,71 @@ class LuaContext::Queue final : public nf7::luajit::Queue,
nf7::luajit::PushImmEnv(L); nf7::luajit::PushImmEnv(L);
lua_setfenv(L, -2); lua_setfenv(L, -2);
lua_pop(L, 1); lua_pop(L, 1);
data_ = std::make_shared<SharedData>();
data_->L = L;
th_ = std::make_shared<Thread>(f, Runner {data_});
SetAsync(async);
} }
~Queue() noexcept { ~Queue() noexcept {
th_->Push( th_->Push(
std::make_shared<nf7::GenericContext>(*env_, 0, "deleting lua_State"), std::make_shared<nf7::GenericContext>(th_->env(), 0, "deleting lua_State"),
[L = L](auto) { lua_close(L); } [data = data_](auto) {
); lua_close(data->L);
data->L = nullptr;
});
} }
Queue(const Queue&) = delete; Queue(const Queue&) = delete;
Queue(Queue&&) = delete; Queue(Queue&&) = delete;
Queue& operator=(const Queue&) = delete; Queue& operator=(const Queue&) = delete;
Queue& operator=(Queue&&) = delete; Queue& operator=(Queue&&) = delete;
void SetAsync(bool async) noexcept {
th_->SetExecutor(async? nf7::Env::kAsync: nf7::Env::kSub);
}
void Push(const std::shared_ptr<nf7::Context>& ctx, Task&& task, nf7::Env::Time t) noexcept override { void Push(const std::shared_ptr<nf7::Context>& ctx, Task&& task, nf7::Env::Time t) noexcept override {
th_->Push(ctx, std::move(task), t); th_->Push(ctx, std::move(task), t);
} }
std::shared_ptr<luajit::Queue> self() noexcept override { return shared_from_this(); } std::shared_ptr<luajit::Queue> self() noexcept override { return shared_from_this(); }
size_t tasksDone() const noexcept { return th_->tasksDone(); } size_t tasksDone() const noexcept {
return th_->tasksDone();
}
std::optional<nf7::Env::Time> currentTaskBegin() const noexcept {
auto& k = data_->lock;
while (k.test_and_set());
const auto ret = data_->begin;
k.clear();
return ret;
}
private: private:
lua_State* L; std::shared_ptr<Thread> th_;
Env* const env_; std::shared_ptr<SharedData> data_;
std::shared_ptr<Thread> th_;
}; };
LuaContext::LuaContext(nf7::Env& env) :
nf7::File(kType, env), void LuaContext::PostHandle(const nf7::File::Event& e) noexcept {
nf7::DirItem(nf7::DirItem::kMenu | nf7::DirItem::kTooltip), switch (e.type) {
q_(Queue::Create(*this)) { case nf7::File::Event::kAdd:
q_->SetAsync(async_);
return;
default:
return;
}
}
void LuaContext::PostUpdate() noexcept {
if (auto beg = q_->currentTaskBegin()) {
if (nf7::Env::Clock::now()-*beg > 10ms) {
log_.Warn("detected stall of LuaJIT thread, you should save and restart Nf7 immediately");
}
}
} }
void LuaContext::UpdateMenu() noexcept { void LuaContext::UpdateMenu() noexcept {
if (ImGui::MenuItem("perform a full GC cycle")) { if (ImGui::MenuItem("async", nullptr, &async_)) {
q_->Push( q_->SetAsync(async_);
std::make_shared<nf7::GenericContext>(*this, "LuaJIT garbage collection"),
[](auto L) {
lua_gc(L, LUA_GCCOLLECT, 0);
}, nf7::Env::Time {});
} }
} }
void LuaContext::UpdateTooltip() noexcept { void LuaContext::UpdateTooltip() noexcept {

View File

@@ -10,6 +10,8 @@
#include <ImNodes.h> #include <ImNodes.h>
#include <tracy/Tracy.hpp>
#include <yaml-cpp/yaml.h> #include <yaml-cpp/yaml.h>
#include <yas/serialize.hpp> #include <yas/serialize.hpp>
@@ -19,11 +21,10 @@
#include "common/dir_item.hh" #include "common/dir_item.hh"
#include "common/file_base.hh" #include "common/file_base.hh"
#include "common/generic_type_info.hh" #include "common/generic_config.hh"
#include "common/generic_memento.hh" #include "common/generic_memento.hh"
#include "common/gui_config.hh" #include "common/generic_type_info.hh"
#include "common/gui_file.hh" #include "common/gui.hh"
#include "common/gui_node.hh"
#include "common/life.hh" #include "common/life.hh"
#include "common/logger_ref.hh" #include "common/logger_ref.hh"
#include "common/luajit_nfile_importer.hh" #include "common/luajit_nfile_importer.hh"
@@ -33,7 +34,6 @@
#include "common/memento.hh" #include "common/memento.hh"
#include "common/node.hh" #include "common/node.hh"
#include "common/ptr_selector.hh" #include "common/ptr_selector.hh"
#include "common/util_algorithm.hh"
using namespace std::literals; using namespace std::literals;
@@ -42,13 +42,13 @@ using namespace std::literals;
namespace nf7 { namespace nf7 {
namespace { namespace {
class Node final : public nf7::FileBase, public nf7::DirItem, public nf7::Node { class Node final : public nf7::FileBase,
public nf7::GenericConfig, public nf7::DirItem, public nf7::Node {
public: public:
static inline const nf7::GenericTypeInfo<Node> kType = static inline const nf7::GenericTypeInfo<Node> kType = {
{"LuaJIT/Node", {"nf7::DirItem", "nf7::Node"}}; "LuaJIT/Node", {"nf7::DirItem", "nf7::Node"},
static void UpdateTypeTooltip() noexcept { "defines new pure Node without creating nfile"
ImGui::TextUnformatted("Defines new pure Node without creating nfile."); };
}
class Lambda; class Lambda;
@@ -63,15 +63,14 @@ class Node final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
}; };
Node(nf7::Env& env, Data&& data = {}) noexcept : Node(nf7::Env& env, Data&& data = {}) noexcept :
nf7::FileBase(kType, env, {}), nf7::FileBase(kType, env),
nf7::DirItem(nf7::DirItem::kMenu | nf7::DirItem::kWidget), nf7::GenericConfig(mem_),
nf7::DirItem(nf7::DirItem::kTooltip),
nf7::Node(nf7::Node::kCustomNode), nf7::Node(nf7::Node::kCustomNode),
life_(*this), life_(*this),
log_(std::make_shared<nf7::LoggerRef>(*this)), log_(std::make_shared<nf7::LoggerRef>(*this)),
mem_(std::move(data), *this), mem_(*this, std::move(data)),
importer_(std::make_shared<nf7::luajit::NFileImporter>(env.npath())) { importer_(std::make_shared<nf7::luajit::NFileImporter>(env.npath())) {
nf7::FileBase::Install(*log_);
mem_.onCommit = mem_.onRestore = [this]() { mem_.onCommit = mem_.onRestore = [this]() {
cache_ = std::nullopt; cache_ = std::nullopt;
}; };
@@ -79,8 +78,8 @@ class Node final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
Node(nf7::Deserializer& ar) : Node(ar.env()) { Node(nf7::Deserializer& ar) : Node(ar.env()) {
ar(mem_->script, mem_->inputs, mem_->outputs); ar(mem_->script, mem_->inputs, mem_->outputs);
nf7::util::Uniq(mem_->inputs); nf7::Node::ValidateSockets(mem_->inputs);
nf7::util::Uniq(mem_->outputs); nf7::Node::ValidateSockets(mem_->outputs);
} }
void Serialize(nf7::Serializer& ar) const noexcept override { void Serialize(nf7::Serializer& ar) const noexcept override {
ar(mem_->script, mem_->inputs, mem_->outputs); ar(mem_->script, mem_->inputs, mem_->outputs);
@@ -92,23 +91,19 @@ class Node final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
std::shared_ptr<nf7::Node::Lambda> CreateLambda( std::shared_ptr<nf7::Node::Lambda> CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override; const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
std::span<const std::string> GetInputs() const noexcept override { nf7::Node::Meta GetMeta() const noexcept override {
return mem_->inputs; return nf7::Node::Meta {mem_->inputs, mem_->outputs};
}
std::span<const std::string> GetOutputs() const noexcept override {
return mem_->outputs;
} }
nf7::Future<std::shared_ptr<nf7::luajit::Ref>> Build() noexcept; nf7::Future<std::shared_ptr<nf7::luajit::Ref>> Build() noexcept;
void Update() noexcept override; void PostUpdate() noexcept override;
void UpdateMenu() noexcept override; void UpdateTooltip() noexcept override;
void UpdateNode(nf7::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 { File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector< return nf7::InterfaceSelector<
nf7::DirItem, nf7::Memento, nf7::Node>(t).Select(this, &mem_); nf7::Config, nf7::DirItem, nf7::Memento, nf7::Node>(t).Select(this, &mem_);
} }
private: private:
@@ -122,6 +117,8 @@ class Node final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
std::filesystem::file_time_type last_build_ = {}; std::filesystem::file_time_type last_build_ = {};
std::shared_ptr<nf7::luajit::NFileImporter> importer_; std::shared_ptr<nf7::luajit::NFileImporter> importer_;
nf7::ContextOwner la_owner_;
}; };
@@ -129,7 +126,9 @@ class Node::Lambda final : public nf7::Node::Lambda,
public std::enable_shared_from_this<Node::Lambda> { public std::enable_shared_from_this<Node::Lambda> {
public: public:
Lambda(Node& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept : Lambda(Node& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept :
nf7::Node::Lambda(f, parent), f_(f.life_), log_(f.log_) { nf7::Node::Lambda(f, parent),
f_(f.life_), log_(f.log_),
table_ctx_(std::make_shared<nf7::GenericContext>(f, "LuaJIT Node context table")) {
} }
void Handle(const nf7::Node::Lambda::Msg& in) noexcept override void Handle(const nf7::Node::Lambda::Msg& in) noexcept override
@@ -140,20 +139,24 @@ class Node::Lambda final : public nf7::Node::Lambda,
f_->Build(). f_->Build().
ThenIf(self, [this, in](auto& func) mutable { ThenIf(self, [this, in](auto& func) mutable {
if (f_) StartThread(in, func); if (f_) StartThread(in, func);
}).
Catch<nf7::Exception>([log = log_](auto&) {
log->Warn("skips execution because of build failure");
}); });
} catch (nf7::ExpiredException&) { } catch (nf7::ExpiredException&) {
} }
void Abort() noexcept override {
th_owner_.AbortAll();
}
private: private:
nf7::Life<Node>::Ref f_; nf7::Life<Node>::Ref f_;
std::shared_ptr<nf7::LoggerRef> log_; std::shared_ptr<nf7::LoggerRef> log_;
std::mutex mtx_; std::mutex mtx_;
std::optional<nf7::luajit::Ref> ctx_; std::shared_ptr<nf7::Context> table_ctx_;
std::optional<nf7::luajit::Ref> table_;
nf7::ContextOwner th_owner_;
void StartThread(const nf7::Node::Lambda::Msg& in, void StartThread(const nf7::Node::Lambda::Msg& in,
@@ -162,35 +165,36 @@ class Node::Lambda final : public nf7::Node::Lambda,
auto self = shared_from_this(); auto self = shared_from_this();
auto hndl = nf7::luajit::Thread::CreateNodeLambdaHandler(in.sender, self); auto hndl = nf7::luajit::Thread::CreateNodeLambdaHandler(in.sender, self);
auto th = std::make_shared<nf7::luajit::Thread>(self, ljq, std::move(hndl)); auto th = th_owner_.Create<nf7::luajit::Thread>(self, ljq, std::move(hndl));
th->Install(log_); th->Install(log_);
th->Install(f_->importer_);
ljq->Push(self, [this, ljq, th, func, in](auto L) mutable { ljq->Push(self, [this, ljq, th, func, in](auto L) mutable {
{ {
std::unique_lock<std::mutex> k {mtx_}; std::unique_lock<std::mutex> k {mtx_};
if (!ctx_ || ctx_->ljq() != ljq) { if (!table_ || table_->ljq() != ljq) {
lua_createtable(L, 0, 0); lua_createtable(L, 0, 0);
ctx_.emplace(shared_from_this(), ljq, L); table_.emplace(table_ctx_, ljq, L);
} }
} }
L = th->Init(L); L = th->Init(L);
func->PushSelf(L); func->PushSelf(L);
nf7::luajit::PushAll(L, in.name, in.value); nf7::luajit::PushAll(L, in.name, in.value);
ctx_->PushSelf(L); table_->PushSelf(L);
th->Resume(L, 3); th->Resume(L, 3);
}); });
} }
}; };
std::shared_ptr<nf7::Node::Lambda> Node::CreateLambda( std::shared_ptr<nf7::Node::Lambda> Node::CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept { const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
return std::make_shared<Lambda>(*this, parent); return la_owner_.Create<Lambda>(*this, parent);
} }
nf7::Future<std::shared_ptr<nf7::luajit::Ref>> Node::Build() noexcept nf7::Future<std::shared_ptr<nf7::luajit::Ref>> Node::Build() noexcept
try { try {
if (cache_) return *cache_; if (cache_ && !cache_->error()) {
return *cache_;
}
last_build_ = std::chrono::file_clock::now(); last_build_ = std::chrono::file_clock::now();
nf7::Future<std::shared_ptr<nf7::luajit::Ref>>::Promise pro; nf7::Future<std::shared_ptr<nf7::luajit::Ref>>::Promise pro;
@@ -214,6 +218,7 @@ try {
// start the thread // start the thread
ljq->Push(ctx, [ctx, ljq, th, pro, script = mem_->script](auto L) mutable { ljq->Push(ctx, [ctx, ljq, th, pro, script = mem_->script](auto L) mutable {
ZoneScopedN("build function for Node");
L = th->Init(L); L = th->Init(L);
if (0 == luaL_loadstring(L, script.c_str())) { if (0 == luaL_loadstring(L, script.c_str())) {
th->Resume(L, 0); th->Resume(L, 0);
@@ -232,20 +237,24 @@ try {
} }
void Node::Update() noexcept { void Node::PostUpdate() noexcept {
nf7::FileBase::Update(); if (cache_ && cache_->done()) {
if (last_build_ < importer_->GetLatestMod()) {
if (last_build_ < importer_->GetLatestMod()) { cache_ = std::nullopt;
cache_ = std::nullopt; }
} }
} }
void Node::UpdateMenu() noexcept { void Node::UpdateTooltip() noexcept {
if (ImGui::BeginMenu("config")) { const char* state = "unused";
nf7::gui::Config(mem_); if (cache_) {
ImGui::EndMenu(); state =
cache_->done()? "ready":
cache_->yet()? "building": "broken";
} }
ImGui::Text("state: %s", state);
} }
void Node::UpdateNode(nf7::Node::Editor&) noexcept { void Node::UpdateNode(nf7::Node::Editor&) noexcept {
const auto em = ImGui::GetFontSize(); const auto em = ImGui::GetFontSize();
@@ -255,16 +264,10 @@ void Node::UpdateNode(nf7::Node::Editor&) noexcept {
ImGui::OpenPopup("ConfigPopup"); ImGui::OpenPopup("ConfigPopup");
} }
if (ImGui::BeginPopup("ConfigPopup")) { if (ImGui::BeginPopup("ConfigPopup")) {
nf7::gui::Config(mem_); static nf7::gui::ConfigEditor ed;
ed(*this);
ImGui::EndPopup(); ImGui::EndPopup();
} }
ImGui::SameLine();
if (ImGui::SmallButton("build")) {
Build();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("try to compile the script (for syntax check)");
}
nf7::gui::NodeInputSockets(mem_->inputs); nf7::gui::NodeInputSockets(mem_->inputs);
ImGui::SameLine(); ImGui::SameLine();
@@ -275,9 +278,6 @@ void Node::UpdateNode(nf7::Node::Editor&) noexcept {
ImGui::SameLine(); ImGui::SameLine();
nf7::gui::NodeOutputSockets(mem_->outputs); nf7::gui::NodeOutputSockets(mem_->outputs);
} }
void Node::UpdateWidget() noexcept {
nf7::gui::Config(mem_);
}
std::string Node::Data::Stringify() const noexcept { std::string Node::Data::Stringify() const noexcept {
@@ -295,20 +295,16 @@ std::string Node::Data::Stringify() const noexcept {
void Node::Data::Parse(const std::string& str) void Node::Data::Parse(const std::string& str)
try { try {
const auto yaml = YAML::Load(str); 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) { Data d;
throw nf7::Exception {"duplicated inputs"}; d.inputs = yaml["inputs"].as<std::vector<std::string>>();
} d.outputs = yaml["outputs"].as<std::vector<std::string>>();
if (nf7::util::Uniq(new_outputs) > 0) { d.script = yaml["script"].as<std::string>();
throw nf7::Exception {"duplicated outputs"};
}
inputs = std::move(new_inputs); nf7::Node::ValidateSockets(d.inputs);
outputs = std::move(new_outputs); nf7::Node::ValidateSockets(d.outputs);
script = std::move(new_script);
*this = std::move(d);
} catch (YAML::Exception& e) { } catch (YAML::Exception& e) {
throw nf7::Exception {e.what()}; throw nf7::Exception {e.what()};
} }

459
file/node_exprtk.cc Normal file
View File

@@ -0,0 +1,459 @@
#include <algorithm>
#include <memory>
#include <optional>
#include <span>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include <exprtk.hpp>
#include <imgui.h>
#include <imgui_stdlib.h>
#include <ImNodes.h>
#include <tracy/Tracy.hpp>
#include <yaml-cpp/yaml.h>
#include <yas/serialize.hpp>
#include <yas/types/std/string.hpp>
#include <yas/types/std/vector.hpp>
#include <yas/types/utility/usertype.hpp>
#include "nf7.hh"
#include "common/dir_item.hh"
#include "common/file_base.hh"
#include "common/generic_config.hh"
#include "common/generic_memento.hh"
#include "common/generic_type_info.hh"
#include "common/gui.hh"
#include "common/life.hh"
#include "common/logger_ref.hh"
#include "common/memento.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
#include "common/value.hh"
#include "common/yas_enum.hh"
namespace nf7 {
namespace {
class ExprTk final : public nf7::FileBase,
public nf7::GenericConfig, public nf7::DirItem, public nf7::Node {
public:
static inline const nf7::GenericTypeInfo<ExprTk> kType = {
"Node/ExprTk", {"nf7::DirItem", "nf7::Node"},
"defines new pure Node using ExprTk"
};
class Lambda;
using Scalar = double;
struct Data {
std::vector<std::string> inputs = {"x"};
std::vector<std::string> outputs = {"out"};
std::string script = "";
Data() noexcept { }
void serialize(auto& ar) {
ar(inputs, outputs, script);
}
std::string Stringify() const noexcept;
void Parse(const std::string&);
void Sanitize() const;
};
ExprTk(nf7::Env& env, Data&& data = {}) noexcept :
nf7::FileBase(kType, env),
nf7::GenericConfig(mem_),
nf7::DirItem(nf7::DirItem::kNone),
nf7::Node(nf7::Node::kCustomNode),
life_(*this), log_(*this), mem_(*this, std::move(data)) {
}
ExprTk(nf7::Deserializer& ar) : ExprTk(ar.env()) {
ar(mem_.data());
mem_->Sanitize();
}
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<ExprTk>(env, Data {mem_.data()});
}
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
nf7::Node::Meta GetMeta() const noexcept override {
return {mem_->inputs, mem_->outputs};
}
void UpdateNode(nf7::Node::Editor&) noexcept override;
File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector<
nf7::Config, nf7::DirItem, nf7::Memento, nf7::Node>(t).Select(this, &mem_);
}
private:
nf7::Life<ExprTk> life_;
nf7::LoggerRef log_;
nf7::GenericMemento<Data> mem_;
};
class ExprTk::Lambda final : public nf7::Node::Lambda,
public std::enable_shared_from_this<ExprTk::Lambda> {
public:
Lambda(ExprTk& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept :
nf7::Node::Lambda(f, parent), f_(f.life_),
load_func_(mem_), store_func_(mem_) {
}
void Handle(const nf7::Node::Lambda::Msg& in) noexcept override
try {
f_.EnforceAlive();
RecordInput(in);
if (!Satisfy()) return;
const auto ptag = std::exchange(tag_, f_->mem_.Save());
if (!expr_ || tag_ != ptag) {
Build();
}
assert(sym_);
assert(expr_);
AssignInputs();
{
ZoneScopedN("ExprTk calc");
yield_func_.SetUp(in.sender, shared_from_this());
expr_->value();
}
inputs_.clear();
} catch (nf7::ExpiredException&) {
} catch (nf7::Exception& e) {
f_->log_.Error(e);
}
private:
nf7::Life<ExprTk>::Ref f_;
std::shared_ptr<nf7::Memento::Tag> tag_;
std::vector<std::pair<std::string, nf7::Value>> inputs_;
using Var = std::variant<Scalar, std::string, std::vector<Scalar>>;
std::vector<std::pair<std::string, Var>> vars_;
std::vector<Scalar> mem_;
std::optional<exprtk::symbol_table<Scalar>> sym_;
std::optional<exprtk::expression<Scalar>> expr_;
void RecordInput(const nf7::Node::Lambda::Msg& in) noexcept {
auto itr = std::find_if(inputs_.begin(), inputs_.end(),
[&](auto& x) { return x.first == in.name; });
if (itr != inputs_.end()) {
itr->second = in.value;
} else {
inputs_.emplace_back(in.name, in.value);
}
}
bool Satisfy() noexcept {
for (const auto& name : f_->mem_->inputs) {
auto itr = std::find_if(inputs_.begin(), inputs_.end(),
[&](auto& x) { return x.first == name; });
if (itr == inputs_.end()) {
return false;
}
}
return true;
}
void Build() {
AllocateVars();
sym_.emplace();
expr_.emplace();
sym_->add_function("yield", yield_func_);
sym_->add_function("load", load_func_);
sym_->add_function("store", store_func_);
for (auto& var : vars_) {
std::visit(Register {*sym_, var.first}, var.second);
}
expr_->register_symbol_table(*sym_);
ZoneScopedN("ExprTk compile");
exprtk::parser<Scalar> p;
if (!p.compile(f_->mem_->script, *expr_)) {
throw nf7::Exception {p.error()};
}
}
void AllocateVars() {
const auto& inputs = f_->mem_->inputs;
vars_.clear();
vars_.reserve(inputs.size());
for (const auto& name : f_->mem_->inputs) {
auto itr = std::find_if(
inputs_.begin(), inputs_.end(), [&](auto& x) { return x.first == name; });
assert(itr != inputs_.end());
const auto& v = itr->second;
if (v.isTuple()) {
const auto n = v.tuple()->size();
if (n == 0) {
throw nf7::Exception {"got an empty tuple: "+name};
}
vars_.emplace_back(name, std::vector<Scalar>(n));
} else if (v.isString()) {
vars_.emplace_back(name, std::string {});
} else {
vars_.emplace_back(name, Scalar {0});
}
}
}
void AssignInputs() {
for (auto& var : vars_) {
auto itr = std::find_if(inputs_.begin(), inputs_.end(),
[&](auto& x) { return x.first == var.first; });
assert(itr != inputs_.end());
std::visit(Cast {}, var.second, itr->second.value());
}
}
struct Register final {
public:
Register(exprtk::symbol_table<Scalar>& sym, const std::string& name) noexcept :
sym_(sym), name_(name) {
}
void operator()(Scalar& y) noexcept {
sym_.add_variable(name_, y);
}
void operator()(std::string& y) noexcept {
sym_.add_stringvar(name_, y);
}
void operator()(std::vector<Scalar>& y) noexcept {
sym_.add_vector(name_, y);
}
private:
exprtk::symbol_table<Scalar>& sym_;
const std::string& name_;
};
struct Cast final {
public:
void operator()(Scalar& y, const nf7::Value::Pulse&) noexcept {
y = 0;
}
void operator()(Scalar& y, const nf7::Value::Scalar& x) noexcept {
y = x;
}
void operator()(Scalar& y, const nf7::Value::Integer& x) noexcept {
y = static_cast<Scalar>(x);
}
void operator()(Scalar& y, const nf7::Value::Boolean& x) noexcept {
y = x? Scalar {1}: Scalar {0};
}
void operator()(std::string& y, const nf7::Value::String& x) noexcept {
y = x;
}
void operator()(std::vector<Scalar>& y, const nf7::Value::ConstTuple& x) {
const auto& tup = *x;
const auto n = std::min(y.size(), tup.size());
for (size_t i = 0; i < n; ++i) {
y[i] = tup[i].second.scalarOrInteger<Scalar>();
}
std::fill(y.begin()+static_cast<intmax_t>(n), y.end(), Scalar {0});
}
void operator()(auto&, auto&) {
throw nf7::Exception {"unsupported input value type"};
}
};
struct YieldFunction final : exprtk::igeneric_function<Scalar> {
public:
YieldFunction() noexcept : exprtk::igeneric_function<Scalar>("S|ST|SS|SV") {
}
void SetUp(const std::shared_ptr<nf7::Node::Lambda>& callee,
const std::shared_ptr<nf7::Node::Lambda>& caller) noexcept {
callee_ = callee;
caller_ = caller;
}
Scalar operator()(const std::size_t& idx, parameter_list_t params) {
nf7::Value ret;
switch (idx) {
case 0: // pulse
ret = nf7::Value::Pulse {};
break;
case 1: // scalar
ret = {static_cast<nf7::Value::Scalar>(generic_type::scalar_view {params[1]}())};
break;
case 2: { // string
generic_type::string_view v {params[1]};
ret = {std::string {v.begin(), v.size()}};
} break;
case 3: { // vector
generic_type::vector_view v {params[1]};
std::vector<nf7::Value::TuplePair> pairs;
pairs.resize(v.size());
for (size_t i = 0; i < v.size(); ++i) {
pairs[i].second = nf7::Value {static_cast<nf7::Value::Scalar>(v[i])};
}
ret = {std::move(pairs)};
} break;
default:
assert(false);
break;
}
generic_type::string_view n {params[0]};
const std::string name {n.begin(), n.size()};
auto callee = callee_.lock();
auto caller = caller_.lock();
if (callee && caller) {
callee->env().ExecSub(callee, [=, *this]() {
callee->Handle(name, ret, caller);
});
}
return 0;
}
private:
std::weak_ptr<nf7::Node::Lambda> callee_, caller_;
};
YieldFunction yield_func_;
struct LoadFunction final : exprtk::ifunction<Scalar> {
public:
LoadFunction(const std::vector<Scalar>& mem) noexcept :
exprtk::ifunction<Scalar>(1), mem_(mem) {
}
Scalar operator()(const Scalar& addr_f) {
const auto addr = static_cast<uint64_t>(addr_f);
return addr < mem_.size()? mem_[addr]: Scalar {0};
}
private:
const std::vector<Scalar>& mem_;
};
LoadFunction load_func_;
struct StoreFunction final : exprtk::ifunction<Scalar> {
public:
StoreFunction(std::vector<Scalar>& mem) noexcept :
exprtk::ifunction<Scalar>(2), mem_(mem) {
}
Scalar operator()(const Scalar& addr_f, const Scalar& v) {
if (addr_f < 0) {
throw nf7::Exception {"negative address"};
}
const auto addr = static_cast<uint64_t>(addr_f);
if (addr >= 1024) {
throw nf7::Exception {"out of memory (max 1024)"};
}
if (addr >= mem_.size()) {
mem_.resize(addr+1);
}
return mem_[addr] = v;
}
private:
std::vector<Scalar>& mem_;
};
StoreFunction store_func_;
};
std::shared_ptr<nf7::Node::Lambda> ExprTk::CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
return std::make_shared<Lambda>(*this, parent);
}
void ExprTk::UpdateNode(nf7::Node::Editor&) noexcept {
const auto em = ImGui::GetFontSize();
ImGui::TextUnformatted("Node/ExprTk");
ImGui::SameLine();
if (ImGui::SmallButton("config")) {
ImGui::OpenPopup("ConfigPopup");
}
if (ImGui::BeginPopup("ConfigPopup")) {
static gui::ConfigEditor ed;
ed(*this);
ImGui::EndPopup();
}
ImGui::BeginGroup();
for (const auto& in : mem_->inputs) {
if (ImNodes::BeginInputSlot(in.c_str(), 1)) {
ImGui::AlignTextToFramePadding();
gui::NodeSocket();
ImGui::SameLine();
ImGui::TextUnformatted(in.c_str());
ImNodes::EndSlot();
}
}
ImGui::EndGroup();
ImGui::SameLine();
ImGui::InputTextMultiline("##script", &mem_->script, ImVec2 {24*em, 8*em});
if (ImGui::IsItemDeactivatedAfterEdit()) {
mem_.Commit();
}
ImGui::SameLine();
gui::NodeOutputSockets(mem_->outputs);
}
std::string ExprTk::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 ExprTk::Data::Parse(const std::string& str) {
const auto yaml = YAML::Load(str);
Data d;
d.inputs = yaml["inputs"].as<std::vector<std::string>>();
d.outputs = yaml["outputs"].as<std::vector<std::string>>();
d.script = yaml["script"].as<std::string>();
d.Sanitize();
*this = std::move(d);
}
void ExprTk::Data::Sanitize() const {
nf7::Node::ValidateSockets(inputs);
nf7::Node::ValidateSockets(outputs);
}
}
} // namespace nf7

View File

@@ -1,151 +0,0 @@
#include <algorithm>
#include <memory>
#include <string>
#include <string_view>
#include <typeinfo>
#include <utility>
#include <vector>
#include <iostream>
#include <imgui.h>
#include <imgui_stdlib.h>
#include <ImNodes.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_memento.hh"
#include "common/generic_type_info.hh"
#include "common/gui_node.hh"
#include "common/gui_value.hh"
#include "common/life.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
#include "common/value.hh"
namespace nf7 {
namespace {
class Imm final : public nf7::File, public nf7::DirItem, public nf7::Node {
public:
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;
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(nf7::Deserializer& ar) : Imm(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<Imm>(env, nf7::gui::Value {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 = {"in"};
return kInputs;
}
std::span<const std::string> GetOutputs() const noexcept override {
static const std::vector<std::string> kOutputs = {"out"};
return kOutputs;
}
void UpdateNode(nf7::Node::Editor&) noexcept override;
void UpdateWidget() 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:
nf7::Life<Imm> life_;
nf7::GenericMemento<nf7::gui::Value> mem_;
};
class Imm::Lambda final : public nf7::Node::Lambda,
public std::enable_shared_from_this<Imm::Lambda> {
public:
Lambda(Imm& f, const std::shared_ptr<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;
if (in.name == "in") {
in.sender->Handle("out", f_->mem_.data().entity(), shared_from_this());
return;
}
}
private:
nf7::Life<Imm>::Ref f_;
};
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(nf7::Node::Editor&) noexcept {
const auto em = ImGui::GetFontSize();
bool mod = false;
ImGui::TextUnformatted("Node/Imm");
ImGui::SameLine();
mod |= mem_.data().UpdateTypeButton(nullptr, true);
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 (mod) {
mem_.Commit();
}
}
void Imm::UpdateWidget() noexcept {
ImGui::TextUnformatted("Node/Imm");
if (mem_.data().UpdateEditor()) {
mem_.Commit();
}
}
}
} // namespace nf7

122
file/node_mutex.cc Normal file
View File

@@ -0,0 +1,122 @@
#include <memory>
#include <typeinfo>
#include <utility>
#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/life.hh"
#include "common/logger_ref.hh"
#include "common/mutex.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
namespace nf7 {
namespace {
class MutexNode final : public nf7::FileBase,
public nf7::DirItem, public nf7::Node {
public:
static inline const nf7::GenericTypeInfo<MutexNode> kType = {
"Node/Mutex", {"nf7::DirItem",},
"mutual exclusion",
};
MutexNode(nf7::Env& env) noexcept :
nf7::FileBase(kType, env),
nf7::DirItem(nf7::DirItem::kTooltip),
nf7::Node(nf7::Node::kNone),
life_(*this),
log_(std::make_shared<nf7::LoggerRef>(*this)) {
}
MutexNode(nf7::Deserializer& ar) : MutexNode(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<MutexNode>(env);
}
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept override {
return std::make_shared<Lambda>(*this, parent);
}
nf7::Node::Meta GetMeta() const noexcept override {
return {{"lock", "exlock", "unlock"}, {"acquired", "failed"}};
}
void UpdateTooltip() noexcept;
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector<
nf7::DirItem, nf7::Node>(t).Select(this);
}
private:
nf7::Life<MutexNode> life_;
nf7::Mutex mtx_;
std::shared_ptr<nf7::LoggerRef> log_;
class Lambda final : public nf7::Node::Lambda,
public std::enable_shared_from_this<Lambda> {
public:
Lambda(MutexNode& 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;
if (in.name == "lock") {
Lock(in.sender, false);
} else if (in.name == "exlock") {
Lock(in.sender, true);
} else if (in.name == "unlock") {
lock_ = nullptr;
}
}
void Lock(const std::shared_ptr<nf7::Node::Lambda>& sender, bool ex) noexcept {
auto self = shared_from_this();
auto log = f_->log_;
if (lock_ || std::exchange(working_, true)) {
log->Warn("race condition detected (lock is already acquired or requested)");
return;
}
auto ctx = std::make_shared<nf7::GenericContext>(*f_, "mutex lock", self);
f_->mtx_.
AcquireLock(ctx, ex).
ThenIf([=](auto& k) {
self->lock_ = k;
self->working_ = false;
sender->Handle("acquired", nf7::Value::Pulse {}, self);
}).
Catch<nf7::Exception>([=](auto&) {
self->working_ = false;
log->Warn("failed to lock lambda");
sender->Handle("failed", nf7::Value::Pulse {}, self);
});
}
private:
nf7::Life<MutexNode>::Ref f_;
bool working_ = false;
std::shared_ptr<nf7::Mutex::Lock> lock_;
};
};
void MutexNode::UpdateTooltip() noexcept {
ImGui::Text("status : %s", mtx_.status());
ImGui::Text("pendings: %zu", mtx_.pendings());
}
}
} // namespace nf7

File diff suppressed because it is too large Load Diff

View File

@@ -21,9 +21,8 @@
#include "common/generic_memento.hh" #include "common/generic_memento.hh"
#include "common/generic_type_info.hh" #include "common/generic_type_info.hh"
#include "common/generic_watcher.hh" #include "common/generic_watcher.hh"
#include "common/gui.hh"
#include "common/gui_dnd.hh" #include "common/gui_dnd.hh"
#include "common/gui_node.hh"
#include "common/gui_popup.hh"
#include "common/life.hh" #include "common/life.hh"
#include "common/logger_ref.hh" #include "common/logger_ref.hh"
#include "common/memento.hh" #include "common/memento.hh"
@@ -37,16 +36,7 @@ namespace {
class Ref final : public nf7::FileBase, public nf7::Node { class Ref final : public nf7::FileBase, public nf7::Node {
public: public:
static inline const nf7::GenericTypeInfo<Ref> kType = { static inline const nf7::GenericTypeInfo<Ref> kType = {
"Node/Ref", {"nf7::Node"}}; "Node/Ref", {"nf7::Node"}, "refers other 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; class Lambda;
@@ -58,47 +48,30 @@ class Ref final : public nf7::FileBase, public nf7::Node {
}; };
Ref(nf7::Env& env, Data&& data = {}) noexcept : Ref(nf7::Env& env, Data&& data = {}) noexcept :
nf7::FileBase(kType, env, {&config_popup_}), nf7::FileBase(kType, env),
nf7::Node(nf7::Node::kCustomNode | nf7::Node::kMenu), nf7::Node(nf7::Node::kCustomNode | nf7::Node::kMenu),
life_(*this), life_(*this),
log_(std::make_shared<nf7::LoggerRef>(*this)), log_(std::make_shared<nf7::LoggerRef>(*this)),
mem_(std::move(data), *this), mem_(*this, std::move(data)) {
config_popup_(*this) { mem_.onRestore = mem_.onCommit = [this]() {
nf7::FileBase::Install(*log_); SetUpWatcher();
};
mem_.onRestore = mem_.onCommit = [this]() { SetUpWatcher(); };
} }
Ref(nf7::Deserializer& ar) : Ref(ar.env()) { Ref(nf7::Deserializer& ar) : Ref(ar.env()) {
ar(data().target, data().inputs, data().outputs); ar(mem_->target, mem_->inputs, mem_->outputs);
} }
void Serialize(nf7::Serializer& ar) const noexcept override { void Serialize(nf7::Serializer& ar) const noexcept override {
ar(data().target, data().inputs, data().outputs); ar(mem_->target, mem_->inputs, mem_->outputs);
} }
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override { std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<Ref>(env, Data {data()}); return std::make_unique<Ref>(env, Data {mem_.data()});
} }
std::shared_ptr<nf7::Node::Lambda> CreateLambda( std::shared_ptr<nf7::Node::Lambda> CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override; const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
std::span<const std::string> GetInputs() const noexcept override { nf7::Node::Meta GetMeta() const noexcept override {
return data().inputs; return {mem_->inputs, mem_->outputs};
}
std::span<const std::string> GetOutputs() const noexcept override {
return data().outputs;
}
void Handle(const nf7::File::Event& ev) noexcept {
nf7::FileBase::Handle(ev);
switch (ev.type) {
case nf7::File::Event::kAdd:
env().ExecMain(std::make_shared<nf7::GenericContext>(*this),
std::bind(&Ref::SetUpWatcher, this));
break;
default:
break;
}
} }
void UpdateNode(nf7::Node::Editor&) noexcept override; void UpdateNode(nf7::Node::Editor&) noexcept override;
@@ -116,48 +89,30 @@ class Ref final : public nf7::FileBase, public nf7::Node {
std::optional<nf7::GenericWatcher> watcher_; std::optional<nf7::GenericWatcher> watcher_;
nf7::GenericMemento<Data> mem_; 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:
ConfigPopup(Ref& f) noexcept : nf7::gui::Popup("ConfigPopup"), f_(&f) {
}
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_;
// accessors // accessors
nf7::File& target() const { nf7::File& target() const {
auto& f = ResolveOrThrow(data().target); auto& f = ResolveOrThrow(mem_->target);
if (&f == this) throw nf7::Exception("self reference"); if (&f == this) throw nf7::Exception("self reference");
return f; return f;
} }
// socket synchronization // socket synchronization
bool SyncQuiet() noexcept { bool SyncQuiet() noexcept {
auto& dsti = data().inputs; auto& dsti = mem_->inputs;
auto& dsto = data().outputs; auto& dsto = mem_->outputs;
bool mod = false; bool mod = false;
try { try {
auto& n = target().interfaceOrThrow<nf7::Node>(); auto& n = target().interfaceOrThrow<nf7::Node>();
const auto meta = n.GetMeta();
const auto srci = n.GetInputs(); const auto& srci = meta.inputs;
mod |= std::equal(dsti.begin(), dsti.end(), srci.begin(), srci.end()); mod |= std::equal(dsti.begin(), dsti.end(), srci.begin(), srci.end());
dsti = std::vector<std::string>{srci.begin(), srci.end()}; dsti = std::vector<std::string>{srci.begin(), srci.end()};
const auto srco = n.GetOutputs(); const auto& srco = meta.outputs;
mod |= std::equal(dsto.begin(), dsto.end(), srco.begin(), srco.end()); mod |= std::equal(dsto.begin(), dsto.end(), srco.begin(), srco.end());
dsto = std::vector<std::string>{srco.begin(), srco.end()}; dsto = std::vector<std::string>{srco.begin(), srco.end()};
} catch (nf7::Exception& e) { } catch (nf7::Exception& e) {
@@ -181,7 +136,7 @@ class Ref final : public nf7::FileBase, public nf7::Node {
// referencee operation // referencee operation
void ExecChangeTarget(Path&& p) noexcept { void ExecChangeTarget(Path&& p) noexcept {
auto& target = mem_.data().target; auto& target = mem_->target;
if (p == target) return; if (p == target) return;
env().ExecMain( env().ExecMain(
@@ -224,28 +179,41 @@ class Ref::Lambda final : public Node::Lambda,
auto parent = this->parent(); auto parent = this->parent();
if (!parent) return; if (!parent) return;
if (in.sender == base_) { // check if target file is changed
parent->Handle(in.name, in.value, shared_from_this()); auto& target = f_->target();
} if (target.id() != target_id_) {
if (in.sender == parent) { if (depth() > kMaxDepth) {
if (!base_) { throw nf7::Exception {"stack overflow"};
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()); target_id_ = target.id();
target_ = target.
interfaceOrThrow<nf7::Node>().
CreateLambda(shared_from_this());
} }
} catch (nf7::Exception& e) {
log_->Error("failed to call referencee: "+e.msg()); // output from the target
if (in.sender == target_) {
parent->Handle(in.name, in.value, shared_from_this());
return;
}
// input from the parent
if (in.sender == parent) {
target_->Handle(in.name, in.value, shared_from_this());
return;
}
// ignore everything from others
} catch (nf7::Exception&) {
log_->Error("failed to call referencee");
Abort();
} }
void Abort() noexcept override { void Abort() noexcept override {
if (base_) { if (target_) {
base_->Abort(); target_->Abort();
target_ = nullptr;
} }
} }
@@ -254,7 +222,8 @@ class Ref::Lambda final : public Node::Lambda,
std::shared_ptr<nf7::LoggerRef> log_; std::shared_ptr<nf7::LoggerRef> log_;
std::shared_ptr<Node::Lambda> base_; nf7::File::Id target_id_ = 0;
std::shared_ptr<nf7::Node::Lambda> target_;
}; };
std::shared_ptr<Node::Lambda> Ref::CreateLambda( std::shared_ptr<Node::Lambda> Ref::CreateLambda(
@@ -277,26 +246,23 @@ void Ref::UpdateNode(Node::Editor&) noexcept {
ExecSync(); ExecSync();
} }
const auto pathstr = mem_.data().target.Stringify();
auto w = 6*em; auto w = 6*em;
{ {
auto pw = ImGui::CalcTextSize(pathstr.c_str()).x+style.FramePadding.x*2;
w = std::max(w, std::min(pw, 8*em));
auto iw = 3*em; auto iw = 3*em;
for (const auto& v : data().inputs) { for (const auto& v : mem_->inputs) {
iw = std::max(iw, ImGui::CalcTextSize(v.c_str()).x); iw = std::max(iw, ImGui::CalcTextSize(v.c_str()).x);
} }
auto ow = 3*em; auto ow = 3*em;
for (const auto& v : data().outputs) { for (const auto& v : mem_->outputs) {
ow = std::max(ow, ImGui::CalcTextSize(v.c_str()).x); 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); 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})) { auto newpath = mem_->target;
config_popup_.Open(); ImGui::SetNextItemWidth(w);
if (nf7::gui::PathButton("##target", newpath, *this)) {
ExecChangeTarget(std::move(newpath));
} }
if (ImGui::BeginDragDropTarget()) { if (ImGui::BeginDragDropTarget()) {
if (auto p = gui::dnd::Accept<Path>(gui::dnd::kFilePath)) { if (auto p = gui::dnd::Accept<Path>(gui::dnd::kFilePath)) {
@@ -307,7 +273,7 @@ void Ref::UpdateNode(Node::Editor&) noexcept {
const auto right = ImGui::GetCursorPosX() + w; const auto right = ImGui::GetCursorPosX() + w;
ImGui::BeginGroup(); ImGui::BeginGroup();
for (const auto& name : data().inputs) { for (const auto& name : mem_->inputs) {
if (ImNodes::BeginInputSlot(name.c_str(), 1)) { if (ImNodes::BeginInputSlot(name.c_str(), 1)) {
gui::NodeSocket(); gui::NodeSocket();
ImGui::SameLine(); ImGui::SameLine();
@@ -318,7 +284,7 @@ void Ref::UpdateNode(Node::Editor&) noexcept {
ImGui::EndGroup(); ImGui::EndGroup();
ImGui::SameLine(); ImGui::SameLine();
ImGui::BeginGroup(); ImGui::BeginGroup();
for (const auto& name : data().outputs) { for (const auto& name : mem_->outputs) {
const auto tw = ImGui::CalcTextSize(name.c_str()).x; const auto tw = ImGui::CalcTextSize(name.c_str()).x;
ImGui::SetCursorPosX(right-(tw+style.ItemSpacing.x+em)); ImGui::SetCursorPosX(right-(tw+style.ItemSpacing.x+em));
@@ -330,31 +296,18 @@ void Ref::UpdateNode(Node::Editor&) noexcept {
} }
} }
ImGui::EndGroup(); ImGui::EndGroup();
config_popup_.Update();
} }
void Ref::UpdateMenu(nf7::Node::Editor& ed) noexcept { void Ref::UpdateMenu(nf7::Node::Editor& ed) noexcept {
if (ImGui::MenuItem("sync")) { if (ImGui::MenuItem("sync")) {
ExecSync(); ExecSync();
} }
if (ImGui::MenuItem("replace target")) {
config_popup_.Open();
}
try { try {
auto& f = target(); auto& f = target();
auto& n = f.interfaceOrThrow<nf7::Node>(); auto& n = f.interfaceOrThrow<nf7::Node>();
auto d = f.interface<nf7::DirItem>();
const bool dmenu = n.flags() & nf7::Node::kMenu_DirItem; if (ImGui::BeginMenu("target")) {
const bool menu = n.flags() & nf7::Node::kMenu; nf7::gui::FileMenuItems(f);
if (n.flags() & nf7::Node::kMenu) {
if ((dmenu || menu) && ImGui::BeginMenu("target")) {
if (dmenu) {
assert(d);
ImGui::Separator();
d->UpdateMenu();
}
if (menu) {
ImGui::Separator(); ImGui::Separator();
n.UpdateMenu(ed); n.UpdateMenu(ed);
} }
@@ -364,36 +317,5 @@ void Ref::UpdateMenu(nf7::Node::Editor& ed) noexcept {
} }
} }
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();
}
}
} }
} // namespace nf7 } // namespace nf7

235
file/node_singleton.cc Normal file
View File

@@ -0,0 +1,235 @@
#include <algorithm>
#include <cassert>
#include <memory>
#include <vector>
#include <yaml-cpp/yaml.h>
#include <yas/serialize.hpp>
#include "nf7.hh"
#include "common/dir_item.hh"
#include "common/file_base.hh"
#include "common/generic_config.hh"
#include "common/generic_memento.hh"
#include "common/generic_type_info.hh"
#include "common/life.hh"
#include "common/logger_ref.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
#include "common/yaml_nf7.hh"
namespace nf7 {
namespace {
class Singleton final : public nf7::FileBase,
public nf7::DirItem, public nf7::GenericConfig, public nf7::Node {
public:
static inline const nf7::GenericTypeInfo<Singleton> kType = {
"Node/Singleton", {"nf7::DirItem",},
"shares a single lambda between multiple callers",
};
class SharedLambda;
class Lambda;
struct Data {
nf7::File::Path target;
void serialize(auto& ar) {
ar(target);
}
std::string Stringify() const noexcept {
YAML::Emitter st;
st << YAML::BeginMap;
st << YAML::Key << "target";
st << YAML::Value << target;
st << YAML::EndMap;
return {st.c_str(), st.size()};
}
void Parse(const std::string& str) noexcept {
const auto yaml = YAML::Load(str);
Data d;
d.target = yaml["target"].as<nf7::File::Path>();
*this = std::move(d);
}
};
Singleton(nf7::Env& env, Data&& d = {}) noexcept :
nf7::FileBase(kType, env),
nf7::DirItem(nf7::DirItem::kMenu |
nf7::DirItem::kTooltip),
nf7::GenericConfig(mem_),
nf7::Node(nf7::Node::kNone),
life_(*this), log_(*this), mem_(*this, std::move(d)) {
}
Singleton(nf7::Deserializer& ar) : Singleton(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<Singleton>(env, Data {mem_.data()});
}
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
nf7::Node::Meta GetMeta() const noexcept override
try {
return target().interfaceOrThrow<nf7::Node>().GetMeta();
} catch (nf7::Exception&) {
return {};
}
void PostHandle(const nf7::File::Event&) noexcept override;
void PostUpdate() noexcept override {
la_.erase(
std::remove_if(
la_.begin(), la_.end(), [](auto& w) { return w.expired(); }),
la_.end());
}
void UpdateMenu() noexcept override;
void UpdateTooltip() noexcept override;
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector<
nf7::Config, nf7::DirItem, nf7::Memento, nf7::Node>(t).Select(this, &mem_);
}
private:
nf7::Life<Singleton> life_;
nf7::LoggerRef log_;
nf7::GenericMemento<Data> mem_;
std::shared_ptr<Singleton::SharedLambda> shared_la_;
std::vector<std::weak_ptr<nf7::Node::Lambda>> la_;
nf7::File& target() const {
return ResolveOrThrow(mem_->target);
}
};
class Singleton::SharedLambda final : public nf7::Node::Lambda,
public std::enable_shared_from_this<SharedLambda> {
public:
SharedLambda(Singleton& f) noexcept : nf7::Node::Lambda(f), f_(f.life_) {
}
~SharedLambda() noexcept {
Abort();
}
void SendToTarget(const nf7::Node::Lambda::Msg& in) noexcept
try {
f_.EnforceAlive();
auto& target_file = f_->target();
if (target_file.id() != target_id_ || !target_) {
target_id_ = target_file.id();
target_ = target_file.
interfaceOrThrow<nf7::Node>().
CreateLambda(shared_from_this());
}
target_->Handle(in.name, in.value, shared_from_this());
} catch (nf7::ExpiredException&) {
} catch (nf7::Exception&) {
f_->log_.Error("failed to call target");
Abort();
}
void Abort() noexcept override {
if (target_) {
target_->Abort();
target_ = nullptr;
}
}
std::string GetDescription() const noexcept override {
return "singleton node lambda";
}
bool active() const noexcept { return !!target_; }
private:
nf7::Life<Singleton>::Ref f_;
nf7::File::Id target_id_ = 0;
std::shared_ptr<nf7::Node::Lambda> target_;
// receive from target
void Handle(const nf7::Node::Lambda::Msg& in) noexcept override
try {
f_.EnforceAlive();
if (!f_) return;
for (auto& wla : f_->la_) {
if (const auto la = wla.lock()) {
la->Handle(in.name, in.value, shared_from_this());
}
}
} catch (nf7::ExpiredException&) {
}
};
class Singleton::Lambda final : public nf7::Node::Lambda,
public std::enable_shared_from_this<Lambda> {
public:
Lambda(Singleton& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept :
nf7::Node::Lambda(f, parent), shared_(f.shared_la_) {
assert(shared_);
}
void Handle(const nf7::Node::Lambda::Msg& in) noexcept override {
const auto p = parent();
if (!p) return;
if (in.sender == shared_) {
p->Handle(in.name, in.value, shared_from_this());
} else if (in.sender == p) {
shared_->SendToTarget(in);
} else {
assert(false);
}
}
private:
std::shared_ptr<Singleton::SharedLambda> shared_;
};
std::shared_ptr<nf7::Node::Lambda> Singleton::CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
const auto ret = std::make_shared<Singleton::Lambda>(*this, parent);
la_.emplace_back(ret);
return ret;
}
void Singleton::PostHandle(const nf7::File::Event& e) noexcept {
switch (e.type) {
case nf7::File::Event::kAdd:
shared_la_ = std::make_shared<SharedLambda>(*this);
return;
case nf7::File::Event::kRemove:
shared_la_->Abort();
shared_la_ = nullptr;
return;
default:
return;
}
}
void Singleton::UpdateMenu() noexcept {
if (ImGui::MenuItem("drop current lambda")) {
shared_la_->Abort();
}
}
void Singleton::UpdateTooltip() noexcept {
ImGui::Text("target : %s", mem_->target.Stringify().c_str());
ImGui::Text("instance: %s", shared_la_->active()? "active": "unused");
}
}
} // namespace nf7

601
file/node_ziptie.cc Normal file
View File

@@ -0,0 +1,601 @@
#include <algorithm>
#include <cassert>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <typeinfo>
#include <unordered_set>
#include <utility>
#include <vector>
#include <imgui.h>
#include <imgui_stdlib.h>
#include <ImNodes.h>
#include <magic_enum.hpp>
#include <yas/serialize.hpp>
#include <yas/types/std/string.hpp>
#include <yas/types/std/vector.hpp>
#include "nf7.hh"
#include "common/file_base.hh"
#include "common/generic_memento.hh"
#include "common/generic_type_info.hh"
#include "common/gui.hh"
#include "common/life.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
#include "common/yas_enum.hh"
namespace nf7 {
class ZipTie final : public nf7::FileBase, public nf7::Node {
public:
static inline const nf7::GenericTypeInfo<ZipTie> kType = {
"Node/ZipTie", {"nf7::Node",},
"[N to 1] or [1 to N] node",
};
static constexpr size_t kMaxN = 64;
static inline const auto kIndexStrings = ([](){
std::vector<std::string> ret(kMaxN);
for (size_t i = 0; i < kMaxN; ++i) ret[i] = std::to_string(i);
return ret;
})();
class Lambda;
static constexpr uint8_t kNto1Flag = 0x10;
static constexpr uint8_t kNamedFlag = 0x20;
enum Algorithm : uint8_t {
// N to 1
kPassthruN1 = 0x0 | kNto1Flag,
kAwait = 0x1 | kNto1Flag,
kMakeArray = 0x2 | kNto1Flag,
kMakeTuple = 0x3 | kNto1Flag | kNamedFlag,
kUpdateArray = 0x4 | kNto1Flag,
kUpdateTuple = 0x5 | kNto1Flag | kNamedFlag,
// 1 to N
kPassthru1N = 0x6,
kOrderedPulse = 0x7,
kExtractArray = 0x8,
kExtractTuple = 0x9 | kNamedFlag,
};
static bool IsNto1(Algorithm algo) noexcept {
return algo & kNto1Flag;
}
static bool IsNameRequired(Algorithm algo) noexcept {
return algo & kNamedFlag;
}
struct AlgoMeta final {
std::string name;
std::string desc;
};
static inline const std::unordered_map<Algorithm, AlgoMeta> kAlgoMetas = {
{kPassthruN1, { .name = "passthru N", .desc = "passthrough multiple input to single output" }},
{kAwait, { .name = "await", .desc = "awaits for all inputs satisfied" }},
{kMakeArray, { .name = "make array", .desc = "emits an array when all inputs satisfied" }},
{kMakeTuple, { .name = "make tuple", .desc = "emits a tuple when all inputs satisfied" }},
{kUpdateArray, { .name = "update array", .desc = "emits an array when one input satisfied" }},
{kUpdateTuple, { .name = "update tuple", .desc = "emits a tuple when one input satisfied" }},
{kPassthru1N, { .name = "passthru 1", .desc = "passthrough single input to multiple output" }},
{kOrderedPulse, { .name = "ordered pulse", .desc = "emits a pulse in order" }},
{kExtractArray, { .name = "extract array", .desc = "extracts values from an array by thier index" }},
{kExtractTuple, { .name = "extract tuple", .desc = "extracts values from a tuple by thier name" }},
};
struct Data {
public:
Data() {}
Algorithm algo = kPassthru1N;
std::vector<std::string> names = {"", ""};
};
ZipTie(nf7::Env& env, Data&& d = {}) noexcept :
nf7::FileBase(kType, env),
nf7::Node(nf7::Node::kCustomNode |
nf7::Node::kMenu),
life_(*this), mem_(*this, std::move(d)) {
}
ZipTie(nf7::Deserializer& ar) : ZipTie(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<ZipTie>(env, Data {mem_.data()});
}
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept override;
nf7::Node::Meta GetMeta() const noexcept override {
const auto n = mem_->names.size();
std::vector<std::string> index(
kIndexStrings.begin(),
kIndexStrings.begin()+static_cast<intmax_t>(n));
if (IsNto1(mem_->algo)) {
return {std::move(index), {"out"}};
} else {
return {{"in"}, std::move(index)};
}
}
void UpdateNode(nf7::Node::Editor&) noexcept override;
void UpdateMenu(nf7::Node::Editor&) 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<ZipTie> life_;
nf7::GenericMemento<Data> mem_;
// socket list manipulation
void InsertSocket(nf7::Node::Editor&, size_t) noexcept;
void RemoveSocket(nf7::Node::Editor&, size_t) noexcept;
void MoveLinks(nf7::Node::Editor&, std::string_view before, std::string_view after) noexcept;
// widgets
bool SocketMenu(nf7::Node::Editor&, size_t) noexcept;
bool AlgorithmComboItem(Algorithm);
};
class ZipTie::Lambda final : public nf7::Node::Lambda,
public std::enable_shared_from_this<Lambda> {
public:
Lambda(ZipTie& 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();
const auto& d = f_->mem_.data();
if (d.algo != std::exchange(prev_algo_, d.algo)) {
values_.clear();
}
if (IsNto1(d.algo)) {
const auto idx = static_cast<size_t>(std::stoul(in.name));
if (idx >= d.names.size()) {
throw nf7::Exception {"index overflow"};
}
values_.resize(d.names.size());
values_[idx] = in.value;
} else {
values_.clear();
}
switch (d.algo) {
case kPassthruN1:
PassthruN1(in);
return;
case kAwait:
Await(in);
return;
case kMakeArray:
MakeArray(in, d);
return;
case kMakeTuple:
MakeTuple(in, d);
return;
case kUpdateArray:
UpdateArray(in, d);
return;
case kUpdateTuple:
UpdateTuple(in, d);
return;
case kPassthru1N:
Passthru1N(in, d);
return;
case kOrderedPulse:
OrderedPulse(in, d);
return;
case kExtractArray:
ExtractArray(in, d);
return;
case kExtractTuple:
ExtractTuple(in, d);
return;
}
} catch (std::invalid_argument&) {
} catch (std::out_of_range&) {
} catch (nf7::ExpiredException&) {
} catch (nf7::Exception&) {
}
private:
nf7::Life<ZipTie>::Ref f_;
std::optional<Algorithm> prev_algo_;
std::vector<std::optional<nf7::Value>> values_;
void PassthruN1(const nf7::Node::Lambda::Msg& in) noexcept {
in.sender->Handle("out", in.value, shared_from_this());
}
void Await(const nf7::Node::Lambda::Msg& in) noexcept {
if (AllSatisifed()) {
in.sender->Handle("out", nf7::Value::Pulse {}, shared_from_this());
values_.clear();
}
}
void MakeArray(const nf7::Node::Lambda::Msg& in, const Data& d) noexcept {
if (AllSatisifed()) {
UpdateArray(in, d);
values_.clear();
}
}
void MakeTuple(const nf7::Node::Lambda::Msg& in, const Data& d) noexcept {
if (AllSatisifed()) {
UpdateTuple(in, d);
values_.clear();
}
}
void UpdateArray(const nf7::Node::Lambda::Msg& in, const Data& d) noexcept {
std::vector<nf7::Value::TuplePair> pairs;
pairs.reserve(d.names.size());
for (size_t i = 0; i < d.names.size(); ++i) {
if (!values_[i]) continue;
pairs.emplace_back(std::string {}, *values_[i]);
}
in.sender->Handle("out", std::move(pairs), shared_from_this());
}
void UpdateTuple(const nf7::Node::Lambda::Msg& in, const Data& d) noexcept {
std::vector<nf7::Value::TuplePair> pairs;
pairs.reserve(d.names.size());
for (size_t i = 0; i < d.names.size(); ++i) {
const auto& name = d.names[i];
if (name == "" || !values_[i]) continue;
pairs.emplace_back(name, *values_[i]);
}
in.sender->Handle("out", std::move(pairs), shared_from_this());
}
void Passthru1N(const nf7::Node::Lambda::Msg& in, const Data& d) noexcept {
for (const auto& name : d.names) {
in.sender->Handle(name, in.value, shared_from_this());
}
}
void OrderedPulse(const nf7::Node::Lambda::Msg& in, const Data& d) noexcept {
for (size_t i = 0; i < d.names.size(); ++i) {
in.sender->Handle(kIndexStrings[i], nf7::Value::Pulse {}, shared_from_this());
}
}
void ExtractArray(const nf7::Node::Lambda::Msg& in, const Data& d) noexcept {
for (size_t i = 0; i < d.names.size(); ++i)
try {
in.sender->Handle(kIndexStrings[i], in.value.tuple(i), shared_from_this());
} catch (nf7::Exception&) {
}
}
void ExtractTuple(const nf7::Node::Lambda::Msg& in, const Data& d) noexcept {
for (size_t i = 0; i < d.names.size(); ++i)
try {
in.sender->Handle(kIndexStrings[i], in.value.tuple(d.names[i]), shared_from_this());
} catch (nf7::Exception&) {
}
}
bool AllSatisifed() const noexcept {
return std::all_of(values_.begin(), values_.end(), [](auto& x) { return !!x; });
}
};
std::shared_ptr<nf7::Node::Lambda> ZipTie::CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
return std::make_shared<Lambda>(*this, parent);
}
void ZipTie::InsertSocket(nf7::Node::Editor& ed, size_t idx) noexcept {
auto& names = mem_->names;
assert(names.size() < kMaxN);
assert(idx <= names.size());
env().ExecMain(nullptr, [&names, idx](){
names.insert(names.begin()+static_cast<intmax_t>(idx), std::string {});
});
for (size_t i = names.size(); i > idx; --i) {
MoveLinks(ed, kIndexStrings[i-1], kIndexStrings[i]);
}
}
void ZipTie::RemoveSocket(nf7::Node::Editor& ed, size_t idx) noexcept {
auto& names = mem_->names;
assert(names.size() >= 1);
assert(idx < names.size());
MoveLinks(ed, kIndexStrings[idx], "");
for (size_t i = idx; i < names.size()-1; ++i) {
MoveLinks(ed, kIndexStrings[i+1], kIndexStrings[i]);
}
env().ExecMain(nullptr, [&names, idx](){
names.erase(names.begin() + static_cast<intmax_t>(idx));
});
}
void ZipTie::MoveLinks(
nf7::Node::Editor& ed, std::string_view before, std::string_view after) noexcept {
const bool self_src = !IsNto1(mem_->algo);
const auto others =
self_src? ed.GetDstOf(*this, before): ed.GetSrcOf(*this, before);
for (const auto& other_ref : others) {
using P = std::pair<nf7::Node*, std::string_view>;
P self = {this, before};
P other = {other_ref.first, other_ref.second};
// remove existing link
{
auto src = &self, dst = &other;
if (!self_src) std::swap(src, dst);
ed.RemoveLink(*src->first, src->second, *dst->first, dst->second);
}
// add removed link
self.second = after;
if (after != "") {
auto src = &self, dst = &other;
if (!self_src) std::swap(src, dst);
ed.AddLink(*src->first, src->second, *dst->first, dst->second);
}
}
}
void ZipTie::UpdateNode(nf7::Node::Editor& ed) noexcept {
const auto em = ImGui::GetFontSize();
auto meta_itr = kAlgoMetas.find(mem_->algo);
assert(meta_itr != kAlgoMetas.end());
const auto& meta = meta_itr->second;
bool mod = false;
ImGui::TextUnformatted("Node/ZipTie");
ImGui::SameLine();
const auto right_top = ImGui::GetCursorPos();
ImGui::NewLine();
const auto left_top = ImGui::GetCursorPos();
ImGui::AlignTextToFramePadding();
ImGui::NewLine();
// inputs
ImGui::BeginGroup();
if (IsNto1(mem_->algo)) {
for (size_t i = 0; i < mem_->names.size(); ++i) {
if (ImNodes::BeginInputSlot(kIndexStrings[i].c_str(), 1)) {
ImGui::AlignTextToFramePadding();
gui::NodeSocket();
ImNodes::EndSlot();
}
if (ImGui::BeginPopupContextItem()) {
mod |= SocketMenu(ed, i);
ImGui::EndPopup();
}
}
} else {
if (ImNodes::BeginInputSlot("in", 1)) {
ImGui::AlignTextToFramePadding();
gui::NodeSocket();
ImNodes::EndSlot();
}
}
ImGui::EndGroup();
// text input
ImGui::SameLine();
ImGui::BeginGroup();
for (size_t i = 0; i < mem_->names.size(); ++i) {
ImGui::AlignTextToFramePadding();
if (!IsNto1(mem_->algo)) {
ImGui::TextUnformatted(" ->");
ImGui::SameLine();
}
if (IsNameRequired(mem_->algo)) {
ImGui::SetNextItemWidth(6*em);
const auto id = "##text"+kIndexStrings[i];
ImGui::InputText(id.c_str(), &mem_->names[i]);
if (ImGui::IsItemDeactivatedAfterEdit()) {
mod = true;
}
if (ImGui::BeginPopupContextItem()) {
mod |= SocketMenu(ed, i);
ImGui::EndPopup();
}
} else {
ImGui::Text("%zu", i);
}
if (IsNto1(mem_->algo)) {
ImGui::SameLine();
ImGui::TextUnformatted("-> ");
}
}
ImGui::EndGroup();
// outputs
ImGui::SameLine();
ImGui::BeginGroup();
if (IsNto1(mem_->algo)) {
if (ImNodes::BeginOutputSlot("out", 1)) {
ImGui::AlignTextToFramePadding();
gui::NodeSocket();
ImNodes::EndSlot();
}
} else {
for (size_t i = 0; i < mem_->names.size(); ++i) {
if (ImNodes::BeginOutputSlot(kIndexStrings[i].c_str(), 1)) {
ImGui::AlignTextToFramePadding();
gui::NodeSocket();
ImNodes::EndSlot();
}
if (ImGui::BeginPopupContextItem()) {
mod |= SocketMenu(ed, i);
ImGui::EndPopup();
}
}
}
ImGui::EndGroup();
ImGui::SameLine();
const auto right_bottom = ImGui::GetCursorPos();
ImGui::NewLine();
// algorithm selection
ImGui::SetCursorPos(left_top);
auto w = std::max(right_bottom.x, right_top.x) - left_top.x;
ImGui::Button(meta.name.c_str(), {w, 0});
if (ImGui::BeginPopupContextItem(nullptr, ImGuiPopupFlags_MouseButtonLeft)) {
ImGui::TextDisabled("N to 1");
mod |= AlgorithmComboItem(kPassthruN1);
mod |= AlgorithmComboItem(kAwait);
mod |= AlgorithmComboItem(kMakeArray);
mod |= AlgorithmComboItem(kMakeTuple);
mod |= AlgorithmComboItem(kUpdateArray);
mod |= AlgorithmComboItem(kUpdateTuple);
ImGui::Separator();
ImGui::TextDisabled("1 to N");
mod |= AlgorithmComboItem(kPassthru1N);
mod |= AlgorithmComboItem(kOrderedPulse);
mod |= AlgorithmComboItem(kExtractArray);
mod |= AlgorithmComboItem(kExtractTuple);
ImGui::EndPopup();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("choose algorithm");
}
// commit changes
if (mod) {
env().ExecMain(
std::make_shared<nf7::GenericContext>(*this, "memento commit"),
[this]() { mem_.Commit(); });
}
}
void ZipTie::UpdateMenu(nf7::Node::Editor&) noexcept {
if (ImGui::BeginMenu("config")) {
static int n;
if (ImGui::IsWindowAppearing()) {
n = static_cast<int>(mem_->names.size());
}
ImGui::PushItemWidth(6*ImGui::GetFontSize());
ImGui::DragInt("sockets", &n, 0.25f, 1, static_cast<int>(kMaxN));
if (ImGui::IsItemDeactivatedAfterEdit()) {
mem_->names.resize(static_cast<size_t>(n));
mem_.Commit();
}
ImGui::PopItemWidth();
ImGui::EndMenu();
}
}
bool ZipTie::SocketMenu(nf7::Node::Editor& ed, size_t i) noexcept {
bool mod = false;
ImGui::BeginDisabled(mem_->names.size() >= kMaxN);
if (ImGui::MenuItem("insert before")) {
InsertSocket(ed, i);
mod = true;
}
if (ImGui::MenuItem("insert after")) {
InsertSocket(ed, i+1);
mod = true;
}
ImGui::EndDisabled();
ImGui::BeginDisabled(mem_->names.size() == 1);
if (ImGui::MenuItem("remove")) {
RemoveSocket(ed, i);
mod = true;
}
ImGui::EndDisabled();
return mod;
}
bool ZipTie::AlgorithmComboItem(Algorithm algo) {
bool mod = false;
auto itr = kAlgoMetas.find(algo);
assert(itr != kAlgoMetas.end());
const auto& meta = itr->second;
if (ImGui::Selectable(meta.name.c_str(), mem_->algo == algo)) {
if (mem_->algo != algo) {
mem_->algo = algo;
mod = true;
}
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s", meta.desc.c_str());
}
return mod;
}
} // namespace nf7
namespace yas::detail {
NF7_YAS_DEFINE_ENUM_SERIALIZER(nf7::ZipTie::Algorithm);
template <size_t F>
struct serializer<
type_prop::not_a_fundamental,
ser_case::use_internal_serializer,
F,
nf7::ZipTie::Data> {
public:
template <typename Archive>
static Archive& save(Archive& ar, const nf7::ZipTie::Data& d) {
ar(d.algo);
if (nf7::ZipTie::IsNameRequired(d.algo)) {
ar(d.names);
} else {
ar(d.names.size());
}
return ar;
}
template <typename Archive>
static Archive& load(Archive& ar, nf7::ZipTie::Data& d) {
ar(d.algo);
if (nf7::ZipTie::IsNameRequired(d.algo)) {
ar(d.names);
} else {
size_t n;
ar(n);
d.names.clear();
d.names.resize(n);
}
if (d.names.size() > nf7::ZipTie::kMaxN) {
throw nf7::DeserializeException {"Node/ZipTie maximum socket count exceeded"};
}
if (d.names.size() == 0) {
d.names.resize(1);
}
return ar;
}
};
} // namespace yas::detail

View File

@@ -15,12 +15,10 @@
#include <yas/types/std/vector.hpp> #include <yas/types/std/vector.hpp>
#include "common/file_base.hh" #include "common/file_base.hh"
#include "common/file_holder.hh"
#include "common/generic_context.hh" #include "common/generic_context.hh"
#include "common/generic_memento.hh" #include "common/generic_memento.hh"
#include "common/generic_type_info.hh" #include "common/generic_type_info.hh"
#include "common/gui_file.hh" #include "common/gui.hh"
#include "common/gui_popup.hh"
#include "common/gui_value.hh" #include "common/gui_value.hh"
#include "common/life.hh" #include "common/life.hh"
#include "common/ptr_selector.hh" #include "common/ptr_selector.hh"
@@ -31,17 +29,13 @@
namespace nf7 { namespace nf7 {
namespace { namespace {
class Adaptor final : public nf7::FileBase, public nf7::Sequencer { class Adaptor final : public nf7::FileBase,
public nf7::Sequencer {
public: public:
static inline const nf7::GenericTypeInfo<Adaptor> kType = static inline const nf7::GenericTypeInfo<Adaptor> kType = {
{"Sequencer/Adaptor", {"nf7::Sequencer"}}; "Sequencer/Adaptor", {"nf7::Sequencer"},
static void UpdateTypeTooltip() noexcept { "wraps and adapts other Sequencer",
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 Session;
class Lambda; class Lambda;
@@ -56,35 +50,32 @@ class Adaptor final : public nf7::FileBase, public nf7::Sequencer {
} }
}; };
struct Data { struct Data {
nf7::FileHolder::Tag target; nf7::File::Path path;
std::vector<std::pair<std::string, nf7::gui::Value>> input_imm; 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, Var>> input_map;
std::vector<std::pair<std::string, std::string>> output_map; std::vector<std::pair<std::string, std::string>> output_map;
void serialize(auto& ar) {
ar(path, input_imm, input_map, output_map);
}
}; };
Adaptor(nf7::Env& env, Data&& data = {}) noexcept : Adaptor(nf7::Env& env, Data&& d = {}) noexcept :
nf7::FileBase(kType, env, {&target_, &target_editor_}), nf7::FileBase(kType, env),
Sequencer(Sequencer::kCustomItem | nf7::Sequencer(Sequencer::kCustomItem |
Sequencer::kTooltip | Sequencer::kTooltip |
Sequencer::kParamPanel), Sequencer::kParamPanel),
life_(*this), life_(*this), mem_(*this, std::move(d)) {
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()) { Adaptor(nf7::Deserializer& ar) : Adaptor(ar.env()) {
ar(target_, data().input_imm, data().input_map, data().output_map); ar(mem_.data());
} }
void Serialize(nf7::Serializer& ar) const noexcept override { void Serialize(nf7::Serializer& ar) const noexcept override {
ar(target_, data().input_imm, data().input_map, data().output_map); ar(mem_.data());
} }
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override { std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<Adaptor>(env, Data {data()}); return std::make_unique<Adaptor>(env, Data {mem_.data()});
} }
std::shared_ptr<nf7::Sequencer::Lambda> CreateLambda( std::shared_ptr<nf7::Sequencer::Lambda> CreateLambda(
@@ -101,14 +92,8 @@ class Adaptor final : public nf7::FileBase, public nf7::Sequencer {
private: private:
nf7::Life<Adaptor> life_; nf7::Life<Adaptor> life_;
nf7::FileHolder target_;
nf7::gui::FileHolderEditor target_editor_;
nf7::GenericMemento<Data> mem_; nf7::GenericMemento<Data> mem_;
const Data& data() const noexcept { return mem_.data(); }
Data& data() noexcept { return mem_.data(); }
}; };
@@ -116,10 +101,10 @@ class Adaptor::Session final : public nf7::Sequencer::Session {
public: public:
// ensure that Adaptor is alive // ensure that Adaptor is alive
Session(Adaptor& f, const std::shared_ptr<nf7::Sequencer::Session>& parent) noexcept : parent_(parent) { Session(Adaptor& f, const std::shared_ptr<nf7::Sequencer::Session>& parent) noexcept : parent_(parent) {
for (auto& p : f.data().input_imm) { for (auto& p : f.mem_->input_imm) {
vars_[p.first] = p.second.entity(); vars_[p.first] = p.second.entity();
} }
for (auto& p : f.data().input_map) { for (auto& p : f.mem_->input_map) {
if (p.second.name.size() == 0) continue; if (p.second.name.size() == 0) continue;
if (p.second.peek) { if (p.second.peek) {
if (const auto ptr = parent->Peek(p.second.name)) { if (const auto ptr = parent->Peek(p.second.name)) {
@@ -131,7 +116,7 @@ class Adaptor::Session final : public nf7::Sequencer::Session {
} }
} }
} }
for (auto& p : f.data().output_map) { for (auto& p : f.mem_->output_map) {
outs_[p.first] = p.second; outs_[p.first] = p.second;
} }
} }
@@ -184,7 +169,7 @@ class Adaptor::Lambda final : public nf7::Sequencer::Lambda,
try { try {
f_.EnforceAlive(); f_.EnforceAlive();
auto& target = f_->target_.GetFileOrThrow(); auto& target = f_->ResolveOrThrow(f_->mem_->path);
auto& seq = target.interfaceOrThrow<nf7::Sequencer>(); auto& seq = target.interfaceOrThrow<nf7::Sequencer>();
if (!la_ || target.id() != cached_id_) { if (!la_ || target.id() != cached_id_) {
la_ = seq.CreateLambda(shared_from_this()); la_ = seq.CreateLambda(shared_from_this());
@@ -215,25 +200,29 @@ class Adaptor::Editor final : public nf7::Sequencer::Editor {
void Adaptor::UpdateItem(Sequencer::Editor&) noexcept { void Adaptor::UpdateItem(Sequencer::Editor&) noexcept {
try { try {
auto& seq = target_.GetFileOrThrow().interfaceOrThrow<nf7::Sequencer>(); auto& seq = ResolveOrThrow(mem_->path).interfaceOrThrow<nf7::Sequencer>();
if (seq.flags() & nf7::Sequencer::kCustomItem) { if (seq.flags() & nf7::Sequencer::kCustomItem) {
Adaptor::Editor ed; Adaptor::Editor ed;
seq.UpdateItem(ed); seq.UpdateItem(ed);
} }
} catch (nf7::Exception&) { } catch (nf7::File::NotFoundException&) {
ImGui::Text("%s", target_editor_.GetDisplayText().c_str()); ImGui::TextUnformatted("file missing");
} catch (nf7::File::NotImplementedException&) {
ImGui::TextUnformatted("file does not have Sequencer interface");
} }
} }
void Adaptor::UpdateParamPanel(Sequencer::Editor&) noexcept { void Adaptor::UpdateParamPanel(Sequencer::Editor&) noexcept {
bool commit = false; bool commit = false;
auto& imm = data().input_imm; auto& imm = mem_->input_imm;
auto& inputs = data().input_map; auto& inputs = mem_->input_map;
auto& outputs = data().output_map; auto& outputs = mem_->output_map;
const auto em = ImGui::GetFontSize(); const auto em = ImGui::GetFontSize();
if (ImGui::CollapsingHeader("Sequencer/Adaptor", ImGuiTreeNodeFlags_DefaultOpen)) { if (ImGui::CollapsingHeader("Sequencer/Adaptor", ImGuiTreeNodeFlags_DefaultOpen)) {
target_editor_.ButtonWithLabel("target"); if (nf7::gui::PathButton("path", mem_->path, *this)) {
commit = true;
}
if (ImGui::BeginTable("table", 3)) { if (ImGui::BeginTable("table", 3)) {
ImGui::TableSetupColumn("left", ImGuiTableColumnFlags_WidthStretch, 1.f); ImGui::TableSetupColumn("left", ImGuiTableColumnFlags_WidthStretch, 1.f);
@@ -390,7 +379,7 @@ void Adaptor::UpdateParamPanel(Sequencer::Editor&) noexcept {
ImGui::Spacing(); ImGui::Spacing();
try { try {
auto& seq = target_.GetFileOrThrow().interfaceOrThrow<nf7::Sequencer>(); auto& seq = ResolveOrThrow(mem_->path).interfaceOrThrow<nf7::Sequencer>();
if (seq.flags() & nf7::Sequencer::kParamPanel) { if (seq.flags() & nf7::Sequencer::kParamPanel) {
Adaptor::Editor ed; Adaptor::Editor ed;
seq.UpdateParamPanel(ed); seq.UpdateParamPanel(ed);
@@ -406,4 +395,3 @@ void Adaptor::UpdateTooltip(Sequencer::Editor&) noexcept {
} }
} // namespace nf7 } // namespace nf7

View File

@@ -12,12 +12,10 @@
#include <yas/types/std/vector.hpp> #include <yas/types/std/vector.hpp>
#include "common/file_base.hh" #include "common/file_base.hh"
#include "common/file_holder.hh" #include "common/gui.hh"
#include "common/generic_context.hh" #include "common/generic_context.hh"
#include "common/generic_memento.hh" #include "common/generic_memento.hh"
#include "common/generic_type_info.hh" #include "common/generic_type_info.hh"
#include "common/gui_file.hh"
#include "common/gui_popup.hh"
#include "common/life.hh" #include "common/life.hh"
#include "common/node.hh" #include "common/node.hh"
#include "common/ptr_selector.hh" #include "common/ptr_selector.hh"
@@ -30,46 +28,40 @@ namespace {
class Call final : public nf7::FileBase, public nf7::Sequencer { class Call final : public nf7::FileBase, public nf7::Sequencer {
public: public:
static inline const nf7::GenericTypeInfo<Call> kType = { static inline const nf7::GenericTypeInfo<Call> kType = {
"Sequencer/Call", {"nf7::Sequencer"}}; "Sequencer/Call", {"nf7::Sequencer"},
static void UpdateTypeTooltip() noexcept { "calls an external Node as a Sequencer",
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 Lambda;
class SessionLambda; class SessionLambda;
struct Data { struct Data {
nf7::FileHolder::Tag callee; nf7::File::Path callee;
std::string expects; std::string expects;
bool pure; bool pure;
void serialize(auto& ar) {
ar(callee, expects, pure);
}
}; };
Call(nf7::Env& env, Data&& data = {}) noexcept : Call(nf7::Env& env, Data&& data = {}) noexcept :
FileBase(kType, env, {&callee_, &callee_editor_}), nf7::FileBase(kType, env),
Sequencer(Sequencer::kCustomItem | Sequencer(Sequencer::kCustomItem |
Sequencer::kTooltip | Sequencer::kTooltip |
Sequencer::kParamPanel), Sequencer::kParamPanel),
life_(*this), life_(*this),
callee_(*this, "callee", mem_), mem_(*this, std::move(data)) {
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()) { Call(nf7::Deserializer& ar) : Call(ar.env()) {
ar(callee_, data().expects, data().pure); ar(mem_.data());
} }
void Serialize(nf7::Serializer& ar) const noexcept override { void Serialize(nf7::Serializer& ar) const noexcept override {
ar(callee_, data().expects, data().pure); ar(mem_.data());
} }
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override { std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<Call>(env, Data {data()}); return std::make_unique<Call>(env, Data {mem_.data()});
} }
std::shared_ptr<nf7::Sequencer::Lambda> CreateLambda( std::shared_ptr<nf7::Sequencer::Lambda> CreateLambda(
@@ -80,20 +72,14 @@ class Call final : public nf7::FileBase, public nf7::Sequencer {
void UpdateTooltip(nf7::Sequencer::Editor&) noexcept override; void UpdateTooltip(nf7::Sequencer::Editor&) noexcept override;
nf7::File::Interface* interface(const std::type_info& t) noexcept override { nf7::File::Interface* interface(const std::type_info& t) noexcept override {
return InterfaceSelector< return nf7::InterfaceSelector<
nf7::Memento, nf7::Sequencer>(t).Select(this, &mem_); nf7::Memento, nf7::Sequencer>(t).Select(this, &mem_);
} }
private: private:
nf7::Life<Call> life_; nf7::Life<Call> life_;
nf7::FileHolder callee_;
nf7::gui::FileHolderEditor callee_editor_;
nf7::GenericMemento<Data> mem_; nf7::GenericMemento<Data> mem_;
Data& data() noexcept { return mem_.data(); }
const Data& data() const noexcept { return mem_.data(); }
}; };
@@ -127,7 +113,7 @@ class Call::SessionLambda final : public nf7::Node::Lambda {
assert(!ss_); assert(!ss_);
ss_ = ss; ss_ = ss;
const auto ex = f.data().expects; const auto ex = f.mem_->expects;
size_t begin = 0; size_t begin = 0;
for (size_t i = 0; i <= ex.size(); ++i) { for (size_t i = 0; i <= ex.size(); ++i) {
if (i == ex.size() || ex[i] == '\n') { if (i == ex.size() || ex[i] == '\n') {
@@ -175,8 +161,8 @@ try {
if (abort_) return; if (abort_) return;
file_.EnforceAlive(); file_.EnforceAlive();
auto& data = file_->data(); auto& data = file_->mem_.data();
auto& callee = file_->callee_.GetFileOrThrow(); auto& callee = file_->ResolveOrThrow(data.callee);
auto& node = callee.interfaceOrThrow<nf7::Node>(); auto& node = callee.interfaceOrThrow<nf7::Node>();
if (!ssla_) { if (!ssla_) {
@@ -189,7 +175,8 @@ try {
} }
ssla_->Listen(*file_, ss); ssla_->Listen(*file_, ss);
for (const auto& name : node.GetInputs()) { const auto inputs = node.GetMeta().inputs;
for (const auto& name : inputs) {
if (auto v = ss->Receive(name)) { if (auto v = ss->Receive(name)) {
la_->Handle(name, *v, ssla_); la_->Handle(name, *v, ssla_);
} }
@@ -199,11 +186,7 @@ try {
ssla_ = nullptr; ssla_ = nullptr;
la_ = nullptr; la_ = nullptr;
} }
} catch (nf7::ExpiredException&) { } catch (nf7::Exception&) {
ss->Finish();
} catch (nf7::FileHolder::EmptyException&) {
ss->Finish();
} catch (nf7::File::NotImplementedException&) {
ss->Finish(); ss->Finish();
} }
void Call::Lambda::Abort() noexcept { void Call::Lambda::Abort() noexcept {
@@ -219,30 +202,35 @@ void Call::Lambda::Abort() noexcept {
void Call::UpdateItem(Sequencer::Editor&) noexcept { void Call::UpdateItem(Sequencer::Editor&) noexcept {
ImGui::Text("%s", callee_editor_.GetDisplayText().c_str()); ImGui::Text("%s", mem_->callee.Stringify().c_str());
} }
void Call::UpdateParamPanel(Sequencer::Editor&) noexcept { void Call::UpdateParamPanel(Sequencer::Editor&) noexcept {
const auto em = ImGui::GetFontSize(); const auto em = ImGui::GetFontSize();
bool commit = false;
if (ImGui::CollapsingHeader("Sequencer/Call", ImGuiTreeNodeFlags_DefaultOpen)) { if (ImGui::CollapsingHeader("Sequencer/Call", ImGuiTreeNodeFlags_DefaultOpen)) {
callee_editor_.ButtonWithLabel("callee"); if (nf7::gui::PathButton("callee", mem_->callee, *this)) {
commit = true;
}
ImGui::InputTextMultiline("expects", &data().expects, {0, 4.f*em}); ImGui::InputTextMultiline("expects", &mem_->expects, {0, 4.f*em});
if (ImGui::IsItemDeactivatedAfterEdit()) { if (ImGui::IsItemDeactivatedAfterEdit()) {
mem_.Commit(); commit = true;
} }
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("session ends right after receiving these outputs"); ImGui::SetTooltip("session ends right after receiving these outputs");
} }
if (ImGui::Checkbox("pure", &data().pure)) { if (ImGui::Checkbox("pure", &mem_->pure)) {
mem_.Commit(); commit = true;
} }
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("callee's lambda is created for each session"); ImGui::SetTooltip("callee's lambda is created for each session");
} }
ImGui::Spacing(); }
callee_editor_.ItemWidget("callee");
if (commit) {
mem_.Commit();
} }
} }
void Call::UpdateTooltip(Sequencer::Editor&) noexcept { void Call::UpdateTooltip(Sequencer::Editor&) noexcept {

View File

@@ -25,9 +25,7 @@
#include "common/file_base.hh" #include "common/file_base.hh"
#include "common/generic_context.hh" #include "common/generic_context.hh"
#include "common/generic_type_info.hh" #include "common/generic_type_info.hh"
#include "common/gui_context.hh" #include "common/gui.hh"
#include "common/gui_file.hh"
#include "common/gui_popup.hh"
#include "common/gui_timeline.hh" #include "common/gui_timeline.hh"
#include "common/gui_window.hh" #include "common/gui_window.hh"
#include "common/life.hh" #include "common/life.hh"
@@ -49,11 +47,8 @@ namespace {
class TL final : public nf7::FileBase, public nf7::DirItem, public nf7::Node { class TL final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
public: public:
static inline const nf7::GenericTypeInfo<TL> kType = { static inline const nf7::GenericTypeInfo<TL> kType = {
"Sequencer/Timeline", {"nf7::DirItem"}}; "Sequencer/Timeline", {"nf7::DirItem"},
static void UpdateTypeTooltip() noexcept { };
ImGui::TextUnformatted("Timeline data");
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Node");
}
struct Timing; struct Timing;
@@ -64,60 +59,41 @@ class TL final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
class Session; class Session;
class Lambda; class Lambda;
class ConfigModifyCommand;
using ItemId = uint64_t; using ItemId = uint64_t;
TL(nf7::Env& env, TL(nf7::Env& env,
std::vector<std::unique_ptr<Layer>>&& layers = {}, std::vector<std::unique_ptr<Layer>>&& layers = {},
ItemId next = 1, ItemId next = 1) noexcept :
const nf7::gui::Window* win = nullptr) noexcept : nf7::FileBase(kType, env),
nf7::FileBase(kType, env, {&log_, &popup_socket_, &popup_add_item_}), nf7::DirItem(nf7::DirItem::kMenu),
nf7::DirItem(nf7::DirItem::kMenu | nf7::DirItem::kWidget), nf7::Node(nf7::Node::kNone),
nf7::Node(nf7::Node::kMenu_DirItem),
life_(*this), log_(*this), life_(*this), log_(*this),
layers_(std::move(layers)), next_(next), layers_(std::move(layers)), next_(next),
win_(*this, "Timeline Editor", win), tl_("timeline"), win_(*this, "Timeline Editor"), tl_("timeline") {
popup_add_item_(*this) { win_.onUpdate = [this]() { TimelineEditor(); };
ApplySeqSocketChanges();
popup_socket_.onSubmit = [this](auto&& i, auto&& o) {
ExecChangeSeqSocket(std::move(i), std::move(o));
};
} }
~TL() noexcept { ~TL() noexcept {
history_.Clear(); history_.Clear();
} }
TL(nf7::Deserializer& ar) : TL(ar.env()) { TL(nf7::Deserializer& ar) : TL(ar.env()) {
ar(seq_inputs_, seq_outputs_, win_, tl_, layers_); ar(win_, tl_, layers_);
AssignId(); AssignId();
ApplySeqSocketChanges();
} }
void Serialize(nf7::Serializer& ar) const noexcept override { void Serialize(nf7::Serializer& ar) const noexcept override {
ar(seq_inputs_, seq_outputs_, win_, tl_, layers_); ar(win_, tl_, layers_);
} }
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override; std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override;
std::shared_ptr<nf7::Node::Lambda> CreateLambda( std::shared_ptr<nf7::Node::Lambda> CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override; const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
std::span<const std::string> GetInputs() const noexcept override { nf7::Node::Meta GetMeta() const noexcept override {
return inputs_; return {{"exec"}, {"result"}};
}
std::span<const std::string> GetOutputs() const noexcept override {
return outputs_;
} }
void Handle(const nf7::File::Event& ev) noexcept; void PostHandle(const nf7::File::Event& ev) noexcept;
void Update() noexcept override; void PostUpdate() noexcept override;
void UpdateMenu() noexcept override; void UpdateMenu() noexcept override;
void UpdateWidget() noexcept override;
void UpdateEditorWindow() noexcept;
void UpdateLambdaSelector() noexcept;
void HandleTimelineAction() noexcept;
void UpdateParamPanelWindow() noexcept;
nf7::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::Node>(t).Select(this); return nf7::InterfaceSelector<nf7::DirItem, nf7::Node>(t).Select(this);
@@ -132,8 +108,6 @@ class TL final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
std::shared_ptr<TL::Lambda> lambda_; std::shared_ptr<TL::Lambda> lambda_;
std::vector<std::weak_ptr<TL::Lambda>> lambdas_running_; std::vector<std::weak_ptr<TL::Lambda>> lambdas_running_;
std::vector<std::string> inputs_, outputs_; // for GetInputs/GetOutputs
uint64_t action_time_; uint64_t action_time_;
uint64_t action_layer_; uint64_t action_layer_;
@@ -141,44 +115,12 @@ class TL final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
uint64_t cursor_ = 0; uint64_t cursor_ = 0;
std::vector<std::unique_ptr<Layer>> layers_; std::vector<std::unique_ptr<Layer>> layers_;
std::vector<std::string> seq_inputs_;
std::vector<std::string> seq_outputs_;
ItemId next_; ItemId next_;
nf7::gui::Window win_; nf7::gui::Window win_;
nf7::gui::Timeline tl_; nf7::gui::Timeline tl_;
// GUI popup
nf7::gui::IOSocketListPopup popup_socket_;
struct AddItemPopup final :
public nf7::FileBase::Feature, private nf7::gui::Popup {
public:
AddItemPopup(TL& f) noexcept :
Popup("AddItemPopup"),
owner_(&f),
factory_(f, [](auto& t) { return t.flags().contains("nf7::Sequencer"); }) {
}
void Open(uint64_t t, TL::Layer& l) noexcept {
target_time_ = t;
target_layer_ = &l;
Popup::Open();
}
void Update() noexcept override;
private:
TL* const owner_;
uint64_t target_time_ = 0;
TL::Layer* target_layer_ = nullptr;
nf7::gui::FileFactory factory_;
} popup_add_item_;
// GUI temporary params // GUI temporary params
bool param_panel_request_focus_ = false; bool param_panel_request_focus_ = false;
TL::Item* param_panel_target_ = nullptr; TL::Item* param_panel_target_ = nullptr;
@@ -206,7 +148,7 @@ class TL final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
void ExecApplyLayerOfSelected() noexcept; void ExecApplyLayerOfSelected() noexcept;
void MoveDisplayLayerOfSelected(int64_t diff) noexcept; void MoveDisplayLayerOfSelected(int64_t diff) noexcept;
// history // history operation
void ExecUnDo() noexcept { void ExecUnDo() noexcept {
env().ExecMain( env().ExecMain(
std::make_shared<nf7::GenericContext>(*this, "reverting commands to undo"), std::make_shared<nf7::GenericContext>(*this, "reverting commands to undo"),
@@ -218,19 +160,17 @@ class TL final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
[this]() { history_.ReDo(); }); [this]() { history_.ReDo(); });
} }
// instant running // instant running
void MoveCursorTo(uint64_t t) noexcept; void MoveCursorTo(uint64_t t) noexcept;
void AttachLambda(const std::shared_ptr<TL::Lambda>&) noexcept; void AttachLambda(const std::shared_ptr<TL::Lambda>&) noexcept;
// socket operation // gui
void ExecChangeSeqSocket(std::vector<std::string>&&, std::vector<std::string>&&) noexcept; void TimelineEditor() noexcept;
void ApplySeqSocketChanges() noexcept { void ParamPanel() noexcept;
inputs_ = seq_inputs_; void LambdaSelector() noexcept;
inputs_.push_back("_exec"); void ItemAdder() noexcept;
outputs_ = seq_outputs_; void HandleTimelineAction() noexcept;
}
}; };
@@ -633,11 +573,11 @@ class TL::Lambda final : public Node::Lambda,
auto caller = parent(); auto caller = parent();
if (!caller) return; if (!caller) return;
for (const auto& name : owner_->seq_outputs_) { std::vector<nf7::Value::TuplePair> tup;
auto itr = vars.find(name); for (auto& p : vars) {
if (itr == vars.end()) continue; tup.emplace_back(p.first, p.second);
caller->Handle(name, itr->second, shared_from_this());
} }
caller->Handle("result", nf7::Value {std::move(tup)}, shared_from_this());
} }
void Abort() noexcept { void Abort() noexcept {
@@ -1135,75 +1075,16 @@ void TL::MoveDisplayLayerOfSelected(int64_t diff) noexcept {
} }
class TL::ConfigModifyCommand final : public nf7::History::Command {
public:
struct Builder final {
public:
Builder(TL& f) noexcept :
prod_(std::make_unique<ConfigModifyCommand>(f)) {
}
Builder& inputs(std::vector<std::string>&& v) noexcept {
prod_->seq_inputs_ = std::move(v);
return *this;
}
Builder& outputs(std::vector<std::string>&& v) noexcept {
prod_->seq_outputs_ = std::move(v);
return *this;
}
std::unique_ptr<ConfigModifyCommand> Build() noexcept {
return std::move(prod_);
}
private:
std::unique_ptr<ConfigModifyCommand> prod_;
};
ConfigModifyCommand(TL& f) noexcept : owner_(&f) {
}
void Apply() override { Exec(); }
void Revert() override { Exec(); }
private:
TL* const owner_;
std::optional<std::vector<std::string>> seq_inputs_, seq_outputs_;
void Exec() noexcept {
if (seq_inputs_) {
std::swap(owner_->seq_inputs_, *seq_inputs_);
}
if (seq_outputs_) {
std::swap(owner_->seq_outputs_, *seq_outputs_);
}
if (seq_inputs_ || seq_outputs_) {
owner_->ApplySeqSocketChanges();
}
}
};
void TL::ExecChangeSeqSocket(std::vector<std::string>&& i, std::vector<std::string>&& o) noexcept {
auto cmd = ConfigModifyCommand::Builder {*this}.
inputs(std::move(i)).
outputs(std::move(o)).
Build();
auto ctx = std::make_shared<nf7::GenericContext>(*this, "updating I/O socket list");
history_.Add(std::move(cmd)).ExecApply(ctx);
}
std::unique_ptr<nf7::File> TL::Clone(nf7::Env& env) const noexcept { std::unique_ptr<nf7::File> TL::Clone(nf7::Env& env) const noexcept {
std::vector<std::unique_ptr<TL::Layer>> layers; std::vector<std::unique_ptr<TL::Layer>> layers;
layers.reserve(layers_.size()); layers.reserve(layers_.size());
ItemId next = 1; ItemId next = 1;
for (const auto& layer : layers_) layers.push_back(layer->Clone(env, next)); for (const auto& layer : layers_) {
return std::make_unique<TL>(env, std::move(layers), next, &win_); layers.push_back(layer->Clone(env, next));
}
return std::make_unique<TL>(env, std::move(layers), next);
} }
void TL::Handle(const Event& ev) noexcept { void TL::PostHandle(const Event& ev) noexcept {
nf7::FileBase::Handle(ev);
switch (ev.type) { switch (ev.type) {
case Event::kAdd: case Event::kAdd:
if (layers_.size() == 0) { if (layers_.size() == 0) {
@@ -1233,179 +1114,177 @@ void TL::Handle(const Event& ev) noexcept {
} }
} }
void TL::Update() noexcept { void TL::PostUpdate() noexcept {
nf7::FileBase::Update(); // display param panel window
if (win_.shown()) {
const auto em = ImGui::GetFontSize();
const auto id = nf7::gui::Window::ConcatId(*this, "Parameter Panel");
if (std::exchange(param_panel_request_focus_, false)) {
ImGui::SetNextWindowFocus();
}
ImGui::SetNextWindowSize({16*em, 16*em}, ImGuiCond_FirstUseEver);
if (ImGui::Begin(id.c_str())) {
ParamPanel();
}
ImGui::End();
}
// update children
for (const auto& layer : layers_) { for (const auto& layer : layers_) {
for (const auto& item : layer->items()) { for (const auto& item : layer->items()) {
item->file().Update(); item->file().Update();
} }
} }
UpdateEditorWindow(); // squash queued commands
UpdateParamPanelWindow();
if (history_.Squash()) { if (history_.Squash()) {
env().ExecMain(std::make_shared<nf7::GenericContext>(*this), env().ExecMain(std::make_shared<nf7::GenericContext>(*this),
[this]() { Touch(); }); [this]() { Touch(); });
} }
} }
void TL::UpdateMenu() noexcept { void TL::UpdateMenu() noexcept {
if (ImGui::MenuItem("editor", nullptr, &win_.shown()) && win_.shown()) { win_.MenuItem();
win_.SetFocus();
}
if (ImGui::MenuItem("I/O list")) {
popup_socket_.Open(seq_inputs_, seq_outputs_);
}
} }
void TL::UpdateWidget() noexcept {
ImGui::TextUnformatted("Sequencer/Timeline");
void TL::TimelineEditor() noexcept {
LambdaSelector();
if (ImGui::Button("Editor")) { // timeline
win_.SetFocus(); if (tl_.Begin()) {
} // layer headers
if (ImGui::Button("I/O list")) { for (size_t i = 0; i < layers_.size(); ++i) {
popup_socket_.Open(seq_inputs_, seq_outputs_); auto& layer = layers_[i];
} tl_.NextLayerHeader(layer.get(), layer->height());
ImGui::PushID(layer.get());
popup_socket_.Update(); layer->UpdateHeader(i);
} ImGui::PopID();
void TL::UpdateEditorWindow() noexcept {
if (win_.shownInCurrentFrame()) {
const auto em = ImGui::GetFontSize();
ImGui::SetNextWindowSizeConstraints({32*em, 16*em}, {1e8, 1e8});
}
if (win_.Begin()) {
UpdateLambdaSelector();
// timeline
if (tl_.Begin()) {
// layer headers
for (size_t i = 0; i < layers_.size(); ++i) {
auto& layer = layers_[i];
tl_.NextLayerHeader(layer.get(), layer->height());
ImGui::PushID(layer.get());
layer->UpdateHeader(i);
ImGui::PopID();
}
if (tl_.BeginBody()) {
// context menu on timeline
if (ImGui::BeginPopupContextWindow()) {
if (ImGui::IsWindowAppearing()) {
action_time_ = tl_.mouseTime();
action_layer_ = 0;
if (auto layer = reinterpret_cast<TL::Layer*>(tl_.mouseLayer())) {
action_layer_ = layer->index();
}
}
if (ImGui::MenuItem("add new item")) {
if (action_layer_ < layers_.size()) {
popup_add_item_.Open(action_time_, *layers_[action_layer_]);
}
}
if (selected_.size()) {
ImGui::Separator();
if (ImGui::MenuItem("deselect")) {
Deselect();
}
}
ImGui::Separator();
if (ImGui::MenuItem("undo", nullptr, false, !!history_.prev())) {
ExecUnDo();
}
if (ImGui::MenuItem("redo", nullptr, false, !!history_.next())) {
ExecReDo();
}
ImGui::Separator();
if (ImGui::MenuItem("I/O socket list")) {
popup_socket_.Open(seq_inputs_, seq_outputs_);
}
ImGui::EndPopup();
}
// layer body
for (auto& layer : layers_) {
tl_.NextLayer(layer.get(), layer->height());
for (auto& item : layer->items()) {
const auto& t = item->displayTiming();
const bool select = selected_.contains(item.get());
ImGui::PushStyleColor(
ImGuiCol_ChildBg, ImGui::GetColorU32(ImGuiCol_FrameBg, 0.3f));
ImGui::PushStyleColor(
ImGuiCol_Border,
ImGui::GetColorU32(select? ImGuiCol_FrameBgActive: ImGuiCol_Border));
ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 2);
const bool shown = tl_.BeginItem(item.get(), t.begin(), t.end());
ImGui::PopStyleVar(1);
ImGui::PopStyleColor(2);
if (shown) {
item->Update();
}
tl_.EndItem();
}
}
}
tl_.EndBody();
// mouse curosr
constexpr auto kFlags =
ImGuiHoveredFlags_ChildWindows |
ImGuiHoveredFlags_AllowWhenBlockedByPopup;
if (ImGui::IsWindowHovered(kFlags)) {
tl_.Cursor(
"mouse",
tl_.GetTimeFromScreenX(ImGui::GetMousePos().x),
ImGui::GetColorU32(ImGuiCol_TextDisabled, .5f));
}
// frame cursor
tl_.Cursor("cursor", cursor_, ImGui::GetColorU32(ImGuiCol_Text, .5f));
// running sessions
if (lambda_) {
const auto now = nf7::Env::Clock::now();
for (auto& wss : lambda_->sessions()) {
auto ss = wss.lock();
if (!ss || ss->done()) continue;
const auto elapsed =
static_cast<float>(
std::chrono::duration_cast<std::chrono::milliseconds>(
now - ss->lastActive()).count()) / 1000;
const auto alpha = 1.f - std::clamp(elapsed, 0.f, 1.f)*0.6f;
const auto color = IM_COL32(255, 0, 0, static_cast<uint8_t>(alpha*255));
tl_.Cursor("S", ss->time(), color);
if (ss->layer() > 0) {
tl_.Arrow(ss->time(), ss->layer()-1, color);
}
}
}
HandleTimelineAction();
} }
tl_.End();
// key bindings if (tl_.BeginBody()) {
const bool focused = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows); // context menu on timeline
if (focused && !ImGui::IsAnyItemFocused()) { if (ImGui::BeginPopupContextWindow()) {
if (!lambda_ || lambda_->depth() == 0) { if (ImGui::IsWindowAppearing()) {
if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow)) { action_time_ = tl_.mouseTime();
if (cursor_ > 0) MoveCursorTo(cursor_-1); action_layer_ = 0;
} else if (ImGui::IsKeyPressed(ImGuiKey_RightArrow)) { if (auto layer = reinterpret_cast<TL::Layer*>(tl_.mouseLayer())) {
MoveCursorTo(cursor_+1); action_layer_ = layer->index();
}
}
if (action_layer_ < layers_.size()) {
if (ImGui::BeginMenu("add new item")) {
ItemAdder();
ImGui::EndMenu();
}
}
if (selected_.size()) {
ImGui::Separator();
if (ImGui::MenuItem("deselect")) {
Deselect();
}
}
ImGui::Separator();
if (ImGui::MenuItem("undo", nullptr, false, !!history_.prev())) {
ExecUnDo();
}
if (ImGui::MenuItem("redo", nullptr, false, !!history_.next())) {
ExecReDo();
}
ImGui::EndPopup();
}
// layer body
for (auto& layer : layers_) {
tl_.NextLayer(layer.get(), layer->height());
for (auto& item : layer->items()) {
const auto& t = item->displayTiming();
const bool select = selected_.contains(item.get());
ImGui::PushStyleColor(
ImGuiCol_ChildBg, ImGui::GetColorU32(ImGuiCol_FrameBg, 0.3f));
ImGui::PushStyleColor(
ImGuiCol_Border,
ImGui::GetColorU32(select? ImGuiCol_FrameBgActive: ImGuiCol_Border));
ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 2);
const bool shown = tl_.BeginItem(item.get(), t.begin(), t.end());
ImGui::PopStyleVar(1);
ImGui::PopStyleColor(2);
if (shown) {
item->Update();
}
tl_.EndItem();
} }
} }
} }
tl_.EndBody();
// mouse curosr
constexpr auto kFlags =
ImGuiHoveredFlags_ChildWindows |
ImGuiHoveredFlags_AllowWhenBlockedByPopup;
if (ImGui::IsWindowHovered(kFlags)) {
tl_.Cursor(
"mouse",
tl_.GetTimeFromScreenX(ImGui::GetMousePos().x),
ImGui::GetColorU32(ImGuiCol_TextDisabled, .5f));
}
// frame cursor
tl_.Cursor("cursor", cursor_, ImGui::GetColorU32(ImGuiCol_Text, .5f));
// running sessions
if (lambda_) {
const auto now = nf7::Env::Clock::now();
for (auto& wss : lambda_->sessions()) {
auto ss = wss.lock();
if (!ss || ss->done()) continue;
const auto elapsed =
static_cast<float>(
std::chrono::duration_cast<std::chrono::milliseconds>(
now - ss->lastActive()).count()) / 1000;
const auto alpha = 1.f - std::clamp(elapsed, 0.f, 1.f)*0.6f;
const auto color = IM_COL32(255, 0, 0, static_cast<uint8_t>(alpha*255));
tl_.Cursor("S", ss->time(), color);
if (ss->layer() > 0) {
tl_.Arrow(ss->time(), ss->layer()-1, color);
}
}
}
HandleTimelineAction();
}
tl_.End();
// key bindings
const bool focused = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows);
if (focused && !ImGui::IsAnyItemFocused()) {
if (!lambda_ || lambda_->depth() == 0) {
if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow)) {
if (cursor_ > 0) MoveCursorTo(cursor_-1);
} else if (ImGui::IsKeyPressed(ImGuiKey_RightArrow)) {
MoveCursorTo(cursor_+1);
}
}
} }
win_.End();
} }
void TL::UpdateLambdaSelector() noexcept { void TL::ParamPanel() noexcept {
if (auto item = param_panel_target_) {
if (item->seq().flags() & Sequencer::kParamPanel) {
TL::Editor ed {*item};
item->seq().UpdateParamPanel(ed);
} else {
ImGui::TextUnformatted("item doesn't have parameter panel");
}
} else {
ImGui::TextUnformatted("no item selected");
}
}
void TL::LambdaSelector() noexcept {
const auto current_lambda = const auto current_lambda =
lambda_? nf7::gui::GetParentContextDisplayName(*lambda_): "(unselected)"; lambda_? nf7::gui::GetParentContextDisplayName(*lambda_): "(unselected)";
if (ImGui::BeginCombo("##lambda", current_lambda.c_str())) { if (ImGui::BeginCombo("##lambda", current_lambda.c_str())) {
@@ -1435,6 +1314,84 @@ void TL::UpdateLambdaSelector() noexcept {
ImGui::EndCombo(); ImGui::EndCombo();
} }
} }
void TL::ItemAdder() noexcept {
// parameters
auto& layer = *layers_[action_layer_];
auto time = action_time_;
uint64_t dur = static_cast<uint64_t>(4.f / tl_.zoom());
if (auto item = layer.FindItemAfter(time)) {
dur = std::min(dur, item->timing().begin() - time);
}
// header and initialization
static const nf7::File::TypeInfo* type;
if (ImGui::IsWindowAppearing()) {
type = nullptr;
}
ImGui::TextUnformatted("Sequencer/Timeline: adding new item...");
const auto em = ImGui::GetFontSize();
// type list
bool exec = false;
if (ImGui::BeginListBox("type", {16*em, 8*em})) {
for (auto& p : nf7::File::registry()) {
const auto& t = *p.second;
if (!t.flags().contains("nf7::Sequencer")) {
continue;
}
constexpr auto kFlags =
ImGuiSelectableFlags_SpanAllColumns |
ImGuiSelectableFlags_AllowItemOverlap;
if (ImGui::Selectable(t.name().c_str(), type == &t, kFlags)) {
type = &t;
}
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
t.UpdateTooltip();
ImGui::EndTooltip();
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
exec = true;
}
}
}
ImGui::EndListBox();
}
// validation
bool valid = true;
if (type == nullptr) {
ImGui::Bullet(); ImGui::TextUnformatted("type not selected");
valid = false;
}
if (dur == 0) {
ImGui::Bullet(); ImGui::TextUnformatted("no space to insert new item");
valid = false;
}
// ok button
ImGui::BeginDisabled(!valid);
if (ImGui::Button("ok")) {
exec = true;
}
ImGui::EndDisabled();
// adding
if (exec && valid) {
ImGui::CloseCurrentPopup();
auto file = type->Create(env());
auto timing = TL::Timing::BeginDur(time, dur);
auto item = std::make_unique<TL::Item>(next_++, std::move(file), timing);
auto cmd = std::make_unique<TL::Layer::ItemSwapCommand>(layer, std::move(item));
auto ctx = std::make_shared<nf7::GenericContext>(*this, "adding new item");
history_.Add(std::move(cmd)).ExecApply(ctx);
}
}
void TL::HandleTimelineAction() noexcept { void TL::HandleTimelineAction() noexcept {
auto item = reinterpret_cast<TL::Item*>(tl_.actionTarget()); auto item = reinterpret_cast<TL::Item*>(tl_.actionTarget());
const auto action_time = tl_.actionTime(); const auto action_time = tl_.actionTime();
@@ -1497,32 +1454,6 @@ void TL::HandleTimelineAction() noexcept {
} }
} }
void TL::UpdateParamPanelWindow() noexcept {
if (!win_.shown()) return;
const auto name = abspath().Stringify() + " | Parameter Panel";
if (std::exchange(param_panel_request_focus_, false)) {
ImGui::SetNextWindowFocus();
}
const auto em = ImGui::GetFontSize();
ImGui::SetNextWindowSize({16*em, 16*em}, ImGuiCond_FirstUseEver);
if (ImGui::Begin(name.c_str())) {
if (auto item = param_panel_target_) {
if (item->seq().flags() & Sequencer::kParamPanel) {
TL::Editor ed {*item};
item->seq().UpdateParamPanel(ed);
} else {
ImGui::TextUnformatted("item doesn't have parameter panel");
}
} else {
ImGui::TextUnformatted("no item selected");
}
}
ImGui::End();
}
void TL::Layer::UpdateHeader(size_t idx) noexcept { void TL::Layer::UpdateHeader(size_t idx) noexcept {
index_ = idx; index_ = idx;
@@ -1574,6 +1505,7 @@ void TL::Layer::UpdateHeader(size_t idx) noexcept {
} }
} }
} }
void TL::Item::Update() noexcept { void TL::Item::Update() noexcept {
assert(owner_); assert(owner_);
assert(layer_); assert(layer_);
@@ -1589,6 +1521,8 @@ void TL::Item::Update() noexcept {
if (ImGui::MenuItem("remove")) { if (ImGui::MenuItem("remove")) {
layer_->ExecRemoveItem(*this); layer_->ExecRemoveItem(*this);
} }
nf7::gui::FileMenuItems(*file_);
if (seq_->flags() & nf7::Sequencer::kMenu) { if (seq_->flags() & nf7::Sequencer::kMenu) {
ImGui::Separator(); ImGui::Separator();
seq_->UpdateMenu(ed); seq_->UpdateMenu(ed);
@@ -1615,33 +1549,6 @@ void TL::Item::Update() noexcept {
} }
} }
void TL::AddItemPopup::Update() noexcept {
if (Popup::Begin()) {
ImGui::TextUnformatted("Sequencer/Timeline: adding new item...");
if (factory_.Update()) {
auto& layer = *target_layer_;
auto time = target_time_;
uint64_t dur = static_cast<uint64_t>(4.f / owner_->tl_.zoom());
if (auto item = layer.FindItemAfter(time)) {
dur = std::min(dur, item->timing().begin() - time);
}
if (dur > 0) {
ImGui::CloseCurrentPopup();
auto file = factory_.type().Create(owner_->env());
auto timing = TL::Timing::BeginDur(time, dur);
auto item = std::make_unique<TL::Item>(owner_->next_++, std::move(file), timing);
auto cmd = std::make_unique<TL::Layer::ItemSwapCommand>(layer, std::move(item));
auto ctx = std::make_shared<nf7::GenericContext>(*owner_, "adding new item");
owner_->history_.Add(std::move(cmd)).ExecApply(ctx);
}
}
ImGui::EndPopup();
}
}
} }
} // namespace nf7 } // namespace nf7

View File

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

@@ -3,6 +3,7 @@
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <unordered_set> #include <unordered_set>
#include <vector>
#include <imgui.h> #include <imgui.h>
#include <imgui_internal.h> #include <imgui_internal.h>
@@ -11,16 +12,19 @@
#include <yas/types/std/unordered_set.hpp> #include <yas/types/std/unordered_set.hpp>
#include <yas/types/std/string.hpp> #include <yas/types/std/string.hpp>
#include <tracy/Tracy.hpp>
#include "nf7.hh" #include "nf7.hh"
#include "common/config.hh"
#include "common/dir.hh" #include "common/dir.hh"
#include "common/dir_item.hh" #include "common/dir_item.hh"
#include "common/file_base.hh" #include "common/file_base.hh"
#include "common/generic_context.hh" #include "common/generic_context.hh"
#include "common/generic_dir.hh"
#include "common/generic_type_info.hh" #include "common/generic_type_info.hh"
#include "common/gui.hh"
#include "common/gui_dnd.hh" #include "common/gui_dnd.hh"
#include "common/gui_file.hh"
#include "common/gui_popup.hh"
#include "common/gui_window.hh" #include "common/gui_window.hh"
#include "common/ptr_selector.hh" #include "common/ptr_selector.hh"
#include "common/yas_nf7.hh" #include "common/yas_nf7.hh"
@@ -30,240 +34,96 @@ namespace nf7 {
namespace { namespace {
class Dir final : public nf7::FileBase, class Dir final : public nf7::FileBase,
public nf7::Dir,
public nf7::DirItem { public nf7::DirItem {
public: public:
static inline const GenericTypeInfo<Dir> kType = {"System/Dir", {"nf7::DirItem"}}; static inline const nf7::GenericTypeInfo<Dir> kType = {
static constexpr const char* kTypeDescription = "generic directory"; "System/Dir", {"nf7::DirItem"}, "generic directory",
};
using ItemMap = std::map<std::string, std::unique_ptr<File>>; using ItemMap = std::map<std::string, std::unique_ptr<File>>;
Dir(nf7::Env& env, ItemMap&& items = {}, const gui::Window* src = nullptr) noexcept : Dir(nf7::Env& env, nf7::GenericDir::ItemMap&& items = {}) noexcept :
nf7::FileBase(kType, env, {&widget_popup_, &add_popup_, &rename_popup_}), nf7::FileBase(kType, env),
nf7::DirItem(nf7::DirItem::kTree | nf7::DirItem(nf7::DirItem::kTree |
nf7::DirItem::kMenu | nf7::DirItem::kMenu |
nf7::DirItem::kTooltip | nf7::DirItem::kTooltip |
nf7::DirItem::kDragDropTarget), nf7::DirItem::kDragDropTarget),
items_(std::move(items)), win_(*this, "TreeView System/Dir", src), dir_(*this, std::move(items)), win_(*this, "Tree View") {
widget_popup_(*this), add_popup_(*this), rename_popup_(*this) { win_.onConfig = []() {
const auto em = ImGui::GetFontSize();
ImGui::SetNextWindowSize({8*em, 8*em}, ImGuiCond_FirstUseEver);
};
win_.onUpdate = [this]() { TreeView(); };
} }
Dir(nf7::Deserializer& ar) : Dir(ar.env()) { Dir(nf7::Deserializer& ar) : Dir(ar.env()) {
ar(opened_, win_); ar(dir_, 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(nf7::Serializer& ar) const noexcept override { void Serialize(nf7::Serializer& ar) const noexcept override {
ar(opened_, win_); ar(dir_, opened_, win_);
ar(static_cast<uint64_t>(items_.size()));
for (auto& p : items_) {
ar(p.first, p.second);
}
} }
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override { std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
ItemMap items; return std::make_unique<Dir>(env, dir_.CloneItems(env));
for (const auto& item : items_) {
items[item.first] = item.second->Clone(env);
}
return std::make_unique<Dir>(env, std::move(items));
} }
File* Find(std::string_view name) const noexcept override {
auto itr = items_.find(std::string(name));
if (itr == items_.end()) return nullptr;
return itr->second.get();
}
File& Add(std::string_view name, std::unique_ptr<File>&& f) override {
const auto sname = std::string(name);
auto [itr, ok] = items_.emplace(sname, std::move(f));
if (!ok) throw DuplicateException("item name duplication: "+sname);
auto& ret = *itr->second;
if (id()) ret.MoveUnder(*this, name);
return ret;
}
std::unique_ptr<File> Remove(std::string_view name) noexcept override {
auto itr = items_.find(std::string(name));
if (itr == items_.end()) return nullptr;
auto ret = std::move(itr->second);
items_.erase(itr);
if (id()) ret->Isolate();
return ret;
}
void Update() noexcept override;
void UpdateTree() noexcept override; void UpdateTree() noexcept override;
void UpdateMenu() noexcept override; void UpdateMenu() noexcept override;
void UpdateTooltip() noexcept override; void UpdateTooltip() noexcept override;
void UpdateDragDropTarget() noexcept override; void UpdateDragDropTarget() noexcept override;
void Handle(const Event& ev) noexcept override { void PostHandle(const Event& ev) noexcept override {
nf7::FileBase::Handle(ev);
switch (ev.type) { switch (ev.type) {
case Event::kAdd: case Event::kAdd:
// force to show window if this is the root // force to show window if this is the root
if (name() == "$") { if (name() == "$") {
win_.shown() = true; win_.Show();
} }
for (const auto& item : items_) item.second->MoveUnder(*this, item.first); return;
break;
case Event::kRemove:
for (const auto& item : items_) item.second->Isolate();
break;
case Event::kReqFocus:
win_.SetFocus();
break;
default: default:
break; return;
} }
} }
File::Interface* interface(const std::type_info& t) noexcept override { File::Interface* interface(const std::type_info& t) noexcept override {
return InterfaceSelector<nf7::Dir, nf7::DirItem>(t).Select(this); return nf7::InterfaceSelector<nf7::Dir, nf7::DirItem>(t).Select(this, &dir_);
} }
private: private:
// persistent params nf7::GenericDir dir_;
ItemMap items_;
gui::Window win_;
std::unordered_set<std::string> opened_; std::unordered_set<std::string> opened_;
gui::Window win_;
std::vector<std::pair<std::string, std::unique_ptr<nf7::File>>> trash_;
// GUI popup static bool TestFlags(nf7::File& f, nf7::DirItem::Flags flags) noexcept
class WidgetPopup final : try {
public nf7::FileBase::Feature, private nf7::gui::Popup { return f.interfaceOrThrow<nf7::DirItem>().flags() & flags;
public: } catch (nf7::Exception&) {
WidgetPopup(Dir& owner) noexcept : return false;
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)) {
ret += "_dup";
}
return ret;
} }
// imgui widgets
void TreeView() noexcept;
void ItemAdder() noexcept;
void ItemRenamer(const std::string& name) noexcept;
bool ValidateName(const std::string& name) noexcept;
}; };
void Dir::Update() noexcept {
nf7::FileBase::Update();
const auto em = ImGui::GetFontSize();
// update children
for (const auto& item : items_) {
ImGui::PushID(item.second.get());
item.second->Update();
ImGui::PopID();
}
// tree view window
if (win_.shownInCurrentFrame()) {
ImGui::SetNextWindowSize({8*em, 8*em}, ImGuiCond_FirstUseEver);
}
if (win_.Begin()) {
if (ImGui::BeginPopupContextWindow()) {
UpdateMenu();
ImGui::EndPopup();
}
UpdateTree();
if (nf7::gui::dnd::IsFirstAccept()) {
ImGui::SetCursorPos({0, 0});
ImGui::Dummy(ImGui::GetContentRegionAvail());
if (ImGui::BeginDragDropTarget()) {
UpdateDragDropTarget();
ImGui::EndDragDropTarget();
}
}
}
win_.End();
}
void Dir::UpdateTree() noexcept { void Dir::UpdateTree() noexcept {
for (const auto& item : items_) { for (const auto& item : dir_.items()) {
ImGuiTreeNodeFlags flags =
ImGuiTreeNodeFlags_NoTreePushOnOpen |
ImGuiTreeNodeFlags_SpanFullWidth;
const auto& name = item.first; const auto& name = item.first;
auto& file = *item.second; auto& file = *item.second;
ImGui::PushID(&file); ImGui::PushID(&file);
auto* ditem = file.interface<nf7::DirItem>(); auto* ditem = file.interface<nf7::DirItem>();
if (ditem && !(ditem->flags() & DirItem::kTree)) { const auto flags = ditem? ditem->flags(): 0;
flags |= ImGuiTreeNodeFlags_Leaf;
ImGuiTreeNodeFlags node_flags =
ImGuiTreeNodeFlags_NoTreePushOnOpen |
ImGuiTreeNodeFlags_SpanFullWidth;
if (!(flags & DirItem::kTree)) {
node_flags |= ImGuiTreeNodeFlags_Leaf;
} }
const bool opened = opened_.contains(name); const bool opened = opened_.contains(name);
@@ -272,72 +132,58 @@ void Dir::UpdateTree() noexcept {
} }
const auto top = ImGui::GetCursorPosY(); const auto top = ImGui::GetCursorPosY();
const bool open = ImGui::TreeNodeEx(item.second.get(), flags, "%s", name.c_str()); const bool open = ImGui::TreeNodeEx(
item.second.get(), node_flags, "%s", name.c_str());
if (!opened && open) { if (!opened && open) {
opened_.insert(name); opened_.insert(name);
} else if (opened && !open) { } else if (opened && !open) {
opened_.erase(name); opened_.erase(name);
} }
if (ditem && (ditem->flags() & DirItem::kWidget)) {
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
widget_popup_.Open(file);
}
}
// tooltip // tooltip
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip(); ImGui::BeginTooltip();
ImGui::TextUnformatted(file.type().name().c_str()); nf7::gui::FileTooltip(file);
ImGui::SameLine();
ImGui::TextDisabled(file.abspath().Stringify().c_str());
if (ditem && (ditem->flags() & DirItem::kTooltip)) {
ImGui::Indent();
ditem->UpdateTooltip();
ImGui::Unindent();
}
ImGui::EndTooltip(); ImGui::EndTooltip();
} }
// send nf7::File::Event::kReqFocus on double click
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
env().Handle({.id = file.id(), .type = nf7::File::Event::kReqFocus});
}
// context menu // context menu
if (ImGui::BeginPopupContextItem()) { if (ImGui::BeginPopupContextItem()) {
if (ditem && (ditem->flags() & DirItem::kWidget)) { ImGui::BeginDisabled(flags & nf7::DirItem::kImportant);
if (ImGui::MenuItem("open widget")) {
widget_popup_.Open(file);
}
}
if (ImGui::MenuItem("copy path")) {
ImGui::SetClipboardText(file.abspath().Stringify().c_str());
}
ImGui::Separator();
if (ImGui::MenuItem("remove")) { if (ImGui::MenuItem("remove")) {
env().ExecMain( env().ExecMain(
std::make_shared<nf7::GenericContext>(*this, "removing item"), std::make_shared<nf7::GenericContext>(*this, "removing item"),
[this, name]() { Remove(name); }); [this, name]() { trash_.emplace_back(name, dir_.Remove(name)); });
} }
if (ImGui::MenuItem("rename")) { if (ImGui::BeginMenu("rename")) {
rename_popup_.Open(name); ItemRenamer(name);
ImGui::EndMenu();
} }
if (ImGui::MenuItem("renew")) { if (ImGui::MenuItem("renew")) {
env().ExecMain( env().ExecMain(
std::make_shared<nf7::GenericContext>(*this, "removing item"), std::make_shared<nf7::GenericContext>(*this, "renewing item"),
[this, name]() { Add(name, Remove(name)); }); [this, name]() { dir_.Renew(name); });
} }
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("re-initialize the item by re-adding after removing"); ImGui::SetTooltip("re-initialize the item by re-adding after removing");
} }
ImGui::Separator(); if (ImGui::MenuItem("clone")) {
if (ImGui::MenuItem("add new sibling")) { env().ExecMain(
add_popup_.Open(); std::make_shared<nf7::GenericContext>(*this, "duplicating item"),
[this, name, &file]() { dir_.Add(dir_.GetUniqueName(name), file.Clone(env())); });
} }
ImGui::EndDisabled();
ImGui::Separator();
nf7::gui::FileMenuItems(file);
if (ditem && (ditem->flags() & DirItem::kMenu)) {
ImGui::Separator();
ditem->UpdateMenu();
}
ImGui::EndPopup(); ImGui::EndPopup();
} }
@@ -353,7 +199,7 @@ void Dir::UpdateTree() noexcept {
// displayed contents // displayed contents
if (open) { if (open) {
ImGui::TreePush(&file); ImGui::TreePush(&file);
if (ditem && (ditem->flags() & DirItem::kTree)) { if (flags & DirItem::kTree) {
ditem->UpdateTree(); ditem->UpdateTree();
} }
ImGui::TreePop(); ImGui::TreePop();
@@ -362,7 +208,7 @@ void Dir::UpdateTree() noexcept {
// dnd target // dnd target
if (nf7::gui::dnd::IsFirstAccept()) { if (nf7::gui::dnd::IsFirstAccept()) {
if (ditem && (ditem->flags() & DirItem::kDragDropTarget)) { if (flags & DirItem::kDragDropTarget) {
ImGui::SetCursorPosY(top); ImGui::SetCursorPosY(top);
ImGui::Dummy({ImGui::GetContentRegionAvail().x, bottom-top}); ImGui::Dummy({ImGui::GetContentRegionAvail().x, bottom-top});
if (ImGui::BeginDragDropTarget()) { if (ImGui::BeginDragDropTarget()) {
@@ -377,21 +223,50 @@ void Dir::UpdateTree() noexcept {
} }
} }
void Dir::UpdateMenu() noexcept { void Dir::UpdateMenu() noexcept {
if (ImGui::MenuItem("add new child")) { if (ImGui::BeginMenu("add new child")) {
add_popup_.Open(); ItemAdder();
ImGui::EndMenu();
}
if (ImGui::BeginMenu("restore item", trash_.size() > 0)) {
for (auto itr = trash_.rbegin(); itr < trash_.rend();) {
const auto idx = std::distance(trash_.rbegin(), itr);
const auto& type = itr->second->type();
const auto id = itr->first + " (" + type.name() + ") ##" + std::to_string(idx);
const auto uniq = !dir_.Find(itr->first);
if (ImGui::MenuItem(id.c_str(), nullptr, false, uniq)) {
auto ctx = std::make_shared<nf7::GenericContext>(*this, "restoring an item");
auto p = std::make_shared<std::pair<std::string, std::unique_ptr<nf7::File>>>(std::move(*itr)); // this sucks
env().ExecMain(ctx, [this, p]() mutable {
dir_.Add(p->first, std::move(p->second));
});
trash_.erase(std::next(itr).base());
itr = trash_.rbegin()+idx;
} else {
++itr;
}
}
ImGui::EndMenu();
} }
ImGui::Separator(); ImGui::Separator();
ImGui::MenuItem("TreeView", nullptr, &win_.shown()); win_.MenuItem();
} }
void Dir::UpdateTooltip() noexcept { void Dir::UpdateTooltip() noexcept {
ImGui::Text("children: %zu", items_.size()); ImGui::Text("children: %zu", dir_.items().size());
} }
void Dir::UpdateDragDropTarget() noexcept void Dir::UpdateDragDropTarget() noexcept
try { try {
nf7::File::Path p; nf7::File::Path p;
if (auto pay = gui::dnd::Peek<Path>(gui::dnd::kFilePath, p)) { if (auto pay = gui::dnd::Peek<Path>(gui::dnd::kFilePath, p)) {
auto& target = ResolveOrThrow(p); auto& target = ResolveOrThrow(p);
if (target.parent() == this) { if (target.parent() == nullptr || target.parent() == this) {
return;
}
auto& ditem = target.interfaceOrThrow<nf7::DirItem>();
if (ditem.flags() & nf7::DirItem::kImportant) {
ImGui::SetTooltip("cannot move an important file");
return; return;
} }
@@ -401,94 +276,169 @@ try {
parent = parent->parent(); parent = parent->parent();
} }
auto& dir = target.parent()->interfaceOrThrow<nf7::Dir>(); const auto pid = target.parent()->id();
auto& src = target.parent()->interfaceOrThrow<nf7::Dir>();
nf7::gui::dnd::DrawRect(); nf7::gui::dnd::DrawRect();
if (pay->IsDelivery()) { if (pay->IsDelivery()) {
env().ExecMain( env().ExecMain(
std::make_shared<nf7::GenericContext>(*this, "moving an item"), std::make_shared<nf7::GenericContext>(*this, "moving an item"),
[this, &dir, name = target.name()]() { Add(GetUniqueName(name), dir.Remove(name)); }); [this, pid, &src, name = target.name()]() {
if (env().GetFile(pid)) {
if (auto f = src.Remove(name)) {
dir_.Add(dir_.GetUniqueName(name), std::move(f));
}
}
});
} }
} }
} catch (nf7::File::NotImplementedException&) {
ImGui::SetTooltip("the file is not an item of nf7::Dir");
} catch (nf7::Exception&) { } catch (nf7::Exception&) {
} }
void Dir::WidgetPopup::Update() noexcept {
if (nf7::gui::Popup::Begin()) { void Dir::TreeView() noexcept {
if (auto item = target_->interface<nf7::DirItem>()) { if (ImGui::BeginPopupContextWindow()) {
ImGui::PushID(item); UpdateMenu();
item->UpdateWidget();
ImGui::PopID();
}
ImGui::EndPopup(); ImGui::EndPopup();
} }
} UpdateTree();
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(); if (nf7::gui::dnd::IsFirstAccept()) {
auto ctx = std::make_shared<nf7::GenericContext>(*owner_, "adding new item"); ImGui::SetCursorPos({0, 0});
auto task = [this, &env]() { owner_->Add(factory_.name(), factory_.Create(env)); }; ImGui::Dummy(ImGui::GetContentRegionAvail());
env.ExecMain(ctx, std::move(task)); if (ImGui::BeginDragDropTarget()) {
UpdateDragDropTarget();
ImGui::EndDragDropTarget();
} }
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; void Dir::ItemAdder() noexcept {
if (ImGui::IsWindowAppearing()) ImGui::SetKeyboardFocusHere(); static const nf7::File::TypeInfo* type;
if (ImGui::InputText("after", &after_, ImGuiInputTextFlags_EnterReturnsTrue)) { static std::string name;
submit = true; static bool type_filtered;
}
bool err = false; if (ImGui::IsWindowAppearing()) {
if (!owner_->Find(before_)) { type = nullptr;
ImGui::Bullet(); ImGui::TextUnformatted("before is invalid: missing target"); name = dir_.GetUniqueName("new_file");
err = true; type_filtered = true;
} }
if (owner_->Find(after_)) { ImGui::TextUnformatted("System/Dir: adding new file...");
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) { const auto em = ImGui::GetFontSize();
if (ImGui::Button("ok")) {
submit = true; bool exec = false;
if (ImGui::BeginListBox("type", {16*em, 8*em})) {
for (auto& p : nf7::File::registry()) {
const auto& t = *p.second;
if (type_filtered && !t.flags().contains("nf7::DirItem")) {
continue;
}
constexpr auto kFlags =
ImGuiSelectableFlags_SpanAllColumns |
ImGuiSelectableFlags_AllowItemOverlap;
if (ImGui::Selectable(t.name().c_str(), type == &t, kFlags)) {
type = &t;
} }
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
ImGui::SetTooltip( ImGui::BeginTooltip();
"rename '%s' to '%s' on '%s'", t.UpdateTooltip();
before_.c_str(), after_.c_str(), ImGui::EndTooltip();
owner_->abspath().Stringify().c_str());
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
exec = true;
}
} }
} }
if (submit) { if (type_filtered) {
ImGui::CloseCurrentPopup(); ImGui::Selectable("(show all types)");
if (ImGui::IsItemHovered()) {
auto ctx = std::make_shared<nf7::GenericContext>(*owner_, "renaming item"); ImGui::BeginTooltip();
auto task = [this, before = std::move(before_), after = std::move(after_)]() { ImGui::TextUnformatted("double click to allow you to place system files");
auto f = owner_->Remove(before); ImGui::TextDisabled(" -- great power brings DESTRUCTION and CREATION");
if (!f) throw nf7::Exception {"missing target"}; ImGui::EndTooltip();
owner_->Add(after, std::move(f)); if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
}; type_filtered = false;
owner_->env().ExecMain(ctx, std::move(task)); }
}
} }
ImGui::EndPopup(); ImGui::EndListBox();
} }
ImGui::SetNextItemWidth(16*em);
if (ImGui::InputText("name", &name, ImGuiInputTextFlags_EnterReturnsTrue)) {
exec = true;
}
bool valid = ValidateName(name);
if (type == nullptr) {
ImGui::Bullet(); ImGui::TextUnformatted("type not selected");
valid = false;
}
ImGui::BeginDisabled(!valid);
if (ImGui::Button("ok")) {
exec = true;
}
ImGui::EndDisabled();
if (exec && valid) {
ImGui::CloseCurrentPopup();
env().ExecMain(
std::make_shared<nf7::GenericContext>(*this, "adding new item"),
[this]() { dir_.Add(name, type->Create(env())); });
}
}
void Dir::ItemRenamer(const std::string& name) noexcept {
static std::string editing_name;
static std::string err;
if (ImGui::IsWindowAppearing()) {
editing_name = name;
err = "";
}
bool exec = ImGui::InputText("##name", &editing_name, ImGuiInputTextFlags_EnterReturnsTrue);
ImGui::SameLine();
const auto pos = ImGui::GetCursorPos();
ImGui::NewLine();
bool valid = ValidateName(editing_name);
ImGui::SetCursorPos(pos);
ImGui::BeginDisabled(!valid);
if (ImGui::Button("apply")) {
exec = true;
}
ImGui::EndDisabled();
if (exec && valid) {
ImGui::CloseCurrentPopup();
env().ExecMain(
std::make_shared<nf7::GenericContext>(*this, "renaming item"),
[this, name]() { dir_.Rename(name, editing_name); });
}
}
bool Dir::ValidateName(const std::string& name) noexcept {
bool ret = true;
if (Find(name)) {
ImGui::Bullet(); ImGui::TextUnformatted("name duplicated");
ret = false;
}
try {
nf7::File::Path::ValidateTerm(name);
} catch (nf7::Exception& e) {
ImGui::Bullet(); ImGui::Text("invalid format: %s", e.msg().c_str());
ret = false;
}
return ret;
} }
} }

View File

@@ -1,5 +1,6 @@
#include <algorithm> #include <algorithm>
#include <memory> #include <memory>
#include <optional>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <unordered_map> #include <unordered_map>
@@ -8,113 +9,170 @@
#include <imgui.h> #include <imgui.h>
#include <yaml-cpp/yaml.h>
#include <yas/serialize.hpp>
#include <yas/types/std/vector.hpp>
#include "nf7.hh" #include "nf7.hh"
#include "common/dir_item.hh" #include "common/dir_item.hh"
#include "common/file_base.hh" #include "common/file_base.hh"
#include "common/file_holder.hh" #include "common/generic_config.hh"
#include "common/gui_file.hh"
#include "common/generic_context.hh" #include "common/generic_context.hh"
#include "common/generic_memento.hh" #include "common/generic_memento.hh"
#include "common/generic_type_info.hh" #include "common/generic_type_info.hh"
#include "common/life.hh" #include "common/generic_watcher.hh"
#include "common/gui.hh"
#include "common/logger.hh" #include "common/logger.hh"
#include "common/logger_ref.hh" #include "common/logger_ref.hh"
#include "common/node.hh" #include "common/node.hh"
#include "common/ptr_selector.hh" #include "common/ptr_selector.hh"
#include "common/value.hh" #include "common/value.hh"
#include "common/yaml_nf7.hh"
#include "common/yas_nf7.hh"
namespace nf7 { namespace nf7 {
namespace { namespace {
class Event final : public nf7::FileBase, public nf7::DirItem, public nf7::Node { class Event final : public nf7::FileBase,
public nf7::GenericConfig, public nf7::DirItem {
public: public:
static inline const nf7::GenericTypeInfo<Event> kType = { static inline const nf7::GenericTypeInfo<Event> kType = {
"System/Event", {"nf7::DirItem"}}; "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; class Lambda;
struct Data final { struct Data {
nf7::FileHolder::Tag handler; nf7::File::Path handler;
// feature switch
bool init = false;
bool key = false;
bool mouse = false;
std::vector<nf7::File::Path> watch;
Data() noexcept { }
void serialize(auto& ar) {
ar(handler, init, key, mouse, watch);
}
std::string Stringify() const noexcept {
YAML::Emitter st;
st << YAML::BeginMap;
st << YAML::Key << "handler";
st << YAML::Value << handler;
st << YAML::Key << "event";
st << YAML::BeginMap;
st << YAML::Key << "init";
st << YAML::Value << init;
st << YAML::Key << "key";
st << YAML::Value << key;
st << YAML::Key << "mouse";
st << YAML::Value << mouse;
st << YAML::Key << "watch";
st << YAML::Value << watch;
st << YAML::EndMap;
st << YAML::EndMap;
return {st.c_str(), st.size()};
}
void Parse(const std::string& str) {
const auto yaml = YAML::Load(str);
Data d;
d.handler = yaml["handler"].as<nf7::File::Path>();
const auto& ev = yaml["event"];
d.init = ev["init"].as<bool>();
d.key = ev["key"].as<bool>();
d.mouse = ev["mouse"].as<bool>();
d.watch = ev["watch"].as<std::vector<nf7::File::Path>>();
*this = std::move(d);
}
}; };
Event(nf7::Env& env, Data&& data = {}) noexcept : Event(nf7::Env& env, Data&& d = {}) noexcept :
nf7::FileBase(kType, env, {&logger_, &handler_, &handler_editor_}), nf7::FileBase(kType, env),
nf7::DirItem(nf7::DirItem::kMenu | nf7::DirItem::kWidget), nf7::GenericConfig(mem_),
nf7::Node(nf7::Node::kMenu_DirItem), nf7::DirItem(nf7::DirItem::kMenu |
life_(*this), logger_(*this), nf7::DirItem::kTooltip),
handler_(*this, "handler", mem_), log_(*this),
handler_editor_(handler_,
[](auto& t) { return t.flags().contains("nf7::Node"); }),
la_root_(std::make_shared<nf7::Node::Lambda>(*this)), la_root_(std::make_shared<nf7::Node::Lambda>(*this)),
mem_(std::move(data)) { mem_(*this, std::move(d)) {
handler_.onEmplace = [this]() { la_ = nullptr; }; mem_.onCommit = [this]() { SetUpWatcher(); };
} }
Event(nf7::Deserializer& ar) : Event(ar.env()) { Event(nf7::Deserializer& ar) : Event(ar.env()) {
ar(handler_); ar(mem_.data());
} }
void Serialize(nf7::Serializer& ar) const noexcept override { void Serialize(nf7::Serializer& ar) const noexcept override {
ar(handler_); ar(mem_.data());
} }
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override { std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<Event>(env, Data {data()}); return std::make_unique<Event>(env, Data {mem_.data()});
} }
std::shared_ptr<nf7::Node::Lambda> CreateLambda( void PostHandle(const nf7::File::Event& e) noexcept override {
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override; switch (e.type) {
std::span<const std::string> GetInputs() const noexcept override { case nf7::File::Event::kAdd:
static const std::vector<std::string> kInputs = {"value"}; if (mem_->init) {
return kInputs; env().ExecMain(
} std::make_shared<nf7::GenericContext>(*this, "trigger init event"),
std::span<const std::string> GetOutputs() const noexcept override { [this]() {
return {}; if (auto la = CreateLambdaIf()) {
la->Handle("init", nf7::Value::Pulse {}, la_root_);
}
});
}
return;
default:
return;
}
} }
void Update() noexcept override; void PostUpdate() noexcept override;
void UpdateMenu() noexcept override; void UpdateMenu() noexcept override;
void UpdateWidget() noexcept override; void UpdateTooltip() noexcept override;
nf7::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::Node>(t).Select(this); return nf7::InterfaceSelector<
nf7::Config, nf7::DirItem, nf7::Node>(t).Select(this);
} }
private: private:
nf7::Life<Event> life_; nf7::LoggerRef log_;
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_root_;
std::shared_ptr<nf7::Node::Lambda> la_; std::shared_ptr<nf7::Node::Lambda> la_;
nf7::GenericMemento<Data> mem_; nf7::GenericMemento<Data> mem_;
Data& data() noexcept { return mem_.data(); }
const Data& data() const noexcept { return mem_.data(); } class Watcher final : public nf7::Env::Watcher {
public:
Watcher(Event& f) noexcept : nf7::Env::Watcher(f.env()), f_(f) {
}
private:
Event& f_;
void Handle(const nf7::File::Event& e) noexcept override { f_.TriggerWatch(e); }
};
std::optional<Watcher> watch_;
std::span<const std::string> GetHandlerInputs() noexcept nf7::Node& GetHandler() const {
try { return ResolveOrThrow(mem_->handler).interfaceOrThrow<nf7::Node>();
return handler_.GetFileOrThrow().interfaceOrThrow<nf7::Node>().GetInputs();
} catch (nf7::Exception&) {
return {};
} }
std::shared_ptr<nf7::Node::Lambda> CreateLambdaIf() noexcept { std::shared_ptr<nf7::Node::Lambda> CreateLambdaIf() noexcept {
try { try {
if (!la_) { if (!la_) {
auto& n = handler_.GetFileOrThrow().interfaceOrThrow<nf7::Node>(); la_ = GetHandler().CreateLambda(la_root_);
la_ = n.CreateLambda(la_root_);
} }
return la_; return la_;
} catch (nf7::Exception& e) { } catch (nf7::Exception& e) {
logger_.Warn("failed to create handler's lambda: "+e.msg()); log_.Warn("failed to create handler's lambda: "+e.msg());
la_ = nullptr; la_ = nullptr;
return nullptr; return nullptr;
} }
@@ -128,43 +186,45 @@ class Event final : public nf7::FileBase, public nf7::DirItem, public nf7::Node
}}, la_root_); }}, la_root_);
} }
} }
void TriggerCustomEvent(const nf7::Value& v) noexcept { void TriggerWatch(const nf7::File::Event& e) noexcept {
if (auto la = CreateLambdaIf()) { if (auto la = CreateLambdaIf()) {
la->Handle("custom", v, la_root_); std::string type;
switch (e.type) {
case nf7::File::Event::kAdd:
type = "add";
break;
case nf7::File::Event::kUpdate:
type = "update";
break;
case nf7::File::Event::kRemove:
type = "remove";
break;
case nf7::File::Event::kReqFocus:
type = "focus";
break;
}
la->Handle("watch", nf7::Value {std::vector<nf7::Value::TuplePair> {
{"file", static_cast<nf7::Value::Integer>(e.id)},
{"type", std::move(type)},
}}, la_root_);
}
}
void SetUpWatcher() noexcept {
watch_.emplace(*this);
for (const auto& p : mem_->watch)
try {
watch_->Watch(ResolveOrThrow(p).id());
} catch (nf7::File::NotFoundException&) {
} }
} }
}; };
class Event::Lambda final : public nf7::Node::Lambda { void Event::PostUpdate() noexcept {
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& io = ImGui::GetIO();
const auto in = GetHandlerInputs();
if (in.end() != std::find(in.begin(), in.end(), "key")) { if (mem_->key) {
for (size_t i = 0; i < ImGuiKey_KeysData_SIZE; ++i) { for (size_t i = 0; i < ImGuiKey_KeysData_SIZE; ++i) {
const auto& key = io.KeysData[i]; const auto& key = io.KeysData[i];
const char* event = nullptr; const char* event = nullptr;
@@ -179,18 +239,32 @@ void Event::Update() noexcept {
} }
} }
} }
if (mem_->mouse) {
// TODO
}
} }
void Event::UpdateMenu() noexcept { void Event::UpdateMenu() noexcept {
if (ImGui::MenuItem("drop handler's lambda")) { if (ImGui::MenuItem("abort and drop lambda", nullptr, false, !!la_)) {
la_->Abort();
la_ = nullptr; la_ = nullptr;
} }
} }
void Event::UpdateWidget() noexcept { void Event::UpdateTooltip() noexcept {
ImGui::TextUnformatted("System/Event"); ImGui::Text("handler: %s", mem_->handler.Stringify().c_str());
ImGui::Text("events :");
handler_editor_.ButtonWithLabel("handler"); if (mem_->init) {
handler_editor_.ItemWidget("handler"); ImGui::Bullet(); ImGui::TextUnformatted("init");
handler_editor_.Update(); }
if (mem_->key) {
ImGui::Bullet(); ImGui::TextUnformatted("key");
}
if (mem_->mouse) {
ImGui::Bullet(); ImGui::TextUnformatted("mouse");
}
if (mem_->watch.size() > 0) {
ImGui::Bullet(); ImGui::TextUnformatted("watch");
}
} }
} // namespace } // namespace

View File

@@ -3,19 +3,29 @@
#include <string_view> #include <string_view>
#include <typeinfo> #include <typeinfo>
#include <utility> #include <utility>
#include <vector>
#include <imgui.h> #include <imgui.h>
#include <yaml-cpp/yaml.h>
#include <yas/serialize.hpp> #include <yas/serialize.hpp>
#include <yas/types/std/string.hpp> #include <yas/types/std/string.hpp>
#include <yas/types/std/string_view.hpp> #include <yas/types/std/string_view.hpp>
#include <yas/types/std/vector.hpp>
#include <yas/types/utility/usertype.hpp>
#include "nf7.hh" #include "nf7.hh"
#include "common/dir_item.hh" #include "common/dir_item.hh"
#include "common/file_base.hh"
#include "common/generic_config.hh"
#include "common/generic_memento.hh"
#include "common/generic_type_info.hh" #include "common/generic_type_info.hh"
#include "common/gui.hh"
#include "common/gui_window.hh" #include "common/gui_window.hh"
#include "common/ptr_selector.hh" #include "common/ptr_selector.hh"
#include "common/util_algorithm.hh"
using namespace std::literals; using namespace std::literals;
@@ -24,16 +34,56 @@ using namespace std::literals;
namespace nf7 { namespace nf7 {
namespace { namespace {
class ImGui_ final : public nf7::File, public nf7::DirItem { class ImGui_ final : public nf7::FileBase,
public nf7::GenericConfig, public nf7::DirItem {
public: public:
static inline const nf7::GenericTypeInfo<ImGui_> kType = {"System/ImGui", {}}; static inline const nf7::GenericTypeInfo<ImGui_> kType = {"System/ImGui", {}};
struct Data {
std::vector<std::string> dockspaces;
void serialize(auto& ar) {
ar(dockspaces);
nf7::util::Uniq(dockspaces);
}
std::string Stringify() const noexcept {
YAML::Emitter st;
st << YAML::BeginMap;
st << YAML::Key << "dockspaces";
st << YAML::Value << dockspaces;
st << YAML::EndMap;
return {st.c_str(), st.size()};
}
void Parse(const std::string& str)
try {
const auto yaml = YAML::Load(str);
Data d;
d.dockspaces = yaml["dockspaces"].as<std::vector<std::string>>();
if (nf7::util::Uniq(d.dockspaces) > 0) {
throw nf7::Exception {"workspace name duplication"};
}
*this = std::move(d);
} catch (YAML::Exception& e) {
throw nf7::Exception {e.what()};
}
};
ImGui_(nf7::Env& env) noexcept : ImGui_(nf7::Env& env) noexcept :
nf7::File(kType, env), nf7::DirItem(nf7::DirItem::kNone) { nf7::FileBase(kType, env),
nf7::GenericConfig(mem_),
nf7::DirItem(nf7::DirItem::kMenu |
nf7::DirItem::kEarlyUpdate |
nf7::DirItem::kImportant),
mem_(*this, {}) {
} }
ImGui_(nf7::Deserializer& ar) : ImGui_(ar.env()) { ImGui_(nf7::Deserializer& ar) : ImGui_(ar.env()) {
std::string config; std::string config;
ar(config); ar(config, mem_.data());
if (config.size() > 0) { if (config.size() > 0) {
ImGui::LoadIniSettingsFromMemory(config.data(), config.size()); ImGui::LoadIniSettingsFromMemory(config.data(), config.size());
@@ -42,40 +92,71 @@ class ImGui_ final : public nf7::File, public nf7::DirItem {
void Serialize(nf7::Serializer& ar) const noexcept override { void Serialize(nf7::Serializer& ar) const noexcept override {
size_t n; size_t n;
const char* config = ImGui::SaveIniSettingsToMemory(&n); const char* config = ImGui::SaveIniSettingsToMemory(&n);
ar(std::string_view(config, n)); ar(std::string_view(config, n), mem_.data());
} }
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override { std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<ImGui_>(env); return std::make_unique<ImGui_>(env);
} }
void Update() noexcept override; void PostUpdate() noexcept override;
void UpdateMenu() noexcept override;
nf7::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>(t).Select(this); return nf7::InterfaceSelector<
nf7::Config, nf7::DirItem>(t).Select(this);
} }
private:
nf7::GenericMemento<Data> mem_;
}; };
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}); void ImGui_::PostUpdate() noexcept {
ImGui::SetNextWindowBgAlpha(0.f); const auto em = ImGui::GetFontSize();
if (ImGui::Begin(id.c_str(), nullptr, kFlags)) {
const auto vp = ImGui::GetMainViewport(); ImGui::DockSpaceOverViewport(ImGui::GetMainViewport(), ImGuiDockNodeFlags_PassthruCentralNode);
ImGui::SetWindowPos({0, 0}, ImGuiCond_Always);
ImGui::SetWindowSize(vp->Size, ImGuiCond_Always); bool mod = false;
auto& ds = mem_->dockspaces;
for (auto itr = ds.begin(); itr < ds.end();) {
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, {0, 0});
{
const auto id = *itr + " - " + nf7::gui::Window::ConcatId(*this, "Dockspace");
ImGui::SetNextWindowSize({8*em, 8*em}, ImGuiCond_FirstUseEver);
bool shown = true;
const bool active = ImGui::Begin(id.c_str(), &shown);
ImGui::DockSpace(ImGui::GetID("_DOCK_SPACE"), {0, 0}, active? 0: ImGuiDockNodeFlags_KeepAliveOnly);
ImGui::End();
if (shown) {
++itr;
} else {
itr = ds.erase(itr);
mod = true;
}
}
ImGui::PopStyleVar(1);
ImGui::DockSpace(ImGui::GetID("DockSpace"), {0, 0},
ImGuiDockNodeFlags_PassthruCentralNode);
} }
ImGui::End(); if (mod) {
ImGui::PopStyleVar(1); mem_.Commit();
}
}
void ImGui_::UpdateMenu() noexcept {
if (ImGui::MenuItem("add workspace")) {
size_t i = 0;
auto& ds = mem_->dockspaces;
for (;; ++i) {
const auto name = std::to_string(i);
if (ds.end() == std::find(ds.begin(), ds.end(), name)) {
ds.push_back(name);
mem_.Commit();
break;
}
}
}
} }
} }

View File

@@ -10,17 +10,23 @@
#include <utility> #include <utility>
#include <iostream> #include <iostream>
#include <imgui.h>
#include <yaml-cpp/yaml.h>
#include "nf7.hh" #include "nf7.hh"
#include "common/dir_item.hh" #include "common/dir_item.hh"
#include "common/file_base.hh" #include "common/file_base.hh"
#include "common/generic_config.hh"
#include "common/generic_context.hh" #include "common/generic_context.hh"
#include "common/generic_memento.hh"
#include "common/generic_type_info.hh" #include "common/generic_type_info.hh"
#include "common/gui.hh"
#include "common/gui_window.hh" #include "common/gui_window.hh"
#include "common/life.hh" #include "common/life.hh"
#include "common/logger.hh" #include "common/logger.hh"
#include "common/logger_ref.hh" #include "common/logger_ref.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh" #include "common/ptr_selector.hh"
#include "common/yas_std_atomic.hh" #include "common/yas_std_atomic.hh"
@@ -30,21 +36,12 @@ using namespace std::literals;
namespace nf7 { namespace nf7 {
namespace { namespace {
class Logger final : public nf7::File, class Logger final : public nf7::FileBase,
public nf7::DirItem { public nf7::GenericConfig, public nf7::DirItem {
public: public:
static inline const nf7::GenericTypeInfo<Logger> kType = { static inline const nf7::GenericTypeInfo<Logger> kType = {
"System/Logger", {"nf7::DirItem"}}; "System/Logger", {"nf7::DirItem"}, "records log output from other files",
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 { struct Row final {
public: public:
@@ -66,63 +63,109 @@ class Logger final : public nf7::File,
return st.str(); return st.str();
} }
}; };
struct Param final {
public:
Param(uint32_t mr, bool p, bool f) : max_rows(mr), propagate(p), freeze(f) {
}
std::atomic<uint32_t> max_rows;
std::atomic<bool> propagate;
std::atomic<bool> freeze;
};
class ItemStore;
Logger(nf7::Env& env, uint32_t max_rows = 1024, bool propagate = false, bool freeze = false) noexcept : struct Data {
File(kType, env), DirItem(DirItem::kMenu), uint32_t max_rows = 1024;
param_(std::make_shared<Param>(max_rows, propagate, freeze)), bool propagate = false;
win_(*this, "LogView") { bool freeze = false;
win_.shown() = true;
Data() noexcept { }
void serialize(auto& ar) {
ar(max_rows, propagate, freeze);
if (max_rows == 0) {
throw DeserializeException("max_rows must be 1 or more");
}
}
std::string Stringify() const noexcept {
YAML::Emitter st;
st << YAML::BeginMap;
st << YAML::Key << "max_rows";
st << YAML::Value << max_rows;
st << YAML::Key << "propagate";
st << YAML::Value << propagate;
st << YAML::Key << "freeze";
st << YAML::Value << freeze;
st << YAML::EndMap;
return {st.c_str(), st.size()};
}
void Parse(const std::string& str)
try {
const auto yaml = YAML::Load(str);
Data d;
d.max_rows = yaml["max_rows"].as<uint32_t>();
d.propagate = yaml["propagate"].as<bool>();
d.freeze = yaml["freeze"].as<bool>();
*this = std::move(d);
} catch (YAML::Exception& e) {
throw nf7::Exception {e.what()};
}
};
Logger(nf7::Env& env, Data&& d = {}) noexcept :
nf7::FileBase(kType, env),
nf7::GenericConfig(mem_),
nf7::DirItem(DirItem::kMenu),
mem_(*this, std::move(d)),
win_(*this, "Log View") {
mem_.onCommit = mem_.onRestore = [this]() {
store_->param(mem_.data());
};
win_.onConfig = []() {
const auto em = ImGui::GetFontSize();
ImGui::SetNextWindowSize({48*em, 16*em}, ImGuiCond_FirstUseEver);
};
win_.onUpdate = [this]() { LogView(); };
} }
Logger(nf7::Deserializer& ar) : Logger(ar.env()) { Logger(nf7::Deserializer& ar) : Logger(ar.env()) {
ar(win_, param_->max_rows, param_->propagate, param_->freeze); ar(win_, mem_.data());
if (param_->max_rows == 0) {
throw DeserializeException("max_rows must be 1 or more");
}
} }
void Serialize(nf7::Serializer& ar) const noexcept override { void Serialize(nf7::Serializer& ar) const noexcept override {
ar(win_, param_->max_rows, param_->propagate, param_->freeze); ar(win_, mem_.data());
} }
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override { std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<Logger>( return std::make_unique<Logger>(env, Data {mem_.data()});
env, param_->max_rows, param_->propagate, param_->freeze); }
void PostHandle(const nf7::File::Event& ev) noexcept override {
switch (ev.type) {
case Event::kAdd:
store_ = std::make_shared<ItemStore>(*this);
return;
default:
return;
}
} }
void Handle(const nf7::File::Event& ev) noexcept override;
void Update() noexcept override;
void UpdateMenu() noexcept override; void UpdateMenu() noexcept override;
void UpdateRowMenu(const Row&) noexcept; void UpdateRowMenu(const Row&) noexcept;
nf7::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). return nf7::InterfaceSelector<nf7::Config, nf7::DirItem, nf7::Logger>(t).
Select(this, store_.get()); Select(this, store_.get());
} }
private: private:
std::shared_ptr<Param> param_; class ItemStore;
std::shared_ptr<ItemStore> store_; std::shared_ptr<ItemStore> store_;
std::deque<Row> rows_; std::deque<Row> rows_;
const char* popup_ = nullptr; nf7::GenericMemento<Data> mem_;
nf7::gui::Window win_; nf7::gui::Window win_;
// log record management
void DropExceededRows() noexcept { void DropExceededRows() noexcept {
if (rows_.size() <= param_->max_rows) return; if (rows_.size() <= mem_->max_rows) return;
rows_.erase(rows_.begin(), rows_.end()-param_->max_rows); rows_.erase(rows_.begin(), rows_.end()-mem_->max_rows);
} }
// stringify
std::string GetPathString(File::Id id) const noexcept std::string GetPathString(File::Id id) const noexcept
try { try {
return env().GetFileOrThrow(id).abspath().Stringify(); return env().GetFileOrThrow(id).abspath().Stringify();
@@ -147,287 +190,87 @@ class Logger final : public nf7::File,
static std::string GetLocationString(const std::source_location loc) noexcept { static std::string GetLocationString(const std::source_location loc) noexcept {
return loc.file_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> {
public:
ItemStore() = delete;
ItemStore(File& owner, const std::shared_ptr<Param>& param) noexcept :
Context(owner.env(), owner.id()), param_(param) {
}
ItemStore(const ItemStore&) = delete;
ItemStore(ItemStore&&) = delete;
ItemStore& operator=(const ItemStore&) = delete;
ItemStore& operator=(ItemStore&&) = delete;
void Write(nf7::Logger::Item&& item) noexcept override { // gui
if (param_->freeze) return; void LogView() noexcept;
if (param_->propagate) {
// TODO propagation
}
std::unique_lock<std::mutex> k(mtx_);
if (items_.size() >= param_->max_rows) items_.pop_front();
items_.push_back(std::move(item));
}
bool MoveItemsTo(auto& owner) noexcept {
std::unique_lock<std::mutex> k(mtx_);
if (items_.empty()) return false;
auto& rows = owner.rows_;
auto itr = items_.begin();
if (rows.size()+items_.size() > param_->max_rows) {
// max_rows may be changed
if (items_.size() > param_->max_rows) {
itr += static_cast<intmax_t>(param_->max_rows - items_.size());
}
const auto keep =
static_cast<intmax_t>(param_->max_rows) - std::distance(itr, items_.end());
rows.erase(rows.begin(), rows.end()-keep);
}
for (; itr < items_.end(); ++itr) {
Row row = {
.file = itr->file,
.srcloc = itr->srcloc,
.level = GetLevelString(itr->level),
.msg = std::move(itr->msg),
.path = owner.GetPathString(itr->file),
.location = GetLocationString(itr->srcloc),
.ex = itr->ex,
};
rows.push_back(std::move(row));
}
items_.clear();
return true;
}
std::string GetDescription() const noexcept override {
return "System/Logger shared instance";
}
std::shared_ptr<nf7::Logger> self(nf7::Logger*) noexcept override {
return shared_from_this();
}
private:
std::mutex mtx_;
std::deque<nf7::Logger::Item> items_;
std::shared_ptr<Param> param_;
};
class Logger::Node final : public nf7::FileBase, public nf7::Node { class ItemStore final : public nf7::Context,
public: public nf7::Logger,
static inline const nf7::GenericTypeInfo<Logger::Node> kType = { public std::enable_shared_from_this<ItemStore> {
"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: public:
Lambda(Logger::Node& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept : ItemStore() = delete;
nf7::Node::Lambda(f, parent), f_(f.life_) { ItemStore(File& f) noexcept : nf7::Context(f) {
}
ItemStore(const ItemStore&) = delete;
ItemStore(ItemStore&&) = delete;
ItemStore& operator=(const ItemStore&) = delete;
ItemStore& operator=(ItemStore&&) = delete;
void Write(nf7::Logger::Item&& item) noexcept override {
if (param_.freeze) return;
if (param_.propagate) {
// TODO propagation
}
std::unique_lock<std::mutex> k(mtx_);
if (items_.size() >= param_.max_rows) items_.pop_front();
items_.push_back(std::move(item));
} }
void Handle(const nf7::Node::Lambda::Msg& in) noexcept override bool MoveItemsTo(auto& owner) noexcept {
try { std::unique_lock<std::mutex> k(mtx_);
f_.EnforceAlive(); if (items_.empty()) return false;
if (in.value.isString()) { auto& rows = owner.rows_;
f_->logger_.Info(in.value.string());
} else { auto itr = items_.begin();
f_->logger_.Info("["s+in.value.typeName()+"]"); if (rows.size()+items_.size() > param_.max_rows) {
if (items_.size() > param_.max_rows) {
itr += static_cast<intmax_t>(param_.max_rows - items_.size());
}
const auto keep =
static_cast<intmax_t>(param_.max_rows) - std::distance(itr, items_.end());
rows.erase(rows.begin(), rows.end()-keep);
} }
} catch (nf7::Exception&) { for (; itr < items_.end(); ++itr) {
Row row = {
.file = itr->file,
.srcloc = itr->srcloc,
.level = GetLevelString(itr->level),
.msg = std::move(itr->msg),
.path = owner.GetPathString(itr->file),
.location = GetLocationString(itr->srcloc),
.ex = itr->ex,
};
rows.push_back(std::move(row));
}
items_.clear();
return true;
}
std::string GetDescription() const noexcept override {
return "System/Logger shared instance";
}
std::shared_ptr<nf7::Logger> self(nf7::Logger*) noexcept override {
return shared_from_this();
}
void param(const Data& d) noexcept {
std::unique_lock<std::mutex> k(mtx_);
param_ = d;
} }
private: private:
nf7::Life<Logger::Node>::Ref f_; std::mutex mtx_;
std::deque<nf7::Logger::Item> items_;
Data param_;
}; };
}; };
void Logger::Handle(const Event& ev) noexcept {
switch (ev.type) {
case Event::kAdd:
store_ = std::make_shared<ItemStore>(*this, param_);
return;
case Event::kRemove:
store_ = nullptr;
return;
default:
return;
}
}
void Logger::Update() noexcept {
if (const auto name = std::exchange(popup_, nullptr)) {
ImGui::OpenPopup(name);
}
const auto em = ImGui::GetFontSize();
// config popup
if (ImGui::BeginPopup("ConfigPopup")) {
ImGui::TextUnformatted("System/Logger Config");
ImGui::Spacing();
static const uint32_t kMinRows = 1, kMaxRows = 1024*1024;
uint32_t max_rows = param_->max_rows;
if (ImGui::DragScalar("max rows", ImGuiDataType_U32, &max_rows, 1, &kMinRows, &kMaxRows)) {
param_->max_rows = max_rows;
DropExceededRows();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("the oldest row is dropped when exceed");
}
bool propagate = param_->propagate;
if (ImGui::Checkbox("propagate", &propagate)) {
param_->propagate = propagate;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("after handling, passes the msg to outer logger if exists");
}
bool freeze = param_->freeze;
if (ImGui::Checkbox("freeze", &freeze)) {
param_->freeze = freeze;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("stop handling except propagation");
}
ImGui::EndPopup();
}
// LogView
if (win_.shownInCurrentFrame()) {
ImGui::SetNextWindowSize({48*em, 16*em}, ImGuiCond_FirstUseEver);
}
if (win_.Begin()) {
constexpr auto kTableFlags =
ImGuiTableFlags_Resizable |
ImGuiTableFlags_Hideable |
ImGuiTableFlags_RowBg |
ImGuiTableFlags_Borders |
ImGuiTableFlags_ContextMenuInBody |
ImGuiTableFlags_SizingStretchProp |
ImGuiTableFlags_ScrollY;
if (ImGui::BeginTable("logs", 4, kTableFlags, ImGui::GetContentRegionAvail(), 0)) {
const bool updated = store_->MoveItemsTo(*this);
const bool autoscroll = updated && ImGui::GetScrollY() == ImGui::GetScrollMaxY();
ImGui::TableSetupColumn("level");
ImGui::TableSetupColumn("msg");
ImGui::TableSetupColumn("path");
ImGui::TableSetupColumn("location");
ImGui::TableSetupScrollFreeze(0, 1);
ImGui::TableHeadersRow();
for (const auto& row : rows_) {
ImGui::TableNextRow();
ImGui::PushID(&row);
if (autoscroll && &row == &rows_.back()) {
ImGui::SetScrollHereY();
}
// level column
if (ImGui::TableSetColumnIndex(0)) {
constexpr auto kFlags =
ImGuiSelectableFlags_SpanAllColumns |
ImGuiSelectableFlags_AllowItemOverlap;
ImGui::Selectable(row.level, false, kFlags);
if (ImGui::BeginPopupContextItem()) {
UpdateRowMenu(row);
ImGui::EndPopup();
}
}
// msg column
if (ImGui::TableNextColumn()) {
ImGui::TextUnformatted(row.msg.c_str());
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(row.msg.c_str());
}
}
// path column
if (ImGui::TableNextColumn()) {
ImGui::TextUnformatted(row.path.c_str());
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(row.path.c_str());
}
}
// location column
if (ImGui::TableNextColumn()) {
ImGui::TextUnformatted(row.location.c_str());
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
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();
}
}
ImGui::PopID();
}
ImGui::EndTable();
}
}
win_.End();
}
void Logger::UpdateMenu() noexcept { void Logger::UpdateMenu() noexcept {
ImGui::MenuItem("shown", nullptr, &win_.shown()); win_.MenuItem();
if (ImGui::MenuItem("config")) {
popup_ = "ConfigPopup";
}
} }
void Logger::UpdateRowMenu(const Row& row) noexcept { void Logger::UpdateRowMenu(const Row& row) noexcept {
if (ImGui::MenuItem("copy as text")) { if (ImGui::MenuItem("copy as text")) {
@@ -440,5 +283,107 @@ void Logger::UpdateRowMenu(const Row& row) noexcept {
} }
} }
void Logger::LogView() noexcept {
constexpr auto kTableFlags =
ImGuiTableFlags_Resizable |
ImGuiTableFlags_Hideable |
ImGuiTableFlags_RowBg |
ImGuiTableFlags_Borders |
ImGuiTableFlags_ContextMenuInBody |
ImGuiTableFlags_SizingStretchProp |
ImGuiTableFlags_ScrollY;
if (ImGui::BeginTable("logs", 4, kTableFlags, ImGui::GetContentRegionAvail(), 0)) {
const bool updated = store_->MoveItemsTo(*this);
const bool autoscroll = updated && ImGui::GetScrollY() == ImGui::GetScrollMaxY();
ImGui::TableSetupColumn("level");
ImGui::TableSetupColumn("msg");
ImGui::TableSetupColumn("path");
ImGui::TableSetupColumn("location");
ImGui::TableSetupScrollFreeze(0, 1);
ImGui::TableHeadersRow();
for (const auto& row : rows_) {
ImGui::TableNextRow();
ImGui::PushID(&row);
if (autoscroll && &row == &rows_.back()) {
ImGui::SetScrollHereY();
}
// level column
if (ImGui::TableSetColumnIndex(0)) {
constexpr auto kFlags =
ImGuiSelectableFlags_SpanAllColumns |
ImGuiSelectableFlags_AllowItemOverlap;
ImGui::Selectable(row.level, false, kFlags);
if (ImGui::BeginPopupContextItem()) {
UpdateRowMenu(row);
ImGui::EndPopup();
}
}
// msg column
if (ImGui::TableNextColumn()) {
auto len = row.msg.find('\n');
if (len == std::string::npos) {
len = row.msg.size();
}
const char* str = row.msg.c_str();
ImGui::TextUnformatted(str, str+len);
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::TextUnformatted(row.msg.c_str());
if (row.ex) {
ImGui::Spacing();
ImGui::TextUnformatted("exception stack:");
for (auto ptr = row.ex; ptr;)
try {
ImGui::Bullet();
std::rethrow_exception(ptr);
} catch (Exception& e) {
ImGui::TextUnformatted(e.msg().c_str());
ptr = e.reason();
} catch (std::exception& e) {
ImGui::TextUnformatted(e.what());
ptr = nullptr;
}
}
ImGui::EndTooltip();
}
}
// path column
if (ImGui::TableNextColumn()) {
ImGui::TextUnformatted(row.path.c_str());
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(row.path.c_str());
}
}
// location column
if (ImGui::TableNextColumn()) {
ImGui::TextUnformatted(row.location.c_str());
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
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();
}
}
ImGui::PopID();
}
ImGui::EndTable();
}
}
} }
} // namespace nf7 } // namespace nf7

View File

@@ -9,21 +9,25 @@
#include <imgui.h> #include <imgui.h>
#include <imgui_stdlib.h> #include <imgui_stdlib.h>
#include <yaml-cpp/yaml.h>
#include <yas/serialize.hpp> #include <yas/serialize.hpp>
#include "nf7.hh" #include "nf7.hh"
#include "common/dir_item.hh" #include "common/dir_item.hh"
#include "common/file_base.hh" #include "common/file_base.hh"
#include "common/generic_config.hh"
#include "common/generic_context.hh" #include "common/generic_context.hh"
#include "common/generic_memento.hh" #include "common/generic_memento.hh"
#include "common/generic_type_info.hh" #include "common/generic_type_info.hh"
#include "common/gui_popup.hh" #include "common/gui.hh"
#include "common/gui_window.hh"
#include "common/life.hh" #include "common/life.hh"
#include "common/logger_ref.hh" #include "common/logger_ref.hh"
#include "common/mutex.hh" #include "common/mutex.hh"
#include "common/nfile.hh" #include "common/nfile.hh"
#include "common/nfile_watcher.hh"
#include "common/node.hh" #include "common/node.hh"
#include "common/ptr_selector.hh" #include "common/ptr_selector.hh"
#include "common/thread.hh" #include "common/thread.hh"
@@ -34,14 +38,12 @@ namespace nf7 {
namespace { namespace {
class NFile final : public nf7::FileBase, class NFile final : public nf7::FileBase,
public nf7::DirItem, public nf7::Node { public nf7::GenericConfig, public nf7::DirItem, public nf7::Node {
public: public:
static inline const nf7::GenericTypeInfo<NFile> kType = { static inline const nf7::GenericTypeInfo<NFile> kType = {
"System/NFile", {"nf7::DirItem", "nf7::Node"}}; "System/NFile", {"nf7::DirItem"},
static void UpdateTypeTooltip() noexcept { "read/write a file placed on native filesystem",
ImGui::TextUnformatted("Read/Write a file placed on native filesystem."); };
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Node");
}
class Lambda; class Lambda;
@@ -83,99 +85,86 @@ class NFile final : public nf7::FileBase,
struct Data final { struct Data final {
std::filesystem::path npath; std::filesystem::path npath;
std::string mode; std::string mode;
std::string Stringify() noexcept {
YAML::Emitter st;
st << YAML::BeginMap;
st << YAML::Key << "npath";
st << YAML::Value << npath.generic_string();
st << YAML::Key << "mode";
st << YAML::Value << mode;
st << YAML::EndMap;
return {st.c_str(), st.size()};
}
void Parse(const std::string& str)
try {
const auto yaml = YAML::Load(str);
Data d;
d.npath = yaml["npath"].as<std::string>();
d.mode = yaml["mode"].as<std::string>();
*this = std::move(d);
} catch (YAML::Exception& e) {
throw nf7::Exception {e.what()};
}
}; };
NFile(nf7::Env& env, Data&& data = {}) noexcept : NFile(nf7::Env& env, Data&& data = {}) noexcept :
nf7::FileBase(kType, env, {&config_popup_}), nf7::FileBase(kType, env),
nf7::DirItem(nf7::DirItem::kMenu | nf7::GenericConfig(mem_),
nf7::DirItem::kTooltip | nf7::DirItem(nf7::DirItem::kTooltip),
nf7::DirItem::kWidget), nf7::Node(nf7::Node::kNone),
nf7::Node(nf7::Node::kMenu_DirItem), life_(*this), nwatch_(*this),
life_(*this),
shared_(std::make_shared<SharedData>(*this)), shared_(std::make_shared<SharedData>(*this)),
th_(std::make_shared<Thread>(*this, Runner {shared_})), th_(std::make_shared<Thread>(*this, Runner {shared_})),
mem_(std::move(data), *this), mem_(*this, std::move(data)) {
config_popup_(*this) { mtx_.onLock = [this]() { SetUp(); };
nf7::FileBase::Install(shared_->log);
mtx_.onLock = [this]() { SetUp(); };
mtx_.onUnlock = [this]() { shared_->nfile.reset(); }; mtx_.onUnlock = [this]() { shared_->nfile.reset(); };
mem_.onRestore = mem_.onCommit = [this]() {
nwatch_.Clear();
nwatch_.Watch(mem_->npath);
};
nwatch_.onMod = [this]() { Touch(); };
} }
NFile(nf7::Deserializer& ar) : NFile(ar.env()) { NFile(nf7::Deserializer& ar) : NFile(ar.env()) {
ar(data().npath, data().mode); ar(mem_->npath, mem_->mode);
} }
void Serialize(nf7::Serializer& ar) const noexcept override { void Serialize(nf7::Serializer& ar) const noexcept override {
ar(data().npath, data().mode); ar(mem_->npath, mem_->mode);
} }
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override { std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<NFile>(env, Data {data()}); return std::make_unique<NFile>(env, Data {mem_.data()});
} }
std::shared_ptr<nf7::Node::Lambda> CreateLambda( std::shared_ptr<nf7::Node::Lambda> CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override; const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
nf7::Node::Meta GetMeta() const noexcept override {
std::span<const std::string> GetInputs() const noexcept override { return {{"command"}, {"result"}};
static const std::vector<std::string> kInputs = {"command"};
return kInputs;
}
std::span<const std::string> GetOutputs() const noexcept override {
static const std::vector<std::string> kOutputs = {"result"};
return kOutputs;
} }
void Update() noexcept override;
void UpdateMenu() noexcept override;
void UpdateTooltip() noexcept override; void UpdateTooltip() noexcept override;
void UpdateWidget() noexcept override;
File::Interface* interface(const std::type_info& t) noexcept override { File::Interface* interface(const std::type_info& t) noexcept override {
return InterfaceSelector<nf7::DirItem, nf7::Node>(t).Select(this); return nf7::InterfaceSelector<
nf7::Config, nf7::DirItem, nf7::Node>(t).Select(this);
} }
private: private:
nf7::Life<NFile> life_; nf7::Life<NFile> life_;
nf7::NFileWatcher nwatch_;
std::shared_ptr<SharedData> shared_; std::shared_ptr<SharedData> shared_;
std::shared_ptr<Thread> th_; std::shared_ptr<Thread> th_;
nf7::Mutex mtx_; nf7::Mutex mtx_;
std::filesystem::file_time_type lastmod_;
nf7::GenericMemento<Data> mem_; nf7::GenericMemento<Data> mem_;
const Data& data() const noexcept { return mem_.data(); }
Data& data() noexcept { return mem_.data(); }
// GUI popup
struct ConfigPopup final :
public nf7::FileBase::Feature, private nf7::gui::Popup {
public:
ConfigPopup(NFile& f) noexcept :
nf7::gui::Popup("ConfigPopup"), f_(&f) {
}
void Open() noexcept {
npath_ = f_->data().npath.generic_string();
const auto& mode = f_->data().mode;
read_ = std::string::npos != mode.find('r');
write_ = std::string::npos != mode.find('w');
nf7::gui::Popup::Open();
}
void Update() noexcept override;
private:
NFile* const f_;
std::string npath_;
bool read_, write_;
} config_popup_;
void SetUp() { void SetUp() {
const auto& mode = data().mode; const auto& mode = mem_->mode;
nf7::NFile::Flags flags = 0; nf7::NFile::Flags flags = 0;
if (std::string::npos != mode.find('r')) flags |= nf7::NFile::kRead; if (std::string::npos != mode.find('r')) flags |= nf7::NFile::kRead;
if (std::string::npos != mode.find('w')) flags |= nf7::NFile::kWrite; if (std::string::npos != mode.find('w')) flags |= nf7::NFile::kWrite;
@@ -184,7 +173,7 @@ class NFile final : public nf7::FileBase,
th_->Push(ctx, Runner::Task { th_->Push(ctx, Runner::Task {
.callee = nullptr, .callee = nullptr,
.caller = nullptr, .caller = nullptr,
.func = [shared = shared_, npath = data().npath, flags]() { .func = [shared = shared_, npath = mem_->npath, flags]() {
shared->nfile.emplace(npath, flags); shared->nfile.emplace(npath, flags);
return nf7::Value::Pulse {}; return nf7::Value::Pulse {};
}, },
@@ -210,9 +199,11 @@ class NFile::Lambda final : public nf7::Node::Lambda,
if (type == "lock") { if (type == "lock") {
const auto ex = v.tuple("ex").boolean(); const auto ex = v.tuple("ex").boolean();
Push(in.sender, ex, []() { return nf7::Value::Pulse {}; }); Push(in.sender, ex, []() { return nf7::Value::Pulse {}; });
} else if (type == "unlock") { } else if (type == "unlock") {
lock_ = std::nullopt; lock_ = std::nullopt;
in.sender->Handle("result", nf7::Value::Pulse {}, shared_from_this()); in.sender->Handle("result", nf7::Value::Pulse {}, shared_from_this());
} else if (type == "read") { } else if (type == "read") {
const auto offset = v.tuple("offset").integer<size_t>(); const auto offset = v.tuple("offset").integer<size_t>();
const auto size = v.tuple("size").integer<size_t>(); const auto size = v.tuple("size").integer<size_t>();
@@ -223,6 +214,7 @@ class NFile::Lambda final : public nf7::Node::Lambda,
buf.resize(actual); buf.resize(actual);
return nf7::Value {std::move(buf)}; return nf7::Value {std::move(buf)};
}); });
} else if (type == "write") { } else if (type == "write") {
const auto offset = v.tuple("offset").integer<size_t>(); const auto offset = v.tuple("offset").integer<size_t>();
const auto buf = v.tuple("buf").vector(); const auto buf = v.tuple("buf").vector();
@@ -230,12 +222,14 @@ class NFile::Lambda final : public nf7::Node::Lambda,
const auto ret = shared_->nfile->Write(offset, buf->data(), buf->size()); const auto ret = shared_->nfile->Write(offset, buf->data(), buf->size());
return nf7::Value {static_cast<nf7::Value::Integer>(ret)}; return nf7::Value {static_cast<nf7::Value::Integer>(ret)};
}); });
} else if (type == "truncate") { } else if (type == "truncate") {
const auto size = v.tuple("size").integer<size_t>(); const auto size = v.tuple("size").integer<size_t>();
Push(in.sender, true, [this, size]() { Push(in.sender, true, [this, size]() {
shared_->nfile->Truncate(size); shared_->nfile->Truncate(size);
return nf7::Value::Pulse {}; return nf7::Value::Pulse {};
}); });
} else { } else {
throw nf7::Exception {"unknown command type: "+type}; throw nf7::Exception {"unknown command type: "+type};
} }
@@ -271,60 +265,9 @@ std::shared_ptr<nf7::Node::Lambda> NFile::CreateLambda(
} }
void NFile::Update() noexcept {
nf7::FileBase::Update();
// file update check
try {
const auto npath = env().npath() / data().npath;
const auto lastmod = std::filesystem::last_write_time(npath);
if (std::exchange(lastmod_, lastmod) < lastmod) {
Touch();
}
} catch (std::filesystem::filesystem_error&) {
}
}
void NFile::UpdateMenu() noexcept {
if (ImGui::MenuItem("config")) {
config_popup_.Open();
}
}
void NFile::UpdateTooltip() noexcept { void NFile::UpdateTooltip() noexcept {
ImGui::Text("npath: %s", data().npath.generic_string().c_str()); ImGui::Text("npath: %s", mem_->npath.generic_string().c_str());
ImGui::Text("mode : %s", data().mode.c_str()); ImGui::Text("mode : %s", mem_->mode.c_str());
}
void NFile::UpdateWidget() noexcept {
ImGui::TextUnformatted("System/NFile");
if (ImGui::Button("config")) {
config_popup_.Open();
}
config_popup_.Update();
}
void NFile::ConfigPopup::Update() noexcept {
if (nf7::gui::Popup::Begin()) {
ImGui::InputText("path", &npath_);
ImGui::Checkbox("read", &read_);
ImGui::Checkbox("write", &write_);
if (ImGui::Button("ok")) {
ImGui::CloseCurrentPopup();
auto& d = f_->data();
d.npath = npath_;
d.mode = "";
if (read_) d.mode += "r";
if (write_) d.mode += "w";
f_->mem_.Commit();
}
if (!std::filesystem::exists(f_->env().npath()/npath_)) {
ImGui::Bullet();
ImGui::TextUnformatted("file not found");
}
ImGui::EndPopup();
}
} }
} }

84
file/system_node.cc Normal file
View File

@@ -0,0 +1,84 @@
#include <chrono>
#include <memory>
#include "nf7.hh"
#include "common/generic_type_info.hh"
#include "common/node.hh"
#include "common/pure_node_file.hh"
using namespace std::literals;
namespace nf7 {
namespace {
class Save final : public nf7::Node::Lambda,
public std::enable_shared_from_this<Save> {
public:
static inline nf7::GenericTypeInfo<nf7::PureNodeFile<Save>> kType = {
"System/Node/Save", {},
};
static inline const nf7::Node::Meta kMeta = {{"exec"}, {},};
using nf7::Node::Lambda::Lambda;
void Handle(const nf7::Node::Lambda::Msg&) noexcept override {
env().ExecMain(shared_from_this(), [this]() {
env().Save();
});
}
};
class Exit final : public nf7::Node::Lambda {
public:
static inline nf7::GenericTypeInfo<nf7::PureNodeFile<Exit>> kType = {
"System/Node/Exit", {},
};
static inline const nf7::Node::Meta kMeta = {{"exec"}, {},};
using nf7::Node::Lambda::Lambda;
void Handle(const nf7::Node::Lambda::Msg&) noexcept override {
env().Exit();
}
};
class Panic final : public nf7::Node::Lambda {
public:
static inline nf7::GenericTypeInfo<nf7::PureNodeFile<Panic>> kType = {
"System/Node/Panic", {},
};
static inline const nf7::Node::Meta kMeta = {{"exec"}, {},};
using nf7::Node::Lambda::Lambda;
void Handle(const nf7::Node::Lambda::Msg& in) noexcept override {
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/Node"}));
}
}
};
class Time final : public nf7::Node::Lambda,
public std::enable_shared_from_this<Time> {
public:
static inline nf7::GenericTypeInfo<nf7::PureNodeFile<Time>> kType = {
"System/Node/Time", {},
};
static inline const nf7::Node::Meta kMeta = {{"get"}, {"time"},};
using nf7::Node::Lambda::Lambda;
void Handle(const nf7::Node::Lambda::Msg& in) noexcept override {
const auto time = nf7::Env::Clock::now();
const auto sec = std::chrono::duration<nf7::Value::Scalar> {time.time_since_epoch()};
in.sender->Handle("time", sec.count(), shared_from_this());
}
};
} // namespace
} // namespace nf7

View File

@@ -7,15 +7,18 @@
#include <imgui.h> #include <imgui.h>
#include <imgui_internal.h> #include <imgui_internal.h>
#include <ImNodes.h>
#include <yas/serialize.hpp> #include <yas/serialize.hpp>
#include <yas/types/utility/usertype.hpp> #include <yas/types/utility/usertype.hpp>
#include "nf7.hh" #include "nf7.hh"
#include "common/dir_item.hh" #include "common/dir_item.hh"
#include "common/file_base.hh"
#include "common/generic_memento.hh" #include "common/generic_memento.hh"
#include "common/generic_type_info.hh" #include "common/generic_type_info.hh"
#include "common/gui_node.hh" #include "common/gui.hh"
#include "common/life.hh" #include "common/life.hh"
#include "common/node.hh" #include "common/node.hh"
#include "common/ptr_selector.hh" #include "common/ptr_selector.hh"
@@ -27,20 +30,15 @@
namespace nf7 { namespace nf7 {
namespace { namespace {
class Curve final : public nf7::File, class Curve final : public nf7::FileBase,
public nf7::DirItem, public nf7::DirItem,
public nf7::Node, public nf7::Node,
public nf7::Sequencer { public nf7::Sequencer {
public: public:
static inline const nf7::GenericTypeInfo<Curve> kType = static inline const nf7::GenericTypeInfo<Curve> kType = {
{"Value/Curve", {"nf7::DirItem", "nf7::Node", "nf7::Sequencer"}}; "Value/Curve", {"nf7::DirItem", "nf7::Node", "nf7::Sequencer"},
static void UpdateTypeTooltip() noexcept { "bezier curve editor",
ImGui::TextUnformatted("bezier curve"); };
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Node");
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Sequencer");
ImGui::Bullet(); ImGui::TextUnformatted(
"changes will be applied to active lambdas immediately");
}
class NodeLambda; class NodeLambda;
class SeqLambda; class SeqLambda;
@@ -67,12 +65,12 @@ class Curve final : public nf7::File,
}; };
Curve(nf7::Env& env, Data&& data = {}) noexcept : Curve(nf7::Env& env, Data&& data = {}) noexcept :
nf7::File(kType, env), nf7::FileBase(kType, env),
nf7::DirItem(nf7::DirItem::kWidget), nf7::DirItem(nf7::DirItem::kWidget),
nf7::Node(nf7::Node::kCustomNode), nf7::Node(nf7::Node::kCustomNode),
nf7::Sequencer(nf7::Sequencer::kCustomItem | nf7::Sequencer(nf7::Sequencer::kCustomItem |
nf7::Sequencer::kParamPanel), nf7::Sequencer::kParamPanel),
life_(*this), mem_(std::move(data), *this) { life_(*this), mem_(*this, std::move(data)) {
AssignId(); AssignId();
Sanitize(); Sanitize();
} }
@@ -94,13 +92,8 @@ class Curve final : public nf7::File,
std::shared_ptr<nf7::Sequencer::Lambda> CreateLambda( std::shared_ptr<nf7::Sequencer::Lambda> CreateLambda(
const std::shared_ptr<nf7::Context>&) noexcept override; const std::shared_ptr<nf7::Context>&) noexcept override;
std::span<const std::string> GetInputs() const noexcept override { nf7::Node::Meta GetMeta() const noexcept override {
static const std::vector<std::string> kInputs = {"x"}; return {{"x"}, {"y"}};
return kInputs;
}
std::span<const std::string> GetOutputs() const noexcept override {
static const std::vector<std::string> kOutputs = {"y"};
return kOutputs;
} }
void UpdateItem(nf7::Sequencer::Editor&) noexcept override; void UpdateItem(nf7::Sequencer::Editor&) noexcept override;
@@ -393,8 +386,8 @@ void Curve::UpdateCurveEditorWindow(const ImVec2& size) noexcept {
const auto pad = ImGui::GetStyle().WindowPadding / 2; const auto pad = ImGui::GetStyle().WindowPadding / 2;
ImGui::SetCursorPos(pad); ImGui::SetCursorPos(pad);
UpdateCurveEditor(ImGui::GetContentRegionAvail()-pad*2); UpdateCurveEditor(ImGui::GetContentRegionAvail()-pad*2);
ImGui::EndChild();
} }
ImGui::EndChild();
} }
void Curve::UpdateCurveEditor(const ImVec2& sz) noexcept { void Curve::UpdateCurveEditor(const ImVec2& sz) noexcept {
const auto& io = ImGui::GetIO(); const auto& io = ImGui::GetIO();

526
file/value_imm.cc Normal file
View File

@@ -0,0 +1,526 @@
#include <array>
#include <cmath>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <variant>
#include <imgui.h>
#include <imgui_internal.h>
#include <imgui_stdlib.h>
#include <ImNodes.h>
#include <yas/serialize.hpp>
#include <yas/types/std/array.hpp>
#include <yas/types/std/string.hpp>
#include <yas/types/std/variant.hpp>
#include <yas/types/utility/usertype.hpp>
#include "nf7.hh"
#include "common/dir_item.hh"
#include "common/file_base.hh"
#include "common/generic_memento.hh"
#include "common/generic_type_info.hh"
#include "common/gui.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 {
struct EditorStatus {
// input
const bool emittable;
const bool autoemit;
const bool autosize;
// output
bool mod = false;
std::optional<nf7::Value> emit = {};
};
struct Pulse {
public:
static constexpr const char* kName = "pulse";
static constexpr const char* kDesc = nullptr;
nf7::Value GetValue() const noexcept {
return nf7::Value::Pulse {};
}
void Editor(EditorStatus& ed) noexcept {
ImGui::BeginDisabled(!ed.emittable);
if (ImGui::Button("PULSE", {6*ImGui::GetFontSize(), 0})) {
ed.emit = nf7::Value {};
}
ImGui::EndDisabled();
}
void serialize(auto&) {
}
};
struct Integer {
public:
static constexpr const char* kName = "integer";
static constexpr const char* kDesc = nullptr;
nf7::Value GetValue() const noexcept {
return {value_};
}
void Editor(EditorStatus& ed) noexcept {
if (!ed.autosize) {
ImGui::SetNextItemWidth(6*ImGui::GetFontSize());
}
if (ImGui::DragScalar("##value", ImGuiDataType_S64, &value_)) {
if (ed.autoemit) ed.emit = nf7::Value {value_};
}
ed.mod = ImGui::IsItemDeactivatedAfterEdit();
}
void serialize(auto& ar) {
ar(value_);
}
private:
nf7::Value::Integer value_ = 0;
};
struct Scalar {
public:
static constexpr const char* kName = "scalar";
static constexpr const char* kDesc = nullptr;
nf7::Value GetValue() const noexcept {
return nf7::Value {value_};
}
void Editor(EditorStatus& ed) noexcept {
if (!ed.autosize) {
ImGui::SetNextItemWidth(6*ImGui::GetFontSize());
}
if (ImGui::DragScalar("##value", ImGuiDataType_Double, &value_)) {
if (ed.autoemit) ed.emit = nf7::Value {value_};
}
ed.mod = ImGui::IsItemDeactivatedAfterEdit();
}
void serialize(auto& ar) {
ar(value_);
}
private:
nf7::Value::Scalar value_ = 0;
};
struct String {
public:
static constexpr const char* kName = "string";
static constexpr const char* kDesc = nullptr;
nf7::Value GetValue() const noexcept {
return nf7::Value {value_};
}
void Editor(EditorStatus& ed) noexcept {
const auto em = ImGui::GetFontSize();
if (!ed.autosize) {
ImGui::SetNextItemWidth(12*em);
}
ImGui::InputTextMultiline("##value", &value_, {0, 2.4f*em});
if (ImGui::IsItemDeactivatedAfterEdit()) {
if (ed.autoemit) ed.emit = GetValue();
ed.mod = true;
}
}
void serialize(auto& ar) {
ar(value_);
}
private:
std::string value_;
};
template <int kMin, int kMax>
struct SliderBase {
public:
nf7::Value GetValue() const noexcept {
return nf7::Value {value_};
}
void Editor(EditorStatus& ed) noexcept {
static const double max = static_cast<double>(kMax);
static const double min = static_cast<double>(kMin);
if (!ed.autosize) {
ImGui::SetNextItemWidth(8*ImGui::GetFontSize());
}
if (ImGui::SliderScalar("##value", ImGuiDataType_Double, &value_, &min, &max)) {
if (ed.autoemit) ed.emit = nf7::Value {value_};
}
ed.mod = ImGui::IsItemDeactivatedAfterEdit();
}
void serialize(auto& ar) {
ar(value_);
}
private:
nf7::Value::Scalar value_ = 0;
};
struct Slider01 : public SliderBase<0, 1> {
static constexpr const char* kName = "slider 0~1";
static constexpr const char* kDesc = nullptr;
};
struct Slider11 : public SliderBase<-1, 1> {
static constexpr const char* kName = "slider -1~1";
static constexpr const char* kDesc = nullptr;
};
struct Color {
public:
static constexpr const char* kName = "color";
static constexpr const char* kDesc = nullptr;
nf7::Value GetValue() const noexcept {
return std::vector<nf7::Value>(values_.begin(), values_.end());
}
void Editor(EditorStatus& ed) noexcept {
if (!ed.autosize) {
ImGui::SetNextItemWidth(16*ImGui::GetFontSize());
}
if (ImGui::ColorEdit4("##value", values_.data())) {
if (ed.autoemit) ed.emit = GetValue();
}
ed.mod = ImGui::IsItemDeactivatedAfterEdit();
}
void serialize(auto& ar) {
ar(values_);
}
private:
std::array<float, 4> values_;
};
struct Pos2D {
public:
static constexpr const char* kName = "position 2D";
static constexpr const char* kDesc = nullptr;
nf7::Value GetValue() const noexcept {
return std::vector<nf7::Value>(values_.begin(), values_.end());
}
void Editor(EditorStatus& ed) noexcept {
const auto em = ImGui::GetFontSize();
const auto& io = ImGui::GetIO();
auto dlist = ImGui::GetForegroundDrawList();
if (!ed.autosize) {
ImGui::SetNextItemWidth(6*ImGui::GetFontSize());
}
ImGui::DragFloat2("##value", values_.data(), 1e-3f);
ImGui::SameLine();
ImGui::Button("+");
if (ImGui::IsItemActive()) {
if (ImGui::IsItemActivated()) {
prev_[0] = values_[0], prev_[1] = values_[1];
std::copy(values_.begin(), values_.end(), prev_.begin());
}
const auto fg_col = ImGui::GetColorU32(ImGuiCol_DragDropTarget);
const auto center = ImGui::GetItemRectMin() + ImGui::GetItemRectSize()/2;
const auto mouse = ImGui::GetMousePos();
dlist->AddLine(mouse, center, fg_col);
const auto axis_size = io.KeyCtrl? 32*em: 16*em;
const auto axis_col = ImGui::GetColorU32(ImGuiCol_DragDropTarget, 0.4f);
dlist->AddLine(center-ImVec2(axis_size, 0),
center+ImVec2(axis_size, 0),
axis_col);
dlist->AddLine(center-ImVec2(0, axis_size),
center+ImVec2(0, axis_size),
axis_col);
const auto apos = mouse - center;
const auto rad = std::sqrt(apos.x*apos.x + apos.y*apos.y);
dlist->AddCircle(center, rad, axis_col);
// set origin pos to values_
const auto rpos = apos / axis_size;
if (io.KeyShift) {
std::copy(prev_.begin(), prev_.end(), values_.begin());
} else {
std::fill(values_.begin(), values_.end(), 0.f);
}
// draw origin text
const auto origin_str = std::to_string(values_[0])+", "+std::to_string(values_[1]);
dlist->AddText(center, axis_col, origin_str.c_str());
// draw mouse pos
const auto mouse_str = std::to_string(rpos.x)+", "+std::to_string(rpos.y);
dlist->AddText(mouse, axis_col, mouse_str.c_str());
// add rpos to values_
values_[0] += rpos.x;
values_[1] += rpos.y;
if (ed.autoemit) ed.emit = GetValue();
}
if (ImGui::IsItemDeactivated()) {
ed.mod = !std::equal(values_.begin(), values_.end(), prev_.begin());
}
}
void serialize(auto& ar) {
ar(values_);
}
private:
std::array<float, 2> values_;
std::array<float, 2> prev_;
};
class Imm final : public nf7::FileBase,
public nf7::DirItem, public nf7::Node {
public:
static inline const nf7::GenericTypeInfo<Imm> kType = {
"Value/Imm", {"nf7::DirItem", "nf7::Node"},
"immediate value",
};
class NodeLambda;
using Value = std::variant<
Pulse, Integer, Scalar, String, Slider01, Slider11, Pos2D, Color>;
struct Data {
Value value;
bool autoemit;
void serialize(auto& ar) {
ar(value, autoemit);
}
};
Imm(nf7::Env& env, Data&& data = {}) noexcept :
nf7::FileBase(kType, env),
nf7::DirItem(nf7::DirItem::kMenu |
nf7::DirItem::kTree |
nf7::DirItem::kTooltip),
nf7::Node(nf7::Node::kCustomNode),
life_(*this), mem_(*this, std::move(data)) {
}
Imm(nf7::Deserializer& ar) : Imm(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<Imm>(env, Data {mem_.data()});
}
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
nf7::Node::Meta GetMeta() const noexcept override {
return {{"in"}, {"out"}};
}
void PostHandle(const nf7::File::Event& e) noexcept override {
switch (e.type) {
case nf7::File::Event::kAdd:
la_node_ = std::make_shared<NodeLambda>(*this);
return;
default:
return;
}
}
void UpdateNode(nf7::Node::Editor&) noexcept override;
void UpdateMenu() noexcept override;
void UpdateTree() noexcept override;
void UpdateTooltip() noexcept override;
nf7::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<Imm> life_;
nf7::GenericMemento<Data> mem_;
std::shared_ptr<NodeLambda> la_node_;
nf7::Value GetValue() const noexcept {
return std::visit([](auto& t) { return t.GetValue(); }, mem_->value);
}
const char* GetTypeName() const noexcept {
return std::visit([](auto& t) { return t.kName; }, mem_->value);
}
void Editor(EditorStatus& ed) noexcept {
std::visit([&](auto& t) { t.Editor(ed); }, mem_->value);
}
// widgets
void MenuItems() noexcept;
template <typename T> void MenuItem() noexcept;
};
class Imm::NodeLambda final : public nf7::Node::Lambda,
public std::enable_shared_from_this<NodeLambda> {
public:
NodeLambda(Imm& f) noexcept : nf7::Node::Lambda(f), f_(f.life_) {
}
void Handle(const nf7::Node::Lambda::Msg& in) noexcept override {
if (f_) {
in.sender->Handle("out", f_->GetValue(), shared_from_this());
}
}
private:
nf7::Life<Imm>::Ref f_;
};
std::shared_ptr<nf7::Node::Lambda> Imm::CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>&) noexcept {
return la_node_;
}
void Imm::UpdateNode(nf7::Node::Editor& ed) noexcept {
ImGui::TextUnformatted("Value/Imm");
ImGui::SameLine();
ImGui::SmallButton(GetTypeName());
if (ImGui::BeginPopupContextItem(nullptr, ImGuiPopupFlags_MouseButtonLeft)) {
MenuItems();
ImGui::EndPopup();
}
if (ImNodes::BeginInputSlot("in", 1)) {
ImGui::AlignTextToFramePadding();
gui::NodeSocket();
ImNodes::EndSlot();
}
ImGui::SameLine();
ImGui::BeginGroup();
EditorStatus stat = {
.emittable = true,
.autoemit = mem_->autoemit,
.autosize = false,
};
Editor(stat);
ImGui::EndGroup();
ImGui::SameLine();
if (ImNodes::BeginOutputSlot("out", 1)) {
ImGui::AlignTextToFramePadding();
gui::NodeSocket();
ImNodes::EndSlot();
}
if (stat.emit) {
ed.Emit(*this, "out", std::move(*stat.emit));
}
if (stat.mod) {
mem_.Commit();
}
}
void Imm::UpdateMenu() noexcept {
if (ImGui::BeginMenu("type")) {
MenuItems();
ImGui::EndMenu();
}
if (ImGui::MenuItem("emit on change", nullptr, &mem_->autoemit)) {
mem_.Commit();
}
}
void Imm::UpdateTree() noexcept {
EditorStatus stat {
.emittable = false,
.autoemit = false,
.autosize = true,
};
Editor(stat);
if (stat.mod) {
mem_.Commit();
}
}
void Imm::UpdateTooltip() noexcept {
ImGui::Text("type : %s", GetTypeName());
ImGui::TextUnformatted("preview:");
EditorStatus stat {
.emittable = false,
.autoemit = false,
.autosize = false,
};
ImGui::Indent();
Editor(stat);
ImGui::Unindent();
}
void Imm::MenuItems() noexcept {
MenuItem<Pulse>();
MenuItem<Integer>();
MenuItem<Scalar>();
MenuItem<String>();
ImGui::Separator();
MenuItem<Slider01>();
MenuItem<Slider11>();
ImGui::Separator();
MenuItem<Pos2D>();
MenuItem<Color>();
}
template <typename T>
void Imm::MenuItem() noexcept {
const bool holding = std::holds_alternative<T>(mem_->value);
if (ImGui::MenuItem(T::kName, nullptr, holding) && !holding) {
mem_->value = T {};
mem_.Commit();
}
if constexpr (T::kDesc) {
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s", T::kDesc);
}
}
}
}
} // namespace nf7
namespace yas::detail {
template <size_t F>
struct serializer<
type_prop::not_a_fundamental,
ser_case::use_internal_serializer,
F,
nf7::Imm::Value> {
public:
template <typename Archive>
static Archive& save(Archive& ar, const nf7::Imm::Value& v) {
std::visit([&](auto& v) { ar(std::string_view {v.kName}, v); }, v);
return ar;
}
template <typename Archive>
static Archive& load(Archive& ar, nf7::Imm::Value& v) {
std::string name;
ar(name);
LoadVariantType(name, ar, v);
return ar;
}
private:
template <size_t kI = 0>
static void LoadVariantType(std::string_view name, auto& ar, nf7::Imm::Value& v) {
if constexpr (kI < std::variant_size_v<nf7::Imm::Value>) {
using T = std::variant_alternative_t<kI, nf7::Imm::Value>;
if (name == T::kName) {
T data;
ar(data);
v = std::move(data);
} else {
LoadVariantType<kI+1>(name, ar, v);
}
} else {
throw nf7::Exception {"unknown Value/Imm type: "+std::string {name}};
}
}
};
} // namespace yas::detail

View File

@@ -21,17 +21,15 @@
#include "common/dir_item.hh" #include "common/dir_item.hh"
#include "common/file_base.hh" #include "common/file_base.hh"
#include "common/generic_config.hh"
#include "common/generic_memento.hh" #include "common/generic_memento.hh"
#include "common/generic_type_info.hh" #include "common/generic_type_info.hh"
#include "common/gui_config.hh"
#include "common/gui_node.hh"
#include "common/gui_window.hh" #include "common/gui_window.hh"
#include "common/life.hh" #include "common/life.hh"
#include "common/logger_ref.hh" #include "common/logger_ref.hh"
#include "common/node.hh" #include "common/node.hh"
#include "common/ptr_selector.hh" #include "common/ptr_selector.hh"
#include "common/util_algorithm.hh" #include "common/util_algorithm.hh"
#include "common/util_string.hh"
#include "common/value.hh" #include "common/value.hh"
#include "common/yas_enum.hh" #include "common/yas_enum.hh"
@@ -40,15 +38,12 @@ namespace nf7 {
namespace { namespace {
class Plot final : public nf7::FileBase, class Plot final : public nf7::FileBase,
public nf7::GenericConfig,
public nf7::DirItem, public nf7::DirItem,
public nf7::Node { public nf7::Node {
public: public:
static inline const nf7::GenericTypeInfo<Plot> kType = static inline const nf7::GenericTypeInfo<Plot> kType =
{"Value/Plot", {"nf7::DirItem", "nf7::Node"}}; {"Value/Plot", {"nf7::DirItem"}, "data plotter"};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("plotter");
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Node");
}
class Lambda; class Lambda;
@@ -110,11 +105,14 @@ class Plot final : public nf7::FileBase,
std::vector<Series> series; std::vector<Series> series;
}; };
Plot(nf7::Env& env, const nf7::gui::Window* win = nullptr, Data&& data = {}) noexcept : Plot(nf7::Env& env, Data&& data = {}) noexcept :
nf7::FileBase(kType, env, {&log_}), nf7::FileBase(kType, env),
nf7::DirItem(nf7::DirItem::kMenu | nf7::DirItem::kWidget), nf7::GenericConfig(mem_),
nf7::DirItem(nf7::DirItem::kMenu),
nf7::Node(nf7::Node::kNone), nf7::Node(nf7::Node::kNone),
life_(*this), log_(*this), win_(*this, "Plot", win), mem_(std::move(data)) { life_(*this), log_(*this), win_(*this, "Plot"),
mem_(*this, std::move(data)) {
win_.onUpdate = [this]() { PlotGraph(); };
mem_.onRestore = mem_.onCommit = [this]() { BuildInputList(); }; mem_.onRestore = mem_.onCommit = [this]() { BuildInputList(); };
Sanitize(); Sanitize();
} }
@@ -127,28 +125,20 @@ class Plot final : public nf7::FileBase,
ar(win_, mem_->series); ar(win_, mem_->series);
} }
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override { std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<Plot>(env, &win_, Data {mem_.data()}); return std::make_unique<Plot>(env, Data {mem_.data()});
} }
std::shared_ptr<nf7::Node::Lambda> CreateLambda( std::shared_ptr<nf7::Node::Lambda> CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override; const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
nf7::Node::Meta GetMeta() const noexcept override {
std::span<const std::string> GetInputs() const noexcept override { return {inputs_, {}};
return inputs_;
}
std::span<const std::string> GetOutputs() const noexcept override {
return {};
} }
void Update() noexcept override;
void UpdateWidget() noexcept override;
void UpdateMenu() noexcept override; void UpdateMenu() noexcept override;
void UpdatePlot() noexcept;
nf7::File::Interface* interface(const std::type_info& t) noexcept override { nf7::File::Interface* interface(const std::type_info& t) noexcept override {
return InterfaceSelector< return nf7::InterfaceSelector<
nf7::DirItem, nf7::Memento, nf7::Node>(t).Select(this, &mem_); nf7::Config, nf7::DirItem, nf7::Memento, nf7::Node>(t).Select(this, &mem_);
} }
private: private:
@@ -162,6 +152,7 @@ class Plot final : public nf7::FileBase,
std::vector<std::string> inputs_; std::vector<std::string> inputs_;
// config management
void Sanitize() { void Sanitize() {
nf7::util::Uniq(mem_->series); nf7::util::Uniq(mem_->series);
mem_.CommitAmend(); mem_.CommitAmend();
@@ -173,6 +164,9 @@ class Plot final : public nf7::FileBase,
inputs_.push_back(s.name); inputs_.push_back(s.name);
} }
} }
// gui
void PlotGraph() noexcept;
}; };
@@ -238,30 +232,11 @@ std::shared_ptr<nf7::Node::Lambda> Plot::CreateLambda(
} }
void Plot::Update() noexcept {
nf7::FileBase::Update();
if (win_.Begin()) {
UpdatePlot();
}
win_.End();
}
void Plot::UpdateWidget() noexcept {
if (ImGui::Button("plot window")) {
win_.SetFocus();
}
nf7::gui::Config(mem_);
}
void Plot::UpdateMenu() noexcept { void Plot::UpdateMenu() noexcept {
ImGui::MenuItem("plot window", nullptr, &win_.shown()); win_.MenuItem();
if (ImGui::BeginMenu("config")) {
nf7::gui::Config(mem_);
ImGui::EndMenu();
}
} }
void Plot::UpdatePlot() noexcept { void Plot::PlotGraph() noexcept {
if (ImPlot::BeginPlot("##plot", ImGui::GetContentRegionAvail())) { if (ImPlot::BeginPlot("##plot", ImGui::GetContentRegionAvail())) {
ImPlot::SetupAxis(ImAxis_X1, "X", ImPlotAxisFlags_AutoFit); ImPlot::SetupAxis(ImAxis_X1, "X", ImPlotAxisFlags_AutoFit);
ImPlot::SetupAxis(ImAxis_Y1, "Y", ImPlotAxisFlags_AutoFit); ImPlot::SetupAxis(ImAxis_Y1, "Y", ImPlotAxisFlags_AutoFit);

33
init.hh
View File

@@ -7,13 +7,34 @@
inline std::unique_ptr<nf7::File> CreateRoot(nf7::Env& env) noexcept { inline std::unique_ptr<nf7::File> CreateRoot(nf7::Env& env) noexcept {
auto ret = nf7::File::registry("System/Dir").Create(env); auto ret = nf7::File::registry("System/Dir").Create(env);
auto& dir = ret->interfaceOrThrow<nf7::Dir>(); auto& root = ret->interfaceOrThrow<nf7::Dir>();
dir.Add("_audio", nf7::File::registry("Audio/Context").Create(env)); const auto Add = [&](nf7::Dir& dir, const char* name, const char* type) -> nf7::File& {
dir.Add("_imgui", nf7::File::registry("System/ImGui").Create(env)); return dir.Add(name, nf7::File::registry(type).Create(env));
dir.Add("_logger", nf7::File::registry("System/Logger").Create(env)); };
dir.Add("_luajit", nf7::File::registry("LuaJIT/Context").Create(env));
dir.Add("home", nf7::File::registry("System/Dir").Create(env)); Add(root, "_audio", "Audio/Context");
Add(root, "_font", "Font/Context");
Add(root, "_imgui", "System/ImGui");
Add(root, "_logger", "System/Logger");
Add(root, "_luajit", "LuaJIT/Context");
auto& node = Add(root, "node", "System/Dir").interfaceOrThrow<nf7::Dir>();
{
auto& codec = Add(node, "codec", "System/Dir").interfaceOrThrow<nf7::Dir>();
{
Add(codec, "stbimage", "Codec/StbImage");
}
auto& system = Add(node, "system", "System/Dir").interfaceOrThrow<nf7::Dir>();
{
Add(system, "save", "System/Node/Save");
Add(system, "exit", "System/Node/Exit");
Add(system, "panic", "System/Node/Panic");
Add(system, "time", "System/Node/Time");
}
}
Add(root, "home", "System/Dir").interfaceOrThrow<nf7::Dir>();
return ret; return ret;
} }

298
main.cc
View File

@@ -2,11 +2,14 @@
#include <cassert> #include <cassert>
#include <chrono> #include <chrono>
#include <condition_variable> #include <condition_variable>
#include <cstdlib>
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <mutex> #include <mutex>
#include <shared_mutex>
#include <thread> #include <thread>
#include <unordered_map> #include <unordered_map>
#include <unordered_set>
#include <vector> #include <vector>
#include <GL/glew.h> #include <GL/glew.h>
@@ -15,9 +18,12 @@
#include <imgui_impl_opengl3.h> #include <imgui_impl_opengl3.h>
#include <implot.h> #include <implot.h>
#include <tracy/Tracy.hpp>
#include "nf7.hh" #include "nf7.hh"
#include "common/queue.hh" #include "common/queue.hh"
#include "common/stopwatch.hh"
#include "common/timed_queue.hh" #include "common/timed_queue.hh"
#include "common/yas_nf7.hh" #include "common/yas_nf7.hh"
@@ -32,7 +38,9 @@ using namespace std::literals;
namespace { namespace {
constexpr size_t kSubTaskUnit = 64; constexpr auto kFrameDur = 1000ms / 30;
constexpr auto kSubTaskDur = 5ms;
constexpr const char* kFontPath = "./nf7.ttf";
std::atomic<bool> alive_ = true; std::atomic<bool> alive_ = true;
@@ -48,44 +56,65 @@ std::atomic<CycleState> cycle_ = kUpdate;
std::condition_variable cycle_cv_; std::condition_variable cycle_cv_;
std::mutex cycle_mtx_; std::mutex cycle_mtx_;
std::shared_mutex task_mtx_;
using Task = std::pair<std::shared_ptr<nf7::Context>, nf7::Env::Task>; using Task = std::pair<std::shared_ptr<nf7::Context>, nf7::Env::Task>;
nf7::Queue<Task> mainq_; nf7::Queue<Task> mainq_;
nf7::Queue<Task> subq_; nf7::TimedQueue<Task> subq_;
nf7::TimedQueue<Task> asyncq_; nf7::TimedQueue<Task> asyncq_;
nf7::TimedQueue<Task> glq_; nf7::TimedQueue<Task> glq_;
nf7::Queue<std::exception_ptr> panicq_; nf7::Queue<std::exception_ptr> panicq_;
void WorkerThread() noexcept { void WorkerThread() noexcept {
[[maybe_unused]] const char kThreadId[] = "SyncWorker";
tracy::SetThreadName("SyncWorker");
std::unique_lock<std::mutex> k {cycle_mtx_}; std::unique_lock<std::mutex> k {cycle_mtx_};
while (alive_) { while (alive_) {
// wait for the end of GUI update FrameMarkStart(kThreadId);
cycle_cv_.wait(k, []() { return cycle_ != kUpdate; }); cycle_cv_.wait(k, []() { return cycle_ != kUpdate; });
k.unlock(); k.unlock();
// exec main tasks for (;;) {
while (auto task = mainq_.Pop()) std::shared_lock<std::shared_mutex> sk {task_mtx_};
try { auto task = mainq_.Pop();
task->second(); if (!task) break;
} catch (nf7::Exception&) { try {
panicq_.Push(std::current_exception()); ZoneScopedNC("main task", tracy::Color::Orange);
if (task->first) {
const auto str = task->first->GetDescription();
ZoneText(str.data(), str.size());
}
task->second();
} catch (nf7::Exception&) {
sk.unlock();
panicq_.Push(std::current_exception());
}
} }
// exec sub tasks // exec sub tasks
while (cycle_ != kSyncUpdate) { while (cycle_ != kSyncUpdate) {
for (size_t i = 0; i < kSubTaskUnit; ++i) { for (nf7::Stopwatch sw; sw.dur() < kSubTaskDur;) {
std::shared_lock<std::shared_mutex> sk {task_mtx_};
const auto task = subq_.Pop(); const auto task = subq_.Pop();
if (!task) break; if (!task) break;
try { try {
ZoneScopedNC("sub task", tracy::Color::Green);
if (task->first) {
const auto str = task->first->GetDescription();
ZoneText(str.data(), str.size());
}
task->second(); task->second();
} catch (nf7::Exception&) { } catch (nf7::Exception&) {
sk.unlock();
panicq_.Push(std::current_exception()); panicq_.Push(std::current_exception());
} }
} }
k.lock(); k.lock();
cycle_cv_.wait(k, []() { cycle_cv_.wait(k, []() {
return cycle_ == kSyncUpdate || subq_.size() > 0; return cycle_ == kSyncUpdate || !subq_.idle();
}); });
k.unlock(); k.unlock();
} }
@@ -94,27 +123,51 @@ void WorkerThread() noexcept {
k.lock(); k.lock();
cycle_ = kUpdate; cycle_ = kUpdate;
cycle_cv_.notify_all(); cycle_cv_.notify_all();
FrameMarkEnd(kThreadId);
} }
TracyMessageL("SyncWorker exitting");
} }
void AsyncThread() noexcept { void AsyncThread() noexcept {
[[maybe_unused]] const char kThreadId[] = "AsyncWorker";
tracy::SetThreadName("AsyncWorker");
std::unique_lock<std::mutex> k {cycle_mtx_}; std::unique_lock<std::mutex> k {cycle_mtx_};
while (alive_) { while (alive_) {
FrameMarkStart(kThreadId);
const auto until = asyncq_.next().value_or(nf7::Env::Time::max()); const auto until = asyncq_.next().value_or(nf7::Env::Time::max());
cycle_cv_.wait_until(k, until, []() { return !alive_ || !asyncq_.idle(); }); cycle_cv_.wait_until(k, until, []() { return !alive_ || !asyncq_.idle(); });
k.unlock(); k.unlock();
while (auto task = asyncq_.Pop()) for (;;) {
try { std::shared_lock<std::shared_mutex> sk {task_mtx_};
task->second(); auto task = asyncq_.Pop();
} catch (nf7::Exception&) { if (!task) break;
panicq_.Push(std::current_exception()); try {
ZoneScopedNC("async task", tracy::Color::Blue);
if (task->first) {
const auto str = task->first->GetDescription();
ZoneText(str.data(), str.size());
}
task->second();
} catch (nf7::Exception&) {
sk.unlock();
panicq_.Push(std::current_exception());
}
} }
k.lock(); k.lock();
FrameMarkEnd(kThreadId);
} }
TracyMessageL("AsyncWorker exitting");
} }
void GLThread(GLFWwindow* window) noexcept { void GLThread(GLFWwindow* window) noexcept {
[[maybe_unused]] const char kThreadId[] = "GLWorker";
tracy::SetThreadName("GLWorker");
std::unique_lock<std::mutex> k {cycle_mtx_}; std::unique_lock<std::mutex> k {cycle_mtx_};
// does nothing when the first cycle because the main thread is using GL context // does nothing when the first cycle because the main thread is using GL context
@@ -123,17 +176,26 @@ void GLThread(GLFWwindow* window) noexcept {
cycle_cv_.notify_all(); cycle_cv_.notify_all();
while (alive_) { while (alive_) {
FrameMarkStart(kThreadId);
// wait for the end of GUI drawing // wait for the end of GUI drawing
cycle_cv_.wait(k, []() { return cycle_ != kDraw; }); cycle_cv_.wait(k, []() { return cycle_ != kDraw; });
k.unlock(); k.unlock();
glfwMakeContextCurrent(window); glfwMakeContextCurrent(window);
for (size_t i = 0; i < kSubTaskUnit; ++i) { for (nf7::Stopwatch sw; sw.dur() < kSubTaskDur;) {
std::shared_lock<std::shared_mutex> sk {task_mtx_};
auto task = glq_.Pop(); auto task = glq_.Pop();
if (!task) break; if (!task) break;
try { try {
ZoneScopedNC("GL task", tracy::Color::Aqua);
if (task->first) {
const auto str = task->first->GetDescription();
ZoneText(str.data(), str.size());
}
task->second(); task->second();
} catch (nf7::Exception&) { } catch (nf7::Exception&) {
sk.unlock();
panicq_.Push(std::current_exception()); panicq_.Push(std::current_exception());
} }
assert(0 == glGetError()); assert(0 == glGetError());
@@ -141,13 +203,16 @@ void GLThread(GLFWwindow* window) noexcept {
glfwMakeContextCurrent(nullptr); glfwMakeContextCurrent(nullptr);
k.lock(); k.lock();
cycle_cv_.wait(k, []() { return cycle_ != kDraw || !glq_.idle(); }); cycle_cv_.wait(k, []() { return cycle_ == kSyncDraw || !glq_.idle(); });
if (cycle_ == kSyncDraw) { if (cycle_ == kSyncDraw) {
// tell the main thread to start GUI drawing // tell the main thread to start GUI drawing
cycle_ = kDraw; cycle_ = kDraw;
cycle_cv_.notify_all(); cycle_cv_.notify_all();
} }
FrameMarkEnd(kThreadId);
} }
TracyMessageL("GLWorker exitting");
} }
@@ -156,6 +221,8 @@ class Env final : public nf7::Env {
static constexpr auto kFileName = "root.nf7"; static constexpr auto kFileName = "root.nf7";
Env() noexcept : nf7::Env(std::filesystem::current_path()) { Env() noexcept : nf7::Env(std::filesystem::current_path()) {
ZoneScopedN("nf7::Env constructor");
// deserialize // deserialize
if (!std::filesystem::exists(kFileName)) { if (!std::filesystem::exists(kFileName)) {
root_ = CreateRoot(*this); root_ = CreateRoot(*this);
@@ -169,8 +236,14 @@ class Env final : public nf7::Env {
} }
} }
} }
~Env() noexcept {
if (ctxs_.size() > 0) {
std::cout << "context leak detected: " << ctxs_.size() << std::endl;
}
}
void TearDownRoot() noexcept { void TearDownRoot() noexcept {
ZoneScoped;
if (root_) { if (root_) {
Save(); Save();
root_->Isolate(); root_->Isolate();
@@ -185,17 +258,21 @@ class Env final : public nf7::Env {
bool notify = false; bool notify = false;
switch (type) { switch (type) {
case kMain: case kMain:
TracyMessageL("queue main task");
mainq_.Push({ctx, std::move(task)}); mainq_.Push({ctx, std::move(task)});
break; break;
case kSub: case kSub:
subq_.Push({ctx, std::move(task)}); TracyMessageL("queue sub task");
subq_.Push(time, {ctx, std::move(task)});
notify = true; notify = true;
break; break;
case kAsync: case kAsync:
TracyMessageL("queue async task");
asyncq_.Push(time, {ctx, std::move(task)}); asyncq_.Push(time, {ctx, std::move(task)});
notify = true; notify = true;
break; break;
case kGL: case kGL:
TracyMessageL("queue gl task");
glq_.Push(time, {ctx, std::move(task)}); glq_.Push(time, {ctx, std::move(task)});
notify = true; notify = true;
break; break;
@@ -206,27 +283,35 @@ class Env final : public nf7::Env {
} }
} }
void Handle(const nf7::File::Event& e) noexcept override nf7::File* Handle(const nf7::File::Event& e) noexcept override
try { try {
// trigger File::Handle() // trigger File::Handle()
GetFileOrThrow(e.id).Handle(e); auto& f = GetFileOrThrow(e.id);
f.Handle(e);
// trigger file watcher // trigger file watcher
auto itr = watchers_map_.find(e.id); auto itr = watchers_.find(e.id);
if (itr != watchers_map_.end()) { if (itr != watchers_.end()) {
for (auto w : itr->second) w->Handle(e); for (auto w : itr->second) w->Handle(e);
} }
// trigger global watcher // trigger global watcher
for (auto w : watchers_map_[0]) w->Handle(e); itr = watchers_.find(0);
if (itr != watchers_.end()) {
for (auto w : itr->second) w->Handle(e);
}
return &f;
} catch (nf7::ExpiredException&) { } catch (nf7::ExpiredException&) {
return nullptr;
} }
void Exit() noexcept override { void Exit() noexcept override {
TracyMessageL("exit requested");
exit_requested_ = true; exit_requested_ = true;
} }
void Save() noexcept override void Save() noexcept override
try { try {
ZoneScoped;
nf7::Serializer::Save(*this, kFileName, root_); nf7::Serializer::Save(*this, kFileName, root_);
} catch (nf7::Exception&) { } catch (nf7::Exception&) {
panicq_.Push(std::current_exception()); panicq_.Push(std::current_exception());
@@ -236,6 +321,7 @@ class Env final : public nf7::Env {
} }
void Update() noexcept { void Update() noexcept {
ZoneScoped;
ImGui::PushID(this); ImGui::PushID(this);
{ {
if (root_) { if (root_) {
@@ -263,16 +349,27 @@ class Env final : public nf7::Env {
files_.erase(id); files_.erase(id);
} }
void AddWatcher(nf7::File::Id id, nf7::Env::Watcher& w) noexcept override { void AddContext(nf7::Context& ctx) noexcept override {
watchers_map_[id].push_back(&w); std::unique_lock<std::mutex> k {ctx_mtx_};
watchers_rmap_[&w].push_back(id); ctxs_.insert(&ctx);
} }
void RemoveWatcher(nf7::Env::Watcher& w) noexcept override { void RemoveContext(nf7::Context& ctx) noexcept override {
for (const auto id : watchers_rmap_[&w]) { std::unique_lock<std::mutex> k {ctx_mtx_};
auto& v = watchers_map_[id]; ctxs_.erase(&ctx);
}
void AddWatcher(nf7::File::Id id, nf7::Env::Watcher& w) noexcept override {
watchers_[id].push_back(&w);
}
void RemoveWatcher(nf7::File::Id id, nf7::Env::Watcher& w) noexcept override {
auto itr = watchers_.find(id);
if (watchers_.end() != itr) {
auto& v = itr->second;
v.erase(std::remove(v.begin(), v.end(), &w), v.end()); v.erase(std::remove(v.begin(), v.end(), &w), v.end());
if (v.size() == 0) {
watchers_.erase(itr);
}
} }
watchers_rmap_.erase(&w);
} }
private: private:
@@ -283,12 +380,16 @@ class Env final : public nf7::Env {
nf7::File::Id file_next_ = 1; nf7::File::Id file_next_ = 1;
std::unordered_map<nf7::File::Id, nf7::File*> files_; std::unordered_map<nf7::File::Id, nf7::File*> files_;
std::unordered_map<nf7::File::Id, std::vector<nf7::Env::Watcher*>> watchers_map_; std::unordered_map<nf7::File::Id, std::vector<nf7::Env::Watcher*>> watchers_;
std::unordered_map<nf7::Env::Watcher*, std::vector<nf7::File::Id>> watchers_rmap_;
std::mutex ctx_mtx_;
std::unordered_set<nf7::Context*> ctxs_;
}; };
void UpdatePanic() noexcept { void UpdatePanic() noexcept {
ZoneScoped;
static std::exception_ptr ptr_; static std::exception_ptr ptr_;
if (!ptr_) { if (!ptr_) {
if (auto ptr = panicq_.Pop()) { if (auto ptr = panicq_.Pop()) {
@@ -362,7 +463,7 @@ int main(int, char**) {
window = glfwCreateWindow(1280, 720, "Nf7", NULL, NULL); window = glfwCreateWindow(1280, 720, "Nf7", NULL, NULL);
if (window == NULL) return 1; if (window == NULL) return 1;
glfwMakeContextCurrent(window); glfwMakeContextCurrent(window);
glfwSwapInterval(1); glfwSwapInterval(0);
if (glewInit() != GLEW_OK) return 1; if (glewInit() != GLEW_OK) return 1;
// start threads // start threads
@@ -387,17 +488,28 @@ int main(int, char**) {
ImGui_ImplGlfw_InitForOpenGL(window, true); ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init("#version 130"); ImGui_ImplOpenGL3_Init("#version 130");
// load GUI font
if (std::filesystem::exists(kFontPath)) {
ZoneScopedN("load GUI font");
io.Fonts->AddFontFromFileTTF(
kFontPath, 16.f, nullptr, io.Fonts->GetGlyphRangesJapanese());
}
// main loop // main loop
::Env env; ::Env env;
glfwShowWindow(window); glfwShowWindow(window);
while (!glfwWindowShouldClose(window) && !env.exitRequested()) { while (!glfwWindowShouldClose(window) && !env.exitRequested()) {
// handle events nf7::Stopwatch sw;
glfwPollEvents();
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
// sync with worker thread {
ZoneScopedN("handle events");
glfwPollEvents();
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
}
// wait for sync thrad
{ {
cycle_ = kSyncUpdate; cycle_ = kSyncUpdate;
std::unique_lock<std::mutex> k {cycle_mtx_}; std::unique_lock<std::mutex> k {cycle_mtx_};
@@ -405,13 +517,15 @@ int main(int, char**) {
cycle_cv_.wait(k, []() { return cycle_ == kUpdate; }); cycle_cv_.wait(k, []() { return cycle_ == kUpdate; });
} }
// GUI update (OpenGL call is forbidden) {
assert(cycle_ == kUpdate); ZoneScopedN("update GUI");
env.Update(); assert(cycle_ == kUpdate);
UpdatePanic(); env.Update();
ImGui::Render(); UpdatePanic();
ImGui::Render();
}
// sync with GL thread // wait for GL thread
{ {
cycle_ = kSyncDraw; cycle_ = kSyncDraw;
std::unique_lock<std::mutex> k {cycle_mtx_}; std::unique_lock<std::mutex> k {cycle_mtx_};
@@ -420,15 +534,18 @@ int main(int, char**) {
} }
// GUI draw (OpenGL calls occur) // GUI draw (OpenGL calls occur)
assert(cycle_ == kDraw); {
glfwMakeContextCurrent(window); ZoneScopedN("update display");
int w, h; assert(cycle_ == kDraw);
glfwGetFramebufferSize(window, &w, &h); glfwMakeContextCurrent(window);
glViewport(0, 0, w, h); int w, h;
glClear(GL_COLOR_BUFFER_BIT); glfwGetFramebufferSize(window, &w, &h);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); glViewport(0, 0, w, h);
glfwSwapBuffers(window); glClear(GL_COLOR_BUFFER_BIT);
glfwMakeContextCurrent(nullptr); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
glfwSwapBuffers(window);
glfwMakeContextCurrent(nullptr);
}
// sleep // sleep
{ {
@@ -436,7 +553,9 @@ int main(int, char**) {
std::unique_lock<std::mutex> k {cycle_mtx_}; std::unique_lock<std::mutex> k {cycle_mtx_};
cycle_cv_.notify_all(); cycle_cv_.notify_all();
} }
std::this_thread::sleep_for(10ms); std::this_thread::sleep_for(kFrameDur - sw.dur());
FrameMark;
} }
// sync with worker thread and tear down filesystem // sync with worker thread and tear down filesystem
@@ -444,9 +563,14 @@ int main(int, char**) {
cycle_ = kSyncUpdate; cycle_ = kSyncUpdate;
std::unique_lock<std::mutex> k {cycle_mtx_}; std::unique_lock<std::mutex> k {cycle_mtx_};
cycle_cv_.notify_all(); cycle_cv_.notify_all();
cycle_cv_.wait(k, []() { return cycle_ != kUpdate; }); cycle_cv_.wait(k, []() { return cycle_ == kUpdate; });
}
{
ZoneScopedN("teardown root");
assert(cycle_ == kUpdate);
env.TearDownRoot();
} }
env.TearDownRoot();
// notify other threads that the destruction is done // notify other threads that the destruction is done
{ {
@@ -455,12 +579,21 @@ int main(int, char**) {
cycle_cv_.notify_all(); cycle_cv_.notify_all();
} }
// wait for all tasks {
while (mainq_.size() || subq_.size() || asyncq_.size() || glq_.size()) { ZoneScopedN("wait for all tasks");
std::this_thread::sleep_for(30ms); for (;;) {
{
ZoneScopedN("check remained tasks");
std::unique_lock<std::shared_mutex> sk {task_mtx_};
if (!mainq_.size() && !subq_.size() && !asyncq_.size() && !glq_.size()) {
break;
}
}
std::this_thread::sleep_for(30ms);
}
} }
// exit worker and async threads // request SyncWorker and AsyncWorker to exit
{ {
alive_ = false; alive_ = false;
cycle_ = kSyncUpdate; cycle_ = kSyncUpdate;
@@ -469,23 +602,46 @@ int main(int, char**) {
} }
for (auto& th : th_async) th.join(); for (auto& th : th_async) th.join();
th_worker.join(); th_worker.join();
TracyMessageL("SyncWorker and AsyncWorker exited");
// exit GL thread // wake up GLWorker to exit
{ {
cycle_ = kSyncDraw; cycle_ = kSyncDraw;
std::unique_lock<std::mutex> k {cycle_mtx_}; std::unique_lock<std::mutex> k {cycle_mtx_};
cycle_cv_.notify_all(); cycle_cv_.notify_all();
} }
th_gl.join(); th_gl.join();
TracyMessageL("GLWorker exited");
// tear down ImGUI {
ImGui_ImplOpenGL3_Shutdown(); ZoneScopedN("tear down everything");
ImGui_ImplGlfw_Shutdown();
ImPlot::DestroyContext();
ImGui::DestroyContext();
// tear down display // tear down ImGUI
glfwDestroyWindow(window); ImGui_ImplOpenGL3_Shutdown();
glfwTerminate(); ImGui_ImplGlfw_Shutdown();
ImPlot::DestroyContext();
ImGui::DestroyContext();
// tear down display
glfwDestroyWindow(window);
glfwTerminate();
}
return 0; return 0;
} }
void* operator new(size_t n) {
auto ptr = std::malloc(n);
if (!ptr) {
throw nf7::Exception {"allocation failure"};
}
TracyAlloc(ptr, n);
return ptr;
}
void operator delete(void* ptr) noexcept {
TracyFree(ptr);
std::free(ptr);
}
void operator delete(void* ptr, size_t) noexcept {
operator delete(ptr);
}

42
nf7.cc
View File

@@ -1,6 +1,7 @@
#include "nf7.hh" #include "nf7.hh"
#include <algorithm> #include <algorithm>
#include <atomic>
#include <cassert> #include <cassert>
#include <map> #include <map>
#include <sstream> #include <sstream>
@@ -87,14 +88,14 @@ void File::MakeAsRoot() noexcept {
assert(id_ == 0); assert(id_ == 0);
assert(name_.empty()); assert(name_.empty());
id_ = env_->AddFile(*this); id_ = env_->AddFile(*this);
name_ = "$"; name_ = "$"s;
Handle({ .id = id_, .type = File::Event::kAdd }); env_->Handle({ .id = id_, .type = File::Event::kAdd });
} }
void File::Isolate() noexcept { void File::Isolate() noexcept {
assert(id_ != 0); assert(id_ != 0);
Handle({ .id = id_, .type = File::Event::kRemove }); env_->Handle({ .id = id_, .type = File::Event::kRemove });
env_->RemoveFile(id_); env_->RemoveFile(id_);
id_ = 0; id_ = 0;
@@ -105,12 +106,13 @@ void File::Touch() noexcept {
if (std::exchange(touch_, true)) { if (std::exchange(touch_, true)) {
return; return;
} }
env().ExecMain(std::make_shared<nf7::GenericContext>(*this), [this]() { env().ExecSub(
if (id()) { std::make_shared<nf7::GenericContext>(*this),
env().Handle( {.id = id(), .type = Event::kUpdate}); [this, &env = env(), fid = id()]() {
} if (env.Handle({ .id = fid, .type = nf7::File::Event::kUpdate })) {
touch_ = false; touch_ = false;
}); }
});
} }
File& File::FindOrThrow(std::string_view name) const { File& File::FindOrThrow(std::string_view name) const {
if (auto ret = Find(name)) return *ret; if (auto ret = Find(name)) return *ret;
@@ -120,6 +122,10 @@ File& File::ResolveOrThrow(const Path& p) const
try { try {
assert(id_ != 0); assert(id_ != 0);
if (p.terms().empty()) {
throw nf7::Exception {"empty path"};
}
auto ret = const_cast<File*>(this); auto ret = const_cast<File*>(this);
for (const auto& term : p.terms()) { for (const auto& term : p.terms()) {
if (term == "..") { if (term == "..") {
@@ -248,8 +254,10 @@ Context::Context(File& f, const std::shared_ptr<Context>& parent) noexcept :
Context::Context(Env& env, File::Id initiator, const std::shared_ptr<Context>& parent) noexcept : Context::Context(Env& env, File::Id initiator, const std::shared_ptr<Context>& parent) noexcept :
env_(&env), initiator_(initiator), env_(&env), initiator_(initiator),
parent_(parent), depth_(parent? parent->depth()+1: 0) { parent_(parent), depth_(parent? parent->depth()+1: 0) {
env_->AddContext(*this);
} }
Context::~Context() noexcept { Context::~Context() noexcept {
env_->RemoveContext(*this);
} }
File& Env::GetFileOrThrow(File::Id id) const { File& Env::GetFileOrThrow(File::Id id) const {
@@ -260,10 +268,22 @@ File& Env::GetFileOrThrow(File::Id id) const {
Env::Watcher::Watcher(Env& env) noexcept : env_(&env) { Env::Watcher::Watcher(Env& env) noexcept : env_(&env) {
} }
Env::Watcher::~Watcher() noexcept { Env::Watcher::~Watcher() noexcept {
env_->RemoveWatcher(*this); for (auto id : targets_) {
env_->RemoveWatcher(id, *this);
}
} }
void Env::Watcher::Watch(File::Id id) noexcept { void Env::Watcher::Watch(File::Id id) noexcept {
env_->AddWatcher(id, *this); if (targets_.end() == std::find(targets_.begin(), targets_.end(), id)) {
targets_.push_back(id);
env_->AddWatcher(id, *this);
}
}
void Env::Watcher::Unwatch(File::Id id) noexcept {
auto itr = std::remove(targets_.begin(), targets_.end(), id);
if (itr != targets_.end()) {
targets_.erase(itr);
env_->RemoveWatcher(id, *this);
}
} }

16
nf7.hh
View File

@@ -288,7 +288,8 @@ class Env {
virtual void Save() noexcept = 0; virtual void Save() noexcept = 0;
virtual void Throw(std::exception_ptr&&) noexcept = 0; virtual void Throw(std::exception_ptr&&) noexcept = 0;
virtual void Handle(const File::Event&) noexcept = 0; // returns the target file if alive
virtual nf7::File* Handle(const File::Event&) noexcept = 0;
virtual File* GetFile(File::Id) const noexcept = 0; virtual File* GetFile(File::Id) const noexcept = 0;
File& GetFileOrThrow(File::Id) const; File& GetFileOrThrow(File::Id) const;
@@ -300,8 +301,12 @@ class Env {
virtual File::Id AddFile(File&) noexcept = 0; virtual File::Id AddFile(File&) noexcept = 0;
virtual void RemoveFile(File::Id) noexcept = 0; virtual void RemoveFile(File::Id) noexcept = 0;
friend class nf7::Context;
virtual void AddContext(nf7::Context&) noexcept = 0;
virtual void RemoveContext(nf7::Context&) noexcept = 0;
virtual void AddWatcher(File::Id, Watcher&) noexcept = 0; virtual void AddWatcher(File::Id, Watcher&) noexcept = 0;
virtual void RemoveWatcher(Watcher&) noexcept = 0; virtual void RemoveWatcher(File::Id, Watcher&) noexcept = 0;
private: private:
std::filesystem::path npath_; std::filesystem::path npath_;
@@ -315,12 +320,17 @@ class Env::Watcher {
Watcher& operator=(const Watcher&) = delete; Watcher& operator=(const Watcher&) = delete;
Watcher& operator=(Watcher&&) = delete; Watcher& operator=(Watcher&&) = delete;
void Watch(File::Id) noexcept;
void Unwatch(File::Id) noexcept;
virtual void Handle(const File::Event&) noexcept = 0; virtual void Handle(const File::Event&) noexcept = 0;
void Watch(File::Id) noexcept; const std::vector<File::Id>& targets() const noexcept { return targets_; }
private: private:
Env* const env_; Env* const env_;
std::vector<File::Id> targets_;
}; };

View File

@@ -4,7 +4,7 @@
inline void SetUpImGuiStyle() noexcept { inline void SetUpImGuiStyle() noexcept {
// Visual Studio style by MomoDeve from ImThemes // based on Visual Studio style by MomoDeve from ImThemes
ImGuiStyle& style = ImGui::GetStyle(); ImGuiStyle& style = ImGui::GetStyle();
style.Alpha = 1.0f; style.Alpha = 1.0f;
@@ -37,7 +37,7 @@ inline void SetUpImGuiStyle() noexcept {
style.ColorButtonPosition = ImGuiDir_Right; style.ColorButtonPosition = ImGuiDir_Right;
style.ButtonTextAlign = ImVec2(0.5f, 0.5f); style.ButtonTextAlign = ImVec2(0.5f, 0.5f);
style.SelectableTextAlign = ImVec2(0.0f, 0.0f); style.SelectableTextAlign = ImVec2(0.0f, 0.0f);
style.Colors[ImGuiCol_Text] = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); style.Colors[ImGuiCol_Text] = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
style.Colors[ImGuiCol_TextDisabled] = ImVec4(0.5921568870544434f, 0.5921568870544434f, 0.5921568870544434f, 1.0f); style.Colors[ImGuiCol_TextDisabled] = ImVec4(0.5921568870544434f, 0.5921568870544434f, 0.5921568870544434f, 1.0f);
style.Colors[ImGuiCol_WindowBg] = ImVec4(0.1450980454683304f, 0.1450980454683304f, 0.1490196138620377f, 1.0f); style.Colors[ImGuiCol_WindowBg] = ImVec4(0.1450980454683304f, 0.1450980454683304f, 0.1490196138620377f, 1.0f);
@@ -62,7 +62,7 @@ inline void SetUpImGuiStyle() noexcept {
style.Colors[ImGuiCol_Button] = ImVec4(0.2000000029802322f, 0.2000000029802322f, 0.2156862765550613f, 1.0f); style.Colors[ImGuiCol_Button] = ImVec4(0.2000000029802322f, 0.2000000029802322f, 0.2156862765550613f, 1.0f);
style.Colors[ImGuiCol_ButtonHovered] = ImVec4(0.1137254908680916f, 0.5921568870544434f, 0.9254902005195618f, 1.0f); style.Colors[ImGuiCol_ButtonHovered] = ImVec4(0.1137254908680916f, 0.5921568870544434f, 0.9254902005195618f, 1.0f);
style.Colors[ImGuiCol_ButtonActive] = ImVec4(0.1137254908680916f, 0.5921568870544434f, 0.9254902005195618f, 1.0f); style.Colors[ImGuiCol_ButtonActive] = ImVec4(0.1137254908680916f, 0.5921568870544434f, 0.9254902005195618f, 1.0f);
style.Colors[ImGuiCol_Header] = ImVec4(0.2000000029802322f, 0.2000000029802322f, 0.2156862765550613f, 1.0f); style.Colors[ImGuiCol_Header] = ImVec4(0.09302068501710892f, 0.2956726551055908f, 0.4334763884544373f, 1.0f);
style.Colors[ImGuiCol_HeaderHovered] = ImVec4(0.1137254908680916f, 0.5921568870544434f, 0.9254902005195618f, 1.0f); style.Colors[ImGuiCol_HeaderHovered] = ImVec4(0.1137254908680916f, 0.5921568870544434f, 0.9254902005195618f, 1.0f);
style.Colors[ImGuiCol_HeaderActive] = ImVec4(0.0f, 0.4666666686534882f, 0.7843137383460999f, 1.0f); style.Colors[ImGuiCol_HeaderActive] = ImVec4(0.0f, 0.4666666686534882f, 0.7843137383460999f, 1.0f);
style.Colors[ImGuiCol_Separator] = ImVec4(0.3058823645114899f, 0.3058823645114899f, 0.3058823645114899f, 1.0f); style.Colors[ImGuiCol_Separator] = ImVec4(0.3058823645114899f, 0.3058823645114899f, 0.3058823645114899f, 1.0f);
@@ -86,7 +86,7 @@ inline void SetUpImGuiStyle() noexcept {
style.Colors[ImGuiCol_TableRowBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.0f); style.Colors[ImGuiCol_TableRowBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.0f);
style.Colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.0f, 1.0f, 1.0f, 0.05999999865889549f); style.Colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.0f, 1.0f, 1.0f, 0.05999999865889549f);
style.Colors[ImGuiCol_TextSelectedBg] = ImVec4(0.0f, 0.4666666686534882f, 0.7843137383460999f, 1.0f); style.Colors[ImGuiCol_TextSelectedBg] = ImVec4(0.0f, 0.4666666686534882f, 0.7843137383460999f, 1.0f);
style.Colors[ImGuiCol_DragDropTarget] = ImVec4(0.1450980454683304f, 0.1450980454683304f, 0.1490196138620377f, 1.0f); style.Colors[ImGuiCol_DragDropTarget] = ImVec4(0.8884119987487793f, 0.8520476222038269f, 0.1182007491588593f, 1.0f);
style.Colors[ImGuiCol_NavHighlight] = ImVec4(0.1450980454683304f, 0.1450980454683304f, 0.1490196138620377f, 1.0f); style.Colors[ImGuiCol_NavHighlight] = ImVec4(0.1450980454683304f, 0.1450980454683304f, 0.1490196138620377f, 1.0f);
style.Colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.0f, 1.0f, 1.0f, 0.699999988079071f); style.Colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.0f, 1.0f, 1.0f, 0.699999988079071f);
style.Colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.800000011920929f, 0.800000011920929f, 0.800000011920929f, 0.2000000029802322f); style.Colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.800000011920929f, 0.800000011920929f, 0.800000011920929f, 0.2000000029802322f);

View File

@@ -10,6 +10,38 @@ else()
endif() endif()
# ---- ExprTk ----
# repository: https://github.com/ArashPartow/exprtk
# license : MIT
FetchContent_Declare(
exprtk
URL "https://github.com/ArashPartow/exprtk/archive/refs/tags/0.0.1.zip"
)
FetchContent_Populate(exprtk)
add_library(exprtk INTERFACE)
target_include_directories(exprtk SYSTEM INTERFACE "${exprtk_SOURCE_DIR}")
# ---- FreeType ----
# repository: https://gitlab.freedesktop.org/freetype/freetype
# license : The FreeType License
FetchContent_Declare(
freetype
URL "https://gitlab.freedesktop.org/freetype/freetype/-/archive/VER-2-12-1/freetype-VER-2-12-1.zip"
)
set(FT_DISABLE_ZLIB ON CACHE BOOL "" FORCE)
set(FT_DISABLE_BZIP2 ON CACHE BOOL "" FORCE)
set(FT_DISABLE_PNG ON CACHE BOOL "" FORCE)
set(FT_DISABLE_HARFBUZZ ON CACHE BOOL "" FORCE)
set(FT_DISABLE_BROTLI ON CACHE BOOL "" FORCE)
set(FT_ENABLE_ERROR_STRINGS ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(freetype)
# ---- GLEW ---- # ---- GLEW ----
# repository: https://github.com/Perlmint/glew-cmake # repository: https://github.com/Perlmint/glew-cmake
# license : Modified BSD License, the Mesa 3-D License (MIT) and the Khronos License (MIT). # license : Modified BSD License, the Mesa 3-D License (MIT) and the Khronos License (MIT).
@@ -184,7 +216,10 @@ FetchContent_Declare(
magic_enum magic_enum
URL "https://github.com/Neargye/magic_enum/archive/refs/tags/v0.8.1.zip" URL "https://github.com/Neargye/magic_enum/archive/refs/tags/v0.8.1.zip"
) )
FetchContent_MakeAvailable(magic_enum) FetchContent_Populate(magic_enum)
add_library(magic_enum INTERFACE)
target_include_directories(magic_enum SYSTEM INTERFACE ${magic_enum_SOURCE_DIR}/include)
# ---- miniaudio ---- # ---- miniaudio ----
@@ -198,7 +233,7 @@ FetchContent_Declare(
FetchContent_Populate(miniaudio) FetchContent_Populate(miniaudio)
add_library(miniaudio) add_library(miniaudio)
target_include_directories(miniaudio PUBLIC SYSTEM ${miniaudio_SOURCE_DIR}) target_include_directories(miniaudio SYSTEM PUBLIC ${miniaudio_SOURCE_DIR})
target_sources(miniaudio target_sources(miniaudio
PUBLIC PUBLIC
"${miniaudio_SOURCE_DIR}/miniaudio.h" "${miniaudio_SOURCE_DIR}/miniaudio.h"
@@ -213,6 +248,38 @@ target_include_directories(source_location SYSTEM INTERFACE .)
target_sources(source_location INTERFACE source_location.hh) target_sources(source_location INTERFACE source_location.hh)
# ---- stb ----
# repository: https://github.com/nothings/stb
# license : Unlicense
FetchContent_Declare(
stb
URL "https://github.com/nothings/stb/archive/8b5f1f37b5b75829fc72d38e7b5d4bcbf8a26d55.zip"
)
FetchContent_Populate(stb)
add_library(stb)
target_include_directories(stb SYSTEM PUBLIC ${stb_SOURCE_DIR})
target_sources(stb
PUBLIC
${stb_SOURCE_DIR}/stb_image.h
PRIVATE
stb.c
)
# ---- Tracy ----
# repository: https://github.com/wolfpld/tracy
# license : 3-clause BSD
FetchContent_Declare(
tracy
URL "https://github.com/wolfpld/tracy/archive/refs/tags/v0.9.zip"
)
set(TRACY_ENABLE ${NF7_PROFILE} CACHE BOOL "" FORCE)
set(TRACY_CALLSTACK ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(tracy)
# ---- yaml-cpp ---- # ---- yaml-cpp ----
# repository: https://github.com/jbeder/yaml-cpp # repository: https://github.com/jbeder/yaml-cpp
# license : MIT # license : MIT

View File

@@ -11,6 +11,9 @@ if (UNIX)
VERBATIM VERBATIM
) )
# To enable assertions, add the following options:
# XCFLAGS="-DLUA_USE_APICHECK -DLUA_USE_ASSERT -Og -g"
elseif (MINGW) elseif (MINGW)
find_program(MAKE mingw32-make REQUIRED) find_program(MAKE mingw32-make REQUIRED)

2
thirdparty/stb.c vendored Normal file
View File

@@ -0,0 +1,2 @@
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>