247 Commits

Author SHA1 Message Date
8b2f40d823 remove System/NFile
moves it to passpawn project
2022-12-22 22:14:48 +09:00
2834136eaf remove nf7->ctx.emit() from Node C API 2022-12-22 22:03:17 +09:00
528bb06523 remove Codec/StbImage 2022-12-21 23:07:41 +09:00
d604ede99b fix default arguments of nf7::Env::Exec() 2022-12-21 23:07:41 +09:00
87b5e461b8 enhance Node C API 2022-12-21 23:07:41 +09:00
0e50b539e5 implement emitting from DLL in Node/DLL 2022-12-18 20:25:55 +09:00
c8feba19c0 update version number 2022-12-17 12:15:31 +09:00
66a2b4f552 add Node/DLL 2022-12-17 12:09:04 +09:00
ee718b09f4 add nf7::gui::NPathButton() function 2022-12-17 12:09:04 +09:00
014c09e788 add nf7::GenericDir::Clear() method 2022-12-16 12:45:23 +09:00
69cd19ebb4 improve System/Logger to be able to focus on the target file 2022-12-03 23:58:46 +09:00
c5d46f37d6 add nf7::File::RequestFocus() 2022-12-03 23:58:46 +09:00
5d796635b7 fix an issue that created Node goes away 2022-12-03 23:58:46 +09:00
fa6d7c36ed fix an issue that texture image displayed on tooltip is too huge 2022-11-30 17:50:59 +09:00
a0eaee59d7 fix an issue that GL/Program cannot take a texture by a file ID 2022-11-30 17:48:02 +09:00
1969f7cdbe add FileRef type to Value/Imm 2022-11-30 17:46:58 +09:00
a3e57406b9 fix an issue that context could leak when exit while loop script is running 2022-11-30 17:40:57 +09:00
382ecb374c display logo image on background 2022-11-30 17:15:09 +09:00
c053768146 add constants of app version 2022-11-30 17:06:40 +09:00
6ed9f7e463 set maximum duration for waiting finish of tasks while exitting Nf7 2022-11-29 13:00:52 +09:00
6e4f8aa114 add active indicator for Node/Network 2022-11-29 12:39:12 +09:00
1681e3ff32 improve usability of Value/Imm position 2D 2022-11-29 12:38:56 +09:00
62fc62da36 add Node/Comment 2022-11-29 11:38:43 +09:00
b2e5ccba8c remove unused thirdparty lib 2022-11-29 11:21:44 +09:00
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
fe7531260f fix simultaneous use of GL context 2022-11-03 21:53:24 +09:00
8e571ee446 implement blit of GL/Framebuffer 2022-11-03 11:54:59 +09:00
42bc1da204 add an assertion for checking OpenGL errors after each tasks 2022-11-03 11:54:23 +09:00
f0c4f893bd fix an issue of a possibility of race condition in unlocking mutex 2022-11-03 11:53:32 +09:00
8f6ff99136 improve ugly codes in locking OpenGL objects 2022-11-03 10:59:28 +09:00
5e515e23fa improve an interface of nf7::Value 2022-11-02 22:54:21 +09:00
3b25790f3c fix an issue that GL/Shader ignores included shader nfile updates 2022-11-02 21:53:22 +09:00
5e8fa70805 fix compiler errors on MSVC 2022-11-01 12:03:00 -07:00
b553f44f95 fix tiny bugs in draw action of GL/Program 2022-10-31 13:12:50 +09:00
08fcda65ca fix an issue that depth config is not applied to OpenGL program 2022-10-31 13:12:16 +09:00
4de72c20af implement Texture Viewer for GL/Texture 2022-10-31 12:18:27 +09:00
2a2c8f3109 fix an issue that unfinalized future might be refered 2022-10-29 22:55:32 +09:00
20b5217869 add string manipulation library to std table in LuaJIT 2022-10-29 22:54:43 +09:00
0cb8468a58 support drawing with depth test in GL/Program 2022-10-29 11:56:55 +09:00
fa1a29c325 support depth/stencil buffer 2022-10-29 11:50:42 +09:00
9fc39b986a (WIP) improve GL/Framebuffer to use depth/stencil buffers 2022-10-29 01:15:10 +09:00
e607a587c1 fix invalid memory reference 2022-10-29 01:14:15 +09:00
31bbf118e1 improve nf7::AggregatePromise not to ignore errors from children 2022-10-29 01:13:51 +09:00
8ef4abd75e refactor gl::Obj 2022-10-29 00:00:43 +09:00
694e9e34bb improve nf7::NFileWatcher to watch multiple native paths 2022-10-27 23:53:40 +09:00
c1f7328628 implement preprocessor for GLSL 2022-10-27 23:53:22 +09:00
dc3d8b15bf add GLSL version specifier automatically if not provided 2022-10-27 23:24:16 +09:00
05201ef13e enhance nf7::Future::Chain 2022-10-27 23:24:16 +09:00
1b424e299c implement automatic-cache-dropping of GL objects 2022-10-26 10:44:19 +09:00
3c09ac4491 enhance logging in OpenGL actions 2022-10-26 10:38:17 +09:00
1ee59aaedf improve usage of opengl PBO and fence 2022-10-26 09:42:02 +09:00
42738f4923 implement drawing with texture in GL/Program 2022-10-25 23:26:47 +09:00
af19f7807d fix behavior of download command in GL/Texture 2022-10-25 17:58:22 +09:00
9dada90b78 support GL_ELEMENT_ARRAY_BUFFER 2022-10-25 17:58:22 +09:00
419b9a98e2 commonize codes to lock GL objects using template 2022-10-25 16:34:16 +09:00
d5b5e664d7 use custom enum to represent metadata of GL objects 2022-10-25 11:43:58 +09:00
f869f191f2 split GLenum utility to other file 2022-10-25 11:06:18 +09:00
c42b63da0c add offset parameters in uploading texture through GL/Texture 2022-10-24 09:04:21 +09:00
1e33062e9a implement dowloading GL/Texture 2022-10-22 16:52:42 +09:00
ecb7fca1e8 support uniform variables in GL/Program 2022-10-21 12:50:09 +09:00
50f270b571 improve nf7::Value interface 2022-10-21 12:49:48 +09:00
e927179176 fix deadlock when failed to fetch VAO or FBO while GL/Program drawing 2022-10-21 11:09:18 +09:00
ffe3d6265d fix an recursion issue in nf7::luajit::ToValue() 2022-10-21 11:05:43 +09:00
3d14e709b1 add nf7::Value::integerOrScalar() accessor 2022-10-21 11:04:12 +09:00
a19b7636fa fix CMake CMP0135 issues
3b0d86c0a5 wasn't worked actually :(
2022-10-21 09:56:20 +09:00
c59d3e2f29 support GL/Program instanced drawing 2022-10-20 13:31:13 +09:00
7f90b40236 allow user to specify viewport before GL/Program drawing 2022-10-20 13:22:10 +09:00
396bebf6b5 implement clearing framebuffer 2022-10-20 13:09:39 +09:00
3b0d86c0a5 fix an issue of configuration on older CMake 2022-10-20 13:09:20 +09:00
fcf3dad206 implement draw command of GL/Program 2022-10-19 19:21:52 +09:00
9a637586e2 add new sugar syntax for nf7::Future::Chain 2022-10-19 19:21:03 +09:00
ca5dfb5933 add GL/Framebuffer 2022-10-19 12:25:21 +09:00
38fc3b680a improve GL/Texture to allocate image on initialization 2022-10-19 12:25:09 +09:00
96fd71df07 separate initialization codes of GL objects 2022-10-19 01:06:47 +09:00
0a55250f52 add GL/VertexArray 2022-10-17 17:49:31 +09:00
2c6608ea09 enhance nf7::Future::Then() method to allow caller to choose an executor 2022-10-15 10:29:35 +09:00
451094c9fc add GL/Program 2022-10-15 08:19:07 +09:00
94615b3669 add nf7::AggregatePromise 2022-10-15 08:14:58 +09:00
4e83f7b5e9 add GL/Shader 2022-10-12 10:25:19 +09:00
7ee770e67d add nf7::Future::Chain() method 2022-10-12 10:25:10 +09:00
4b79c5e4df add GL/Texture 2022-10-10 13:03:22 +09:00
58d39739e8 add GL/Buffer 2022-10-10 11:18:34 +09:00
77dc8cef32 improve nf7::Mutex 2022-10-09 17:59:13 +09:00
2edd7d9e88 improve Node interface 2022-10-07 11:53:36 +09:00
cafba96971 improve synchronization in main loop 2022-10-07 11:53:36 +09:00
e3dbcb016d unify LuaJIT/Node and LuaJIT/InlineNode into LuaJIT/Node
add std.import to LuaJIT std lib
2022-10-01 11:10:30 +09:00
4a25b88e25 add GL task executor to Env 2022-09-30 21:47:06 +09:00
5c84d95139 improve an NodeRootLambda interface on LuaJIT 2022-09-30 17:52:32 +09:00
f96188ef14 rename NodeRootSelectLambda -> NodeRootLambda
and improve common/luajit.hh
2022-09-30 17:18:46 +09:00
7d696cfbd9 remove unused headers 2022-09-30 16:29:15 +09:00
615d2eacb0 fix an issue that tasks pushed to Audio/Context aren't run when nf7 is shutting down 2022-09-28 13:46:45 +09:00
c2c4b83918 improve main.cc readability 2022-09-28 13:28:27 +09:00
4f13dd9456 refactor Audio/Device 2022-09-27 11:09:45 +09:00
7b2f9c8d55 improve device list of Audio/Context 2022-09-26 23:32:34 +09:00
b949383932 fix an issue that node sockets can be duplicated 2022-09-26 12:45:05 +09:00
5ef347fa2e fix an issue that global table is not applied in lua's main thread 2022-09-26 12:31:06 +09:00
f77a60831c refactor LuaJIT/InlineNode 2022-09-26 12:18:49 +09:00
8688ef98b6 add nf7::gui::Config 2022-09-26 12:00:46 +09:00
d0d6a2ebd5 refactor LuaJIT/Node 2022-09-26 12:00:46 +09:00
ab802d02e3 improve nf7::Future for using with const qulifier 2022-09-26 12:00:46 +09:00
007882ccfd fix data race 2022-09-26 02:15:13 +09:00
2082a6e482 enhance Future::Then() 2022-09-26 02:15:13 +09:00
8ffad3347f rename NativeFile -> NFile 2022-09-26 02:15:13 +09:00
7275e9a710 add NFileWatcher 2022-09-26 02:15:13 +09:00
46e6a78682 allow GenericMemento to access data through arrow operator
the arrow allows
2022-09-26 02:15:13 +09:00
09375ced9c restore nf7::Task once removed 2022-09-26 02:15:13 +09:00
79f3cc9639 add bitmanip functions to Lua script std table 2022-09-24 13:46:37 +09:00
f16937da5a update Lua std library 2022-09-23 10:33:02 +09:00
7dbda8d281 add Value/Plot 2022-09-23 09:36:16 +09:00
8879e9ed41 update a thirdparty lib, ImPlot 2022-09-23 09:36:16 +09:00
e045e86b11 add nf7::gui::ConfigPopup 2022-09-22 17:22:45 +09:00
07b198f71e generalize nf7::util::Uniq 2022-09-22 17:22:45 +09:00
b004723464 add new thirdparty library, yaml-cpp 2022-09-22 17:22:45 +09:00
6e820daef8 add new thirdparty library, magic_enum
this is awesome X)
2022-09-20 22:27:00 +09:00
dcbd3594cf add EnumSerializer 2022-09-19 10:04:30 +09:00
c5590092fa fix synchronization issues 2022-09-15 19:36:21 +09:00
8339cc814a enable sanitizers on g++ 2022-09-15 19:36:00 +09:00
bbfee304bd add 'perform GC cycle' option to LuaJIT/Context menu 2022-09-14 15:38:08 +09:00
2040898bd7 fix an issue that items are not kept sorted in layers of Sequencer/Timeline 2022-09-14 15:25:47 +09:00
46ddb16128 implement exception handling in some complicated cases 2022-09-14 15:25:14 +09:00
d6a9c62a63 fix an issue that NodeRootSelectLambda violates assetion 2022-09-02 17:42:33 +09:00
a5f3e459bf enhance locking feature of System/NativeFile 2022-09-02 17:42:33 +09:00
336f436942 add Mutex 2022-09-02 17:42:33 +09:00
589cd4b4fc add NodeRootSelectLambda and remove luajit::Thread::Lambda 2022-08-31 15:04:49 +09:00
5505488661 fix unnatural English 2022-08-31 13:10:28 +09:00
f76960da1b fix compile errors on release build of g++ 2022-08-30 12:48:32 +09:00
108 changed files with 12469 additions and 6080 deletions

View File

@@ -1,17 +1,22 @@
cmake_minimum_required(VERSION 3.18)
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
cmake_policy(SET CMP0077 NEW)
# ---- configuration ----
project(nf7 C CXX)
option(NF7_STATIC "link all libs statically" ON)
option(NF7_SANITIZE_THREAD "use thread sanitizer" OFF)
option(NF7_SANITIZE "use various sanitizer" OFF)
option(NF7_PROFILE "profiler" OFF)
set(NF7_GENERATED_INCLUDE_DIR "${PROJECT_BINARY_DIR}/include/generated")
file(MAKE_DIRECTORY "${NF7_GENERATED_INCLUDE_DIR}")
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(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(NF7_CXX_FLAGS
set(NF7_OPTIONS_WARNING
$<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>:
-Wall -Werror -pedantic-errors -Wextra -Wconversion -Wsign-conversion>
$<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>>:
@@ -20,6 +25,25 @@ set(NF7_CXX_FLAGS
/W4 /WX /Zc:__cplusplus /external:anglebrackets /external:W0>
)
if (NF7_SANITIZE)
set(NF7_OPTIONS_SANITIZE
$<$<CONFIG:Debug>:$<$<CXX_COMPILER_ID:GNU>:
-fsanitize=address -fsanitize=undefined -fsanitize=leak -fno-omit-frame-pointer>>
)
endif()
if (NF7_SANITIZE_THREAD)
set(NF7_OPTIONS_SANITIZE
$<$<CONFIG:Debug>:$<$<CXX_COMPILER_ID:GNU>:
-fsanitize=thread -fno-omit-frame-pointer>>
)
endif()
set(NF7_GENERATED_INCLUDE_DIR "${PROJECT_BINARY_DIR}/include/generated")
file(MAKE_DIRECTORY "${NF7_GENERATED_INCLUDE_DIR}")
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
add_subdirectory(thirdparty EXCLUDE_FROM_ALL)
@@ -27,9 +51,15 @@ add_subdirectory(thirdparty EXCLUDE_FROM_ALL)
# ---- application ----
add_executable(nf7)
target_compile_options(nf7 PRIVATE ${NF7_CXX_FLAGS})
target_include_directories(nf7 PRIVATE . "${PROJECT_BINARY_DIR}/include")
target_compile_options(nf7 PRIVATE
${NF7_OPTIONS_WARNING}
${NF7_OPTIONS_SANITIZE}
$<$<CXX_COMPILER_ID:MSVC>:/bigobj>
)
target_link_options(nf7 PRIVATE
${NF7_OPTIONS_SANITIZE}
)
target_compile_definitions(nf7
PRIVATE
IMGUI_DEFINE_MATH_OPERATORS
@@ -44,59 +74,75 @@ target_sources(nf7
nf7.cc
nf7.hh
theme.hh
version.hh
common/aggregate_command.hh
common/aggregate_promise.hh
common/audio_queue.hh
common/config.hh
common/context_owner.hh
common/dir.hh
common/dir_item.hh
common/dll.hh
common/factory.hh
common/file_base.hh
common/file_holder.hh
common/file_holder.cc
common/font_queue.hh
common/future.hh
common/generic_config.hh
common/generic_context.hh
common/generic_dir.hh
common/generic_history.hh
common/generic_memento.hh
common/generic_type_info.hh
common/generic_watcher.hh
common/gl_enum.hh
common/gl_fence.hh
common/gl_obj.hh
common/gl_obj.cc
common/gl_shader_preproc.hh
common/gui.hh
common/gui.cc
common/gui_dnd.hh
common/gui_context.hh
common/gui_file.hh
common/gui_file.cc
common/gui_node.hh
common/gui_popup.hh
common/gui_popup.cc
common/gui_timeline.hh
common/gui_timeline.cc
common/gui_value.hh
common/gui_value.cc
common/gui_window.hh
common/gui_window.cc
common/history.hh
common/life.hh
common/logger.hh
common/logger_ref.hh
common/luajit.hh
common/luajit.cc
common/luajit_nfile_importer.hh
common/luajit_queue.hh
common/luajit_ref.hh
common/luajit_thread.hh
common/luajit_thread.cc
common/luajit_thread_lambda.hh
common/memento.hh
common/memento_recorder.hh
common/mutable_memento.hh
common/native_file.hh
common/mutex.hh
common/nfile.hh
common/nfile_watcher.hh
common/node.h
common/node.hh
common/node_link_store.hh
common/node_root_lambda.hh
common/ptr_selector.hh
common/pure_node_file.hh
common/queue.hh
common/ring_buffer.hh
common/sequencer.hh
common/squashed_history.hh
common/stopwatch.hh
common/task.hh
common/thread.hh
common/timed_queue.hh
common/util_string.hh
common/util_algorithm.hh
common/value.hh
common/yas_audio.hh
common/yaml_nf7.hh
common/yas_enum.hh
common/yas_imgui.hh
common/yas_imnodes.hh
common/yas_nf7.hh
@@ -104,38 +150,50 @@ target_sources(nf7
common/yas_std_filesystem.hh
common/yas_std_variant.hh
$<$<PLATFORM_ID:Linux>:common/native_file_unix.cc>
$<$<PLATFORM_ID:Windows>:common/native_file_win.cc>
$<$<PLATFORM_ID:Linux>:common/nfile_unix.cc>
$<$<PLATFORM_ID:Windows>:common/nfile_win.cc>
file/audio_context.cc
file/audio_device.cc
file/font_context.cc
file/font_face.cc
file/gl_obj.cc
file/luajit_context.cc
file/luajit_inline_node.cc
file/luajit_node.cc
file/node_imm.cc
file/node_comment.cc
file/node_dll.cc
file/node_exprtk.cc
file/node_mutex.cc
file/node_network.cc
file/node_ref.cc
file/node_singleton.cc
file/node_ziptie.cc
file/sequencer_adaptor.cc
file/sequencer_call.cc
file/sequencer_timeline.cc
file/system_call.cc
file/system_dir.cc
file/system_event.cc
file/system_imgui.cc
file/system_logger.cc
file/system_native_file.cc
file/system_node.cc
file/value_curve.cc
file/value_imm.cc
file/value_plot.cc
)
target_link_libraries(nf7
PRIVATE
exprtk
freetype
glew
glfw
imgui
imnodes
implot
linalg.h
luajit
magic_enum
miniaudio
source_location
TracyClient
yas
yaml-cpp
)

View File

@@ -1,8 +1,67 @@
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
WTFPLv2
Do What The Fuck You Want To Public License v2
*-- expression has nothing without the real free --*

View File

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

View File

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

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 {
public:
enum Flag : uint8_t {
enum Flag : uint16_t {
kNone = 0,
kTree = 1 << 0,
kMenu = 1 << 1,
kTooltip = 1 << 2,
kWidget = 1 << 3,
kDragDropTarget = 1 << 4,
// Update() will be called earlier than other items.
// This is used by some system files and meaningless in most of cases.
kEarlyUpdate = 1 << 5,
// suggests to forbid to move/remove/clone through UI
kImportant = 1 << 6,
};
using Flags = uint8_t;

72
common/dll.hh Normal file
View File

@@ -0,0 +1,72 @@
#pragma once
#include <memory>
#include <string_view>
#if defined(__unix__)
# include <dlfcn.h>
#endif
#include "nf7.hh"
#include "common/future.hh"
namespace nf7 {
struct DLL final {
public:
class Exception : public nf7::Exception {
public:
using nf7::Exception::Exception;
};
static nf7::Future<std::shared_ptr<DLL>> Create(
const std::shared_ptr<nf7::Context>& ctx, const std::string& p) noexcept {
nf7::Future<std::shared_ptr<DLL>>::Promise pro {ctx};
ctx->env().ExecAsync(ctx, [p, pro]() mutable {
pro.Wrap([&]() { return std::make_shared<nf7::DLL>(p.c_str()); });
});
return pro.future();
}
explicit DLL(const char* p) : ptr_(Open(p)) {
}
~DLL() noexcept {
Close(ptr_);
}
DLL(const DLL&) = delete;
DLL(DLL&&) = delete;
DLL& operator=(const DLL&) = delete;
DLL& operator=(DLL&&) = delete;
template <typename R, typename... Args>
std::function<R(Args...)> Resolve(const char* name) {
return reinterpret_cast<R (*)(Args...)>(Resolve(ptr_, name));
}
private:
void* ptr_;
# if defined(__unix__)
static void* Open(const char* p) {
if (auto ret = dlopen(p, RTLD_LAZY | RTLD_LOCAL)) {
return ret;
}
throw DLL::Exception {dlerror()};
}
static void Close(void* ptr) noexcept {
dlclose(ptr);
}
static void* Resolve(void* ptr, const char* name) {
if (auto ret = dlsym(ptr, name)) {
return ret;
}
throw DLL::Exception {dlerror()};
}
# else
# error "unknown OS"
# endif
};
} // namespace nf7

27
common/factory.hh Normal file
View File

@@ -0,0 +1,27 @@
#pragma once
#include "nf7.hh"
#include "common/future.hh"
namespace nf7 {
template <typename T>
class Factory : public nf7::File::Interface {
public:
using Product = T;
Factory() = default;
Factory(const Factory&) = delete;
Factory(Factory&&) = delete;
Factory& operator=(const Factory&) = delete;
Factory& operator=(Factory&&) = delete;
virtual Product Create() noexcept = 0;
};
template <typename T>
using AsyncFactory = Factory<nf7::Future<T>>;
} // namespace nf7

View File

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

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

View File

@@ -24,16 +24,23 @@ namespace nf7 {
// 3. Call Promise::Return(T) or Promise::Throw() to finish the promise.
//
// Users who receive Future can wait for finishing
// by Future::Then(), Future::ThenSub(), or co_await.
// by Future::Then(), Future::ThenIf(), Future::Catch(), or co_await.
class CoroutineAbortException final : public nf7::Exception {
public:
using nf7::Exception::Exception;
};
// T must not be void, use std::monostate instead
template <typename T>
class Future final {
public:
class Promise;
class Coro;
using Product = T;
using ThisFuture = nf7::Future<T>;
using Handle = std::coroutine_handle<Promise>;
using Imm = std::variant<T, std::exception_ptr>;
@@ -59,7 +66,7 @@ class Future final {
// Factory side have this to tell finish or abort.
class Promise final {
public:
// Use data_() instead, MSVC doesn't allow this:
// Use data_() instead, MSVC can't understand the followings:
// template <typename U> friend class nf7::Future<U>;
// template <typename U> friend class nf7::Future<U>::Coro;
@@ -90,11 +97,14 @@ class Future final {
auto Return(T&& v) noexcept {
std::unique_lock<std::mutex> k(data_->mtx);
if (data_->state == kYet) {
data_->state = kDone;
data_->value = std::move(v);
data_->state = kDone;
CallReceivers();
}
}
auto Return(const T& v) noexcept {
Return(T {v});
}
// thread-safe
void Throw(std::exception_ptr e) noexcept {
std::unique_lock<std::mutex> k(data_->mtx);
@@ -104,21 +114,25 @@ class Future final {
CallReceivers();
}
}
template <typename E, typename... Args>
void Throw(Args&&... args) noexcept {
return Throw(std::make_exception_ptr<E>(E {std::forward<Args>(args)...}));
}
// thread-safe
// Do Return(f()) if no exception is thrown, otherwise call Throw().
auto Wrap(const std::function<T()>& f) noexcept
try {
Return(f());
} catch (Exception&) {
} catch (...) {
Throw(std::current_exception());
}
// thread-safe
// Creates Future() object.
Future future() const noexcept {
ThisFuture future() const noexcept {
assert(data_);
return Future(data_);
return ThisFuture(data_);
}
auto get_return_object() noexcept {
@@ -179,14 +193,14 @@ class Future final {
Coro& operator=(const Coro&) = delete;
Coro& operator=(Coro&&) = default;
Future Start(const std::shared_ptr<nf7::Context>& ctx) noexcept {
ThisFuture Start(const std::shared_ptr<nf7::Context>& ctx) noexcept {
ctx->env().ExecSub(ctx, [h = h_]() { h.resume(); });
data_->ctx = ctx;
return Future(data_);
return ThisFuture(data_);
}
void Abort() noexcept {
h_.promise().Throw(
std::make_exception_ptr<nf7::Exception>({"coroutine aborted"}));
std::make_exception_ptr<CoroutineAbortException>({"coroutine aborted"}));
data_->aborted = true;
}
@@ -208,43 +222,126 @@ class Future final {
}
Future(Imm&& imm) noexcept : imm_(std::move(imm)) {
}
Future(const Future&) = default;
Future(Future&&) = default;
Future& operator=(const Future&) = default;
Future& operator=(Future&&) = default;
Future(const ThisFuture&) = default;
Future(ThisFuture&&) = default;
Future& operator=(const ThisFuture&) = default;
Future& operator=(ThisFuture&&) = default;
// Schedules to execute f() immediately on any thread
// when the promise is finished or aborted.
Future& Then(std::function<void(Future)>&& f) noexcept {
// If ctx is not nullptr, the function will be run in the executor.
ThisFuture& Then(nf7::Env::Executor exec,
const std::shared_ptr<nf7::Context>& ctx,
std::function<void(const ThisFuture&)>&& f) noexcept {
auto fun = std::move(f);
if (ctx) {
fun = [exec, ctx, fun = std::move(fun)](auto& fu) {
ctx->env().Exec(
exec, ctx, [fu, fun = std::move(fun)]() mutable { fun(fu); });
};
}
if (data_) {
std::unique_lock<std::mutex> k(data_->mtx);
if (yet()) {
data_->recv.push_back(
[d = data_, f = std::move(f)]() { f(Future(d)); });
[fun = std::move(fun), d = data_]() mutable { fun(ThisFuture {d}); });
return *this;
}
}
f(*this);
fun(*this);
return *this;
}
template <typename R>
nf7::Future<R> Then(nf7::Env::Executor exec,
const std::shared_ptr<nf7::Context>& ctx,
std::function<void(const ThisFuture&, typename nf7::Future<R>::Promise&)>&& f) noexcept {
typename nf7::Future<R>::Promise pro;
Then(exec, ctx, [pro, f = std::move(f)](auto& fu) mutable {
try {
f(fu, pro);
} catch (...) {
pro.Throw(std::current_exception());
}
});
return pro.future();
}
template <typename F>
ThisFuture& Then(const std::shared_ptr<nf7::Context>& ctx, F&& f) noexcept {
return Then(nf7::Env::kSub, ctx, std::move(f));
}
template <typename F>
ThisFuture& Then(F&& f) noexcept {
return Then(nullptr, std::move(f));
}
// Schedules to execute f() as a sub task when the promise is finished or aborted.
Future& ThenSub(const std::shared_ptr<nf7::Context>& ctx,
std::function<void(Future)>&& f) noexcept {
if (data_) {
std::unique_lock<std::mutex> k(data_->mtx);
if (yet()) {
data_->recv.push_back([d = data_, ctx, f = std::move(f)]() {
ctx->env().ExecSub(ctx, std::bind(f, Future(d)));
// same as Then() but called when it's done without error
ThisFuture& ThenIf(nf7::Env::Executor exec,
const std::shared_ptr<nf7::Context>& ctx,
std::function<void(const T&)>&& f) noexcept {
Then(exec, ctx, [f = std::move(f)](auto& fu) {
if (fu.done()) f(fu.value());
});
return *this;
}
ThisFuture& ThenIf(const std::shared_ptr<nf7::Context>& ctx,
std::function<void(const T&)>&& f) noexcept {
return ThenIf(nf7::Env::kSub, ctx, std::move(f));
}
ctx->env().ExecSub(ctx, std::bind(f, *this));
return *this;
ThisFuture& ThenIf(std::function<void(const T&)>&& f) noexcept {
return ThenIf(nullptr, std::move(f));
}
auto& value() {
// same as Then() but called when it caused an exception
template <typename E>
ThisFuture& Catch(nf7::Env::Executor exec,
const std::shared_ptr<nf7::Context>& ctx,
std::function<void(E&)>&& f) noexcept {
Then(exec, ctx, [f = std::move(f)](auto& fu) {
try { fu.value(); } catch (E& e) { f(e); } catch (...) { }
});
return *this;
}
template <typename E>
ThisFuture& Catch(const std::shared_ptr<nf7::Context>& ctx,
std::function<void(E&)>&& f) noexcept {
return Catch<E>(nf7::Env::kSub, ctx, std::move(f));
}
template <typename E>
ThisFuture& Catch(std::function<void(E&)>&& f) noexcept {
return Catch<E>(nullptr, std::move(f));
}
// Finalizes the other promise on finalize of this future.
template <typename P, typename F>
ThisFuture& Chain(nf7::Env::Executor exec,
const std::shared_ptr<nf7::Context>& ctx,
P& pro, F&& func) noexcept {
return Then(exec, ctx, [pro, func = std::move(func)](auto& fu) mutable {
try {
if constexpr (std::is_void<decltype(func(fu.value()))>::value) {
func(fu.value());
} else {
pro.Return(func(fu.value()));
}
} catch (...) {
pro.Throw(std::current_exception());
}
});
}
template <typename P, typename F>
ThisFuture& Chain(const std::shared_ptr<nf7::Context>& ctx, P& pro, F&& func) noexcept {
return Chain(nf7::Env::kSub, ctx, pro, std::move(func));
}
template <typename P, typename F>
ThisFuture& Chain(P& pro, F&& func) noexcept {
return Chain(nullptr, pro, std::move(func));
}
template <typename P>
ThisFuture& Chain(P& pro) noexcept {
return Chain(pro, [](auto& v) { return v; });
}
const auto& value() const {
if (imm_) {
if (std::holds_alternative<T>(*imm_)) return std::get<T>(*imm_);
std::rethrow_exception(std::get<std::exception_ptr>(*imm_));
@@ -267,11 +364,14 @@ class Future final {
return !imm_ && data_->state == kYet;
}
bool done() const noexcept {
return (imm_ && std::holds_alternative<T>(*imm_)) || data_->state == kDone;
return
(imm_ && std::holds_alternative<T>(*imm_)) ||
(data_ && data_->state == kDone);
}
bool error() const noexcept {
return (imm_ && std::holds_alternative<std::exception_ptr>(*imm_)) ||
data_->state == kError;
return
(imm_ && std::holds_alternative<std::exception_ptr>(*imm_)) ||
(data_ && data_->state == kError);
}
bool await_ready() const noexcept { return !yet(); }
@@ -283,7 +383,6 @@ class Future final {
std::unique_lock<std::mutex> k(data.mtx);
auto callee_ctx = data.ctx.lock();
assert(callee_ctx);
auto caller_data = caller.promise().data__();
auto caller_ctx = caller_data->ctx.lock();
@@ -306,7 +405,7 @@ class Future final {
caller.resume();
}
}
auto await_resume() { return value(); }
auto& await_resume() { return value(); }
private:
std::optional<Imm> imm_;

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

View File

@@ -10,13 +10,18 @@
namespace nf7 {
class GenericContext : public Context {
class GenericContext : public nf7::Context {
public:
GenericContext(Env& env, File::Id id, std::string_view desc = "") noexcept :
Context(env, id), desc_(desc) {
GenericContext(nf7::Env& env,
nf7::File::Id id,
std::string_view desc = "",
const std::shared_ptr<nf7::Context>& parent = nullptr) noexcept :
nf7::Context(env, id, parent), desc_(desc) {
}
GenericContext(File& f, std::string_view desc = "") noexcept :
GenericContext(f.env(), f.id(), desc) {
GenericContext(nf7::File& f,
std::string_view desc = "",
const std::shared_ptr<nf7::Context>& parent = nullptr) noexcept :
GenericContext(f.env(), f.id(), desc, parent) {
}
void CleanUp() noexcept override {

180
common/generic_dir.hh Normal file
View File

@@ -0,0 +1,180 @@
#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);
}
void Clear() noexcept {
if (f_.id()) {
for (auto& p : items_) p.second->Isolate();
}
items_.clear();
}
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,28 +7,35 @@
#include "nf7.hh"
#include "common/mutable_memento.hh"
#include "common/file_base.hh"
#include "common/generic_context.hh"
#include "common/memento.hh"
namespace nf7 {
template <typename T>
class GenericMemento : public nf7::MutableMemento {
class GenericMemento : public nf7::FileBase::Feature, public nf7::Memento {
public:
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)) {
}
GenericMemento(T&& data, nf7::File& f) noexcept :
GenericMemento(std::move(data), &f) {
}
~GenericMemento() noexcept {
tag_ = nullptr;
last_ = nullptr;
assert(map_.empty());
}
T* operator->() noexcept {
return &data_;
}
const T* operator->() const noexcept {
return &data_;
}
std::shared_ptr<Tag> Save() noexcept override {
if (tag_) return tag_;
auto [itr, emplaced] = map_.emplace(next_++, data_);
@@ -43,20 +50,26 @@ class GenericMemento : public nf7::MutableMemento {
tag_ = tag;
last_ = tag;
onRestore();
if (file_) file_->Touch();
file_.Touch();
}
void Commit() noexcept override {
void Commit(bool quiet = false) noexcept {
tag_ = nullptr;
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;
auto itr = map_.find(tag_->id());
assert(itr != map_.end());
itr->second = data_;
onCommit();
if (file_) file_->Touch();
if (!quiet) file_.Touch();
}
void CommitAmendQuiet() noexcept {
CommitAmend(true);
}
T& data() noexcept { return data_; }
@@ -74,7 +87,7 @@ class GenericMemento : public nf7::MutableMemento {
std::function<void()> onCommit = [](){};
private:
nf7::File* const file_;
nf7::File& file_;
const T initial_;
T data_;
@@ -84,6 +97,19 @@ class GenericMemento : public nf7::MutableMemento {
std::shared_ptr<nf7::Memento::Tag> tag_;
std::shared_ptr<nf7::Memento::Tag> last_;
void Handle(const nf7::File::Event& e) noexcept override {
switch (e.type) {
case nf7::File::Event::kAdd:
file_.env().ExecMain(
std::make_shared<nf7::GenericContext>(file_),
[this]() { CommitQuiet(); });
return;
default:
return;
}
}
};
template <typename T>

View File

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

327
common/gl_enum.hh Normal file
View File

@@ -0,0 +1,327 @@
#pragma once
#include <cassert>
#include <cstdint>
#include <cstdlib>
#include <unordered_map>
#include <GL/glew.h>
#include <magic_enum.hpp>
#include "nf7.hh"
#include "common/yas_enum.hh"
namespace nf7::gl {
template <typename T>
struct EnumMeta {};
template <typename T>
GLenum ToEnum(T v) noexcept {
assert(EnumMeta<T>::glmap.contains(v));
return EnumMeta<T>::glmap.find(v)->second;
}
template <typename T>
GLenum ToEnum(const std::string& v)
try {
return ToEnum(magic_enum::enum_cast<T>(v).value());
} catch (std::bad_optional_access&) {
throw nf7::Exception {"unknown enum: "+v};
}
enum class NumericType : uint8_t {
U8 = 0x01,
I8 = 0x11,
U16 = 0x02,
I16 = 0x12,
U32 = 0x04,
I32 = 0x14,
F16 = 0x22,
F32 = 0x24,
F64 = 0x28,
};
template <>
struct EnumMeta<NumericType> {
static inline const std::unordered_map<NumericType, GLenum> glmap = {
{NumericType::U8, GL_UNSIGNED_BYTE},
{NumericType::I8, GL_BYTE},
{NumericType::U16, GL_UNSIGNED_SHORT},
{NumericType::I16, GL_SHORT},
{NumericType::U32, GL_UNSIGNED_INT},
{NumericType::I32, GL_INT},
{NumericType::F16, GL_HALF_FLOAT},
{NumericType::F32, GL_FLOAT},
{NumericType::F64, GL_DOUBLE},
};
};
inline uint8_t GetByteSize(NumericType t) noexcept {
return magic_enum::enum_integer(t) & 0xF;
}
enum class ColorComp : uint8_t {
R = 0x01,
G = 0x11,
B = 0x21,
RG = 0x02,
RGB = 0x03,
RGBA = 0x04,
};
template <>
struct EnumMeta<ColorComp> {
static inline const std::unordered_map<ColorComp, GLenum> glmap = {
{ColorComp::R, GL_RED},
{ColorComp::G, GL_GREEN},
{ColorComp::B, GL_BLUE},
{ColorComp::RG, GL_RG},
{ColorComp::RGB, GL_RGB},
{ColorComp::RGBA, GL_RGBA},
};
};
inline uint8_t GetCompCount(ColorComp c) noexcept {
return magic_enum::enum_integer(c) & 0xF;
}
enum class InternalFormat : uint8_t {
R8 = 0x01,
RG8 = 0x02,
RGB8 = 0x03,
RGBA8 = 0x04,
RF32 = 0x11,
RGF32 = 0x12,
RGBF32 = 0x13,
RGBAF32 = 0x14,
Depth16 = 0x21,
Depth24 = 0x31,
DepthF32 = 0x41,
Depth24_Stencil8 = 0x22,
DepthF32_Stencil8 = 0x32,
};
template <>
struct EnumMeta<InternalFormat> {
static inline const std::unordered_map<InternalFormat, GLenum> glmap = {
{InternalFormat::R8, GL_R8},
{InternalFormat::RG8, GL_RG8},
{InternalFormat::RGB8, GL_RGB8},
{InternalFormat::RGBA8, GL_RGBA8},
{InternalFormat::RF32, GL_R32F},
{InternalFormat::RGF32, GL_RG32F},
{InternalFormat::RGBF32, GL_RGB32F},
{InternalFormat::RGBAF32, GL_RGBA32F},
{InternalFormat::Depth16, GL_DEPTH_COMPONENT16},
{InternalFormat::Depth24, GL_DEPTH_COMPONENT24},
{InternalFormat::DepthF32, GL_DEPTH_COMPONENT32F},
{InternalFormat::Depth24_Stencil8, GL_DEPTH24_STENCIL8},
{InternalFormat::DepthF32_Stencil8, GL_DEPTH32F_STENCIL8},
};
};
inline uint8_t GetByteSize(InternalFormat fmt) noexcept {
switch (fmt) {
case InternalFormat::R8: return 1;
case InternalFormat::RG8: return 2;
case InternalFormat::RGB8: return 3;
case InternalFormat::RGBA8: return 4;
case InternalFormat::RF32: return 4;
case InternalFormat::RGF32: return 8;
case InternalFormat::RGBF32: return 12;
case InternalFormat::RGBAF32: return 16;
case InternalFormat::Depth16: return 2;
case InternalFormat::Depth24: return 3;
case InternalFormat::DepthF32: return 4;
case InternalFormat::Depth24_Stencil8: return 4;
case InternalFormat::DepthF32_Stencil8: return 5;
}
std::abort();
}
inline ColorComp GetColorComp(InternalFormat fmt) {
switch (fmt) {
case InternalFormat::R8: return ColorComp::R;
case InternalFormat::RG8: return ColorComp::RG;
case InternalFormat::RGB8: return ColorComp::RGB;
case InternalFormat::RGBA8: return ColorComp::RGBA;
case InternalFormat::RF32: return ColorComp::R;
case InternalFormat::RGF32: return ColorComp::RG;
case InternalFormat::RGBF32: return ColorComp::RGB;
case InternalFormat::RGBAF32: return ColorComp::RGBA;
default: throw nf7::Exception {"does not have color component"};
}
}
inline NumericType GetNumericType(InternalFormat fmt) {
switch (fmt) {
case InternalFormat::R8:
case InternalFormat::RG8:
case InternalFormat::RGB8:
case InternalFormat::RGBA8:
return NumericType::U8;
case InternalFormat::RF32:
case InternalFormat::RGF32:
case InternalFormat::RGBF32:
case InternalFormat::RGBAF32:
return NumericType::F32;
default:
throw nf7::Exception {"does not have color component"};
}
}
inline bool IsColor(InternalFormat fmt) noexcept {
return (magic_enum::enum_integer(fmt) & 0xF0) <= 1;
}
inline bool HasDepth(InternalFormat fmt) noexcept {
return !IsColor(fmt);
}
inline bool HasStencil(InternalFormat fmt) noexcept {
return
fmt == InternalFormat::Depth24_Stencil8 ||
fmt == InternalFormat::DepthF32_Stencil8;
}
enum class TestFunc {
Never,
Always,
Equal,
NotEqual,
Less,
LessOrEqual,
Greater,
GreaterOrEqual,
};
template <>
struct EnumMeta<TestFunc> {
static inline const std::unordered_map<TestFunc, GLenum> glmap = {
{TestFunc::Never, GL_NEVER},
{TestFunc::Always, GL_ALWAYS},
{TestFunc::Equal, GL_EQUAL},
{TestFunc::NotEqual, GL_NOTEQUAL},
{TestFunc::Less, GL_LESS},
{TestFunc::LessOrEqual, GL_LEQUAL},
{TestFunc::Greater, GL_GREATER},
{TestFunc::GreaterOrEqual, GL_GEQUAL},
};
};
enum class BufferTarget {
Array,
ElementArray,
};
template <>
struct EnumMeta<BufferTarget> {
static inline const std::unordered_map<BufferTarget, GLenum> glmap = {
{BufferTarget::Array, GL_ARRAY_BUFFER},
{BufferTarget::ElementArray, GL_ELEMENT_ARRAY_BUFFER},
};
};
enum class BufferUsage {
StaticDraw,
DynamicDraw,
StreamDraw,
StaticRead,
DynamicRead,
StreamRead,
StaticCopy,
DynamicCopy,
StreamCopy,
};
template <>
struct EnumMeta<BufferUsage> {
static inline const std::unordered_map<BufferUsage, GLenum> glmap = {
{BufferUsage::StaticDraw, GL_STATIC_DRAW},
{BufferUsage::DynamicDraw, GL_DYNAMIC_DRAW},
{BufferUsage::DynamicDraw, GL_STREAM_DRAW},
{BufferUsage::StaticRead, GL_STATIC_READ},
{BufferUsage::DynamicRead, GL_DYNAMIC_READ},
{BufferUsage::DynamicRead, GL_STREAM_READ},
{BufferUsage::StaticCopy, GL_STATIC_COPY},
{BufferUsage::DynamicCopy, GL_DYNAMIC_COPY},
{BufferUsage::DynamicCopy, GL_STREAM_COPY},
};
};
enum class TextureTarget : uint8_t {
Tex2D = 0x02,
Rect = 0x12,
};
template <>
struct EnumMeta<TextureTarget> {
static inline const std::unordered_map<TextureTarget, GLenum> glmap = {
{TextureTarget::Tex2D, GL_TEXTURE_2D},
{TextureTarget::Rect, GL_TEXTURE_RECTANGLE},
};
};
inline uint8_t GetDimension(TextureTarget t) noexcept {
return magic_enum::enum_integer(t) & 0xF;
}
enum class ShaderType {
Vertex,
Fragment,
};
template <>
struct EnumMeta<ShaderType> {
static inline const std::unordered_map<ShaderType, GLenum> glmap = {
{ShaderType::Vertex, GL_VERTEX_SHADER},
{ShaderType::Fragment, GL_FRAGMENT_SHADER},
};
};
enum class DrawMode {
Points,
LineStrip,
LineLoop,
Lines,
LineStripAdjacency,
LinesAdjacency,
TriangleStrip,
TriangleFan,
Triangles,
TriangleStripAdjacency,
TrianglesAdjacency,
};
template <>
struct EnumMeta<DrawMode> {
static inline const std::unordered_map<DrawMode, GLenum> glmap = {
{DrawMode::Points, GL_POINTS},
{DrawMode::LineStrip, GL_LINE_STRIP},
{DrawMode::LineLoop, GL_LINE_LOOP},
{DrawMode::Lines, GL_LINES},
{DrawMode::LineStripAdjacency, GL_LINE_STRIP_ADJACENCY},
{DrawMode::LinesAdjacency, GL_LINES_ADJACENCY},
{DrawMode::TriangleStrip, GL_TRIANGLE_STRIP},
{DrawMode::TriangleFan, GL_TRIANGLE_FAN},
{DrawMode::Triangles, GL_TRIANGLES},
{DrawMode::TriangleStripAdjacency, GL_TRIANGLE_STRIP_ADJACENCY},
{DrawMode::TrianglesAdjacency, GL_TRIANGLES_ADJACENCY},
};
};
} // namespace nf7::gl
namespace yas::detail {
NF7_YAS_DEFINE_ENUM_SERIALIZER(nf7::gl::NumericType);
NF7_YAS_DEFINE_ENUM_SERIALIZER(nf7::gl::ColorComp);
NF7_YAS_DEFINE_ENUM_SERIALIZER(nf7::gl::InternalFormat);
NF7_YAS_DEFINE_ENUM_SERIALIZER(nf7::gl::TestFunc);
NF7_YAS_DEFINE_ENUM_SERIALIZER(nf7::gl::BufferTarget);
NF7_YAS_DEFINE_ENUM_SERIALIZER(nf7::gl::BufferUsage);
NF7_YAS_DEFINE_ENUM_SERIALIZER(nf7::gl::TextureTarget);
NF7_YAS_DEFINE_ENUM_SERIALIZER(nf7::gl::ShaderType);
NF7_YAS_DEFINE_ENUM_SERIALIZER(nf7::gl::DrawMode);
} // namespace yas::detail

44
common/gl_fence.hh Normal file
View File

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

457
common/gl_obj.cc Normal file
View File

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

236
common/gl_obj.hh Normal file
View File

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

114
common/gl_shader_preproc.hh Normal file
View File

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

307
common/gui.cc Normal file
View File

@@ -0,0 +1,307 @@
#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.RequestFocus();
}
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());
}
}
bool NPathButton(const char* id, std::filesystem::path& p, nf7::Env& env) noexcept {
const auto pstr = p.string();
const auto w = ImGui::CalcItemWidth();
const auto dstr = pstr == ""? "(empty)": pstr.c_str();
const auto base = env.npath();
const auto full = base / p;
bool ret = false;
ImGui::PushID(id);
if (ImGui::Button(dstr, {w, 0})) {
ImGui::OpenPopup("editor");
}
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::TextUnformatted(dstr);
ImGui::Text("abs : %s", full.string().c_str());
ImGui::Text("base: %s", base.string().c_str());
ImGui::Indent();
if (!std::filesystem::exists(full)) {
ImGui::Bullet();
ImGui::TextUnformatted("the file doesn't seem to be existing");
}
ImGui::Unindent();
ImGui::EndTooltip();
}
if (id[0] != '#') {
ImGui::SameLine();
ImGui::TextUnformatted(id);
}
if (ImGui::BeginPopup("editor")) {
static std::string text;
if (ImGui::IsWindowAppearing()) {
text = pstr;
}
bool submit = false;
if (ImGui::InputText("npath", &text, ImGuiInputTextFlags_EnterReturnsTrue)) {
submit = true;
}
if (ImGui::Button("ok")) {
submit = true;
}
if (!std::filesystem::exists(base/text)) {
ImGui::Bullet();
ImGui::TextUnformatted("the file doesn't seem to be existing");
}
if (submit) {
p = text;
ret = true;
}
ImGui::EndPopup();
}
ImGui::PopID();
return ret;
}
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,43 @@
#pragma once
#include <cinttypes>
#include <cstdint>
#include <cstring>
#include <filesystem>
#include <string>
#include <imgui.h>
#include "nf7.hh"
#include "common/config.hh"
namespace nf7::gui {
// widgets
void FileMenuItems(nf7::File& f) noexcept;
void FileTooltip(nf7::File& f) noexcept;
bool PathButton (const char* id, nf7::File::Path&, nf7::File&) noexcept;
void ContextStack(const nf7::Context&) noexcept;
bool NPathButton(const char* id, std::filesystem::path&, nf7::Env&) noexcept;
void NodeSocket() noexcept;
void NodeInputSockets(std::span<const std::string>) noexcept;
void NodeOutputSockets(std::span<const std::string>) noexcept;
struct ConfigEditor {
public:
void operator()(nf7::Config&) noexcept;
private:
std::string text_;
std::string msg_;
bool mod_;
};
// stringify utility
inline std::string GetContextDisplayName(const nf7::Context& ctx) noexcept {
auto f = ctx.env().GetFile(ctx.initiator());
@@ -21,7 +49,6 @@ inline std::string GetContextDisplayName(const nf7::Context& ctx) noexcept {
return initiator + " " + buf;
}
inline std::string GetParentContextDisplayName(const nf7::Context& ctx) noexcept {
if (auto parent = ctx.parent()) {
return nf7::gui::GetContextDisplayName(*parent);
@@ -32,15 +59,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

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

View File

@@ -1,5 +1,6 @@
#pragma once
#include <atomic>
#include <cassert>
#include <memory>
@@ -8,38 +9,13 @@
namespace nf7 {
class LifeExpiredException final : public nf7::Exception {
public:
using nf7::Exception::Exception;
};
template <typename T>
class Life final {
public:
class Ref;
Life() = delete;
Life(T& target) noexcept : ptr_(&target) {
}
~Life() noexcept {
if (data_) data_->ptr = nullptr;
}
Life(const Life&) = delete;
Life(Life&&) = delete;
Life& operator=(const Life&) = delete;
Life& operator=(Life&&) = delete;
private:
T* const ptr_;
struct Data final {
T* ptr;
std::atomic<T*> ptr;
};
std::shared_ptr<Data> data_;
};
template <typename T>
class Life<T>::Ref final {
class Ref final {
public:
Ref() = default;
Ref(const Life& life) noexcept {
@@ -57,7 +33,7 @@ class Life<T>::Ref final {
void EnforceAlive() const {
if (!data_->ptr) {
throw LifeExpiredException {"target expired"};
throw nf7::ExpiredException {"target expired"};
}
}
@@ -73,8 +49,28 @@ class Life<T>::Ref final {
}
private:
std::shared_ptr<Data> data_;
};
Life() = delete;
Life(T& target) noexcept : ptr_(&target) {
}
~Life() noexcept {
if (data_) data_->ptr = nullptr;
}
Life(const Life&) = delete;
Life(Life&&) = delete;
Life& operator=(const Life&) = delete;
Life& operator=(Life&&) = delete;
Ref ref() const noexcept { return *this; }
private:
T* const ptr_;
std::shared_ptr<Data> data_;
};
} // namespace nf7

View File

@@ -4,9 +4,10 @@
#include <exception>
#include <memory>
#include <mutex>
#include <source_location>
#include <string_view>
#include <source_location.hh>
#include "nf7.hh"
#include "common/file_base.hh"
@@ -17,8 +18,8 @@ namespace nf7 {
class LoggerRef final : public nf7::FileBase::Feature {
public:
LoggerRef(nf7::File& f, nf7::File::Path&& p = {"_logger"}) noexcept :
file_(&f), path_(std::move(p)) {
LoggerRef(nf7::FileBase& f, nf7::File::Path&& p = {"_logger"}) noexcept :
nf7::FileBase::Feature(f), file_(&f), path_(std::move(p)) {
}
LoggerRef(const LoggerRef&) = default;
LoggerRef(LoggerRef&&) = default;

View File

@@ -7,22 +7,18 @@
#include <cctype>
#include <string>
#include <string_view>
#include <variant>
#include <lua.hpp>
#include "common/logger.hh"
#include "common/logger_ref.hh"
#include "common/luajit_thread.hh"
#include "common/luajit_std.hh"
namespace nf7::luajit {
// pushes original libraries
static void PushLuaLib(lua_State* L) noexcept;
static void PushMathLib(lua_State* L) noexcept;
static void PushTableLib(lua_State* L) noexcept;
static void PushTimeLib(lua_State* L) noexcept;
// buffer <-> lua value conversion
template <typename T>
static size_t PushArrayFromBytes(
@@ -33,79 +29,6 @@ template <typename T>
static size_t ToBytes(lua_State* L, uint8_t* ptr, uint8_t* end);
void PushGlobalTable(lua_State* L) noexcept {
if (luaL_newmetatable(L, "nf7::luajit::PushGlobalTable")) {
PushLuaLib(L);
lua_setfield(L, -2, "lua");
PushMathLib(L);
lua_setfield(L, -2, "math");
PushTableLib(L);
lua_setfield(L, -2, "table");
PushTimeLib(L);
lua_setfield(L, -2, "time");
lua_pushcfunction(L, [](auto L) {
if (lua_isstring(L, 2)) {
const char* type = lua_tostring(L, 2);
if (std::string_view {"integer"} == type) {
PushValue(L, static_cast<nf7::Value::Integer>(luaL_checkinteger(L, 1)));
} else {
return luaL_error(L, "unknown type specifier: %s", type);
}
} else {
PushValue(L, CheckValue(L, 1));
}
return 1;
});
lua_setfield(L, -2, "nf7_Value");
lua_pushcfunction(L, [](auto L) {
if (auto imm = ToVector(L, 1)) {
return 1;
}
if (auto mut = ToMutableVector(L, 1)) {
PushVector(L, std::make_shared<std::vector<uint8_t>>(std::move(*mut)));
return 1;
}
return luaL_error(L, "expected nf7::Value::MutableVector or nf7::Value::ConstVector");
});
lua_setfield(L, -2, "nf7_Vector");
lua_pushcfunction(L, [](auto L) {
if (auto imm = ToVector(L, 1)) {
if (imm->use_count() == 1) {
PushMutableVector(L, std::move(const_cast<std::vector<uint8_t>&>(**imm)));
} else {
PushMutableVector(L, std::vector<uint8_t> {**imm});
}
return 1;
}
if (auto mut = ToMutableVector(L, 1)) {
PushMutableVector(L, std::vector<uint8_t> {*mut});
return 1;
}
PushMutableVector(L, {});
return 1;
});
lua_setfield(L, -2, "nf7_MutableVector");
}
}
void PushImmEnv(lua_State* L) noexcept {
if (luaL_newmetatable(L, "nf7::luajit::PushImmEnv")) {
lua_createtable(L, 0, 0);
PushGlobalTable(L);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, [](auto L) { return luaL_error(L, "global is immutable"); });
lua_setfield(L, -2, "__newindex");
lua_setmetatable(L, -2);
}
}
void PushValue(lua_State* L, const nf7::Value& v) noexcept {
new (lua_newuserdata(L, sizeof(v))) nf7::Value(v);
@@ -123,30 +46,29 @@ void PushValue(lua_State* L, const nf7::Value& v) noexcept {
struct Visitor final {
lua_State* L;
const nf7::Value& v;
auto operator()(Value::Pulse) noexcept { lua_pushnil(L); }
auto operator()(Value::Boolean) noexcept { lua_pushboolean(L, v.boolean()); }
auto operator()(Value::Integer) noexcept { lua_pushinteger(L, v.integer()); }
auto operator()(Value::Scalar) noexcept { lua_pushnumber(L, v.scalar()); }
auto operator()(Value::String) noexcept { lua_pushstring(L, v.string().c_str()); }
auto operator()(Value::Vector) noexcept { PushVector(L, v.vector()); }
auto operator()(Value::DataPtr) noexcept { lua_pushnil(L); }
auto operator()(const Value::Pulse&) noexcept { lua_pushnil(L); }
auto operator()(const Value::Boolean& v) noexcept { lua_pushboolean(L, v); }
auto operator()(const Value::Integer& v) noexcept { lua_pushinteger(L, v); }
auto operator()(const Value::Scalar& v) noexcept { lua_pushnumber(L, v); }
auto operator()(const Value::String& v) noexcept { lua_pushstring(L, v.c_str()); }
auto operator()(const Value::ConstVector& v) noexcept { PushVector(L, v); }
auto operator()(const Value::DataPtr&) noexcept { lua_pushnil(L); }
auto operator()(Value::Tuple) noexcept {
const auto& tup = *v.tuple();
auto operator()(const Value::ConstTuple& v) noexcept {
const auto& tup = *v;
lua_createtable(L, 0, 0);
size_t arridx = 0;
for (auto& p : tup) {
PushValue(L, p.second);
if (p.first.empty()) {
lua_rawseti(L, -2, static_cast<int>(arridx++));
lua_rawseti(L, -2, static_cast<int>(++arridx));
} else {
lua_setfield(L, -2, p.first.c_str());
}
}
}
};
v.Visit(Visitor{.L = L, .v = v});
std::visit(Visitor {.L = L}, v.value());
return 1;
});
lua_setfield(L, -2, "value");
@@ -160,14 +82,65 @@ void PushValue(lua_State* L, const nf7::Value& v) noexcept {
}
lua_setmetatable(L, -2);
}
std::optional<nf7::Value> ToValue(lua_State* L, int idx) noexcept {
// get absolute position on stack because recursion call may occur
if (idx < 0) {
idx = lua_gettop(L)+idx+1;
}
if (lua_isnoneornil(L, idx)) {
return nf7::Value {nf7::Value::Pulse {}};
}
if (lua_isnumber(L, idx)) {
const double n = lua_tonumber(L, idx);
return nf7::Value {n};
}
if (lua_isboolean(L, idx)) {
return nf7::Value {bool {!!lua_toboolean(L, idx)}};
}
if (lua_isstring(L, idx)) {
size_t len;
const char* str = lua_tolstring(L, idx, &len);
return nf7::Value {std::string {str, len}};
}
if (auto vec = ToVector(L, idx)) {
return nf7::Value {std::move(*vec)};
}
if (auto vec = ToMutableVector(L, idx)) {
return nf7::Value {std::move(*vec)};
}
if (lua_istable(L, idx)) {
std::vector<nf7::Value::TuplePair> tup;
lua_pushnil(L);
while (lua_next(L, idx)) {
std::string name;
if (lua_type(L, -2) == LUA_TSTRING) {
name = lua_tostring(L, -2);
}
auto val = ToValue(L, -1);
if (!val) return std::nullopt;
tup.push_back({std::move(name), std::move(*val)});
lua_pop(L, 1);
}
return nf7::Value {std::move(tup)};
}
if (auto val = ToRef<nf7::Value>(L, idx, "nf7::Value")) {
return *val;
}
return std::nullopt;
}
void PushVector(lua_State* L, const nf7::Value::ConstVector& v) noexcept {
assert(v);
static const char* kTypeName = "nf7::Value::ConstVector";
using T = nf7::Value::ConstVector;
assert(v != nullptr);
new (lua_newuserdata(L, sizeof(v))) nf7::Value::ConstVector(v);
if (luaL_newmetatable(L, "nf7::Value::ConstVector")) {
if (luaL_newmetatable(L, kTypeName)) {
lua_createtable(L, 0, 0);
lua_pushcfunction(L, [](auto L) {
const auto& v = CheckRef<nf7::Value::ConstVector>(L, 1, "nf7::Value::ConstVector");
const auto& v = CheckRef<T>(L, 1, kTypeName);
const auto offset = luaL_checkinteger(L, 2);
if (offset < 0) {
return luaL_error(L, "negative offset");
@@ -247,14 +220,14 @@ void PushVector(lua_State* L, const nf7::Value::ConstVector& v) noexcept {
lua_setfield(L, -2, "get");
lua_pushcfunction(L, [](auto L) {
const auto& v = CheckRef<nf7::Value::ConstVector>(L, 1, "nf7::Value::ConstVector");
const auto& v = CheckRef<T>(L, 1, kTypeName);
lua_pushlstring(L, reinterpret_cast<const char*>(v->data()), v->size());
return 1;
});
lua_setfield(L, -2, "str");
lua_pushcfunction(L, [](auto L) {
const auto& v = CheckRef<nf7::Value::ConstVector>(L, 1, "nf7::Value::ConstVector");
const auto& v = CheckRef<T>(L, 1, kTypeName);
lua_pushinteger(L, static_cast<lua_Integer>(v->size()));
return 1;
});
@@ -262,20 +235,23 @@ void PushVector(lua_State* L, const nf7::Value::ConstVector& v) noexcept {
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, [](auto L) {
CheckRef<nf7::Value::ConstVector>(L, 1, "nf7::Value::ConstVector").~shared_ptr();
CheckRef<T>(L, 1, kTypeName).~shared_ptr();
return 0;
});
lua_setfield(L, -2, "__gc");
}
lua_setmetatable(L, -2);
}
void PushMutableVector(lua_State* L, std::vector<uint8_t>&& v) noexcept {
new (lua_newuserdata(L, sizeof(v))) std::vector<uint8_t>(std::move(v));
if (luaL_newmetatable(L, "nf7::Value::MutableVector")) {
void PushMutableVector(lua_State* L, std::vector<uint8_t>&& v) noexcept {
constexpr const char* kTypeName = "nf7::Value::MutableVector";
using T = std::vector<uint8_t>;
new (lua_newuserdata(L, sizeof(v))) T(std::move(v));
if (luaL_newmetatable(L, kTypeName)) {
lua_createtable(L, 0, 0);
lua_pushcfunction(L, [](auto L) {
auto& v = CheckRef<std::vector<uint8_t>>(L, 1, "nf7::Value::MutableVector");
auto& v = CheckRef<T>(L, 1, kTypeName);
const lua_Integer offset = luaL_checkinteger(L, 2);
if (offset < 0) return luaL_error(L, "negative offset");
@@ -319,7 +295,7 @@ void PushMutableVector(lua_State* L, std::vector<uint8_t>&& v) noexcept {
lua_setfield(L, -2, "set");
lua_pushcfunction(L, [](auto L) {
auto& v = CheckRef<std::vector<uint8_t>>(L, 1, "nf7::Value::MutableVector");
auto& v = CheckRef<T>(L, 1, kTypeName);
const lua_Integer size = luaL_checkinteger(L, 2);
if (size < 0) return luaL_error(L, "negative size");
v.resize(static_cast<size_t>(size));
@@ -328,10 +304,10 @@ void PushMutableVector(lua_State* L, std::vector<uint8_t>&& v) noexcept {
lua_setfield(L, -2, "resize");
lua_pushcfunction(L, [](auto L) {
auto& dst = CheckRef<std::vector<uint8_t>>(L, 1, "nf7::Value::MutableVector");
auto& dst = CheckRef<T>(L, 1, kTypeName);
const auto dst_off = luaL_checkinteger(L, 2);
const std::vector<uint8_t>* src;
const T* src;
if (const auto& v = ToVector(L, 3)) {
src = &**v;
} else if (const auto& mv = ToMutableVector(L, 3)) {
@@ -360,7 +336,69 @@ void PushMutableVector(lua_State* L, std::vector<uint8_t>&& v) noexcept {
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, [](auto L) {
CheckRef<std::vector<uint8_t>>(L, 1, "nf7::Value::MutableVector").~vector();
CheckRef<T>(L, 1, kTypeName).~vector();
return 0;
});
lua_setfield(L, -2, "__gc");
}
lua_setmetatable(L, -2);
}
void PushNodeRootLambda(
lua_State* L, const std::shared_ptr<nf7::NodeRootLambda>& la) noexcept {
assert(la);
using T = std::shared_ptr<nf7::NodeRootLambda>;
new (lua_newuserdata(L, sizeof(T))) T {la};
if (luaL_newmetatable(L, "nf7::NodeRootLambda")) {
lua_createtable(L, 0, 0);
{
// la:send(nf7, key, value)
lua_pushcfunction(L, [](auto L) {
auto la = CheckNodeRootLambda(L, 1);
la->ExecSend(luaL_checkstring(L, 2), luajit::CheckValue(L, 3));
return 0;
});
lua_setfield(L, -2, "send");
// la:recv(nf7, {name1, name2, ...})
lua_pushcfunction(L, [](auto L) {
auto la = CheckNodeRootLambda(L, 1);
auto th = luajit::Thread::GetPtr(L, 2);
std::vector<std::string> names;
ToStringList(L, 3, names);
if (names.size() == 0) {
return 0;
}
auto fu = la->Select(
std::unordered_set<std::string>(names.begin(), names.end()));
if (fu.done()) {
try {
const auto& p = fu.value();
lua_pushstring(L, p.first.c_str());
luajit::PushValue(L, p.second);
return 2;
} catch (nf7::Exception&) {
return 0;
}
} else {
fu.ThenIf([L, th](auto& p) {
th->ExecResume(L, p.first, p.second);
}).template Catch<nf7::Exception>(nullptr, [L, th](nf7::Exception&) {
th->ExecResume(L);
});
return th->Yield(L, la);
}
});
lua_setfield(L, -2, "recv");
}
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, [](auto L) {
CheckNodeRootLambda(L, 1).~shared_ptr();
return 0;
});
lua_setfield(L, -2, "__gc");
@@ -369,170 +407,30 @@ void PushMutableVector(lua_State* L, std::vector<uint8_t>&& v) noexcept {
}
std::optional<nf7::Value> ToValue(lua_State* L, int idx) noexcept {
if (lua_isnoneornil(L, idx)) {
return nf7::Value {nf7::Value::Pulse {}};
void PushGlobalTable(lua_State* L) noexcept {
if (luaL_newmetatable(L, "nf7::luajit::GlobalTable")) {
PushStdTable(L);
lua_setfield(L, -2, "std");
}
if (lua_isnumber(L, idx)) {
const double n = lua_tonumber(L, idx);
return nf7::Value {n};
}
if (lua_isboolean(L, idx)) {
return nf7::Value {bool {!!lua_toboolean(L, idx)}};
}
if (lua_isstring(L, idx)) {
size_t len;
const char* str = lua_tolstring(L, idx, &len);
return nf7::Value {std::string {str, len}};
}
if (auto vec = ToVector(L, idx)) {
return nf7::Value {std::move(*vec)};
}
if (auto vec = ToMutableVector(L, idx)) {
return nf7::Value {std::move(*vec)};
}
if (lua_istable(L, idx)) {
std::vector<nf7::Value::TuplePair> tup;
lua_pushnil(L);
while (lua_next(L, idx)) {
std::string name = "";
if (lua_isstring(L, -2)) {
name = lua_tostring(L, -2);
}
auto val = ToValue(L, -1);
if (!val) return std::nullopt;
tup.push_back({std::move(name), std::move(*val)});
lua_pop(L, 1);
}
return nf7::Value {std::move(tup)};
}
if (auto val = ToRef<nf7::Value>(L, idx, "nf7::Value")) {
return *val;
}
return std::nullopt;
}
std::optional<nf7::Value::ConstVector> ToVector(lua_State* L, int idx) noexcept {
auto ptr = ToRef<nf7::Value::ConstVector>(L, idx, "nf7::Value::ConstVector");
if (!ptr) return std::nullopt;
return *ptr;
}
std::optional<std::vector<uint8_t>> ToMutableVector(lua_State* L, int idx) noexcept {
auto ptr = ToRef<std::vector<uint8_t>>(L, idx, "nf7::Value::MutableVector");
if (!ptr) return std::nullopt;
return std::move(*ptr);
}
static void PushLuaLib(lua_State* L) noexcept {
lua_newuserdata(L, 0);
lua_createtable(L, 0, 0);
void PushImmEnv(lua_State* L) noexcept {
if (luaL_newmetatable(L, "nf7::luajit::ImmEnv")) {
lua_createtable(L, 0, 0);
{
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");
lua_pushcfunction(L, [](auto L) {
return luaL_error(L, luaL_checkstring(L, 1));
});
lua_setfield(L, -2, "error");
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");
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");
}
PushGlobalTable(L);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, [](auto L) { return luaL_error(L, "global is immutable"); });
lua_setfield(L, -2, "__newindex");
}
lua_setmetatable(L, -2);
}
}
static void PushMathLib(lua_State* L) noexcept {
lua_newuserdata(L, 0);
lua_createtable(L, 0, 0);
lua_createtable(L, 0, 0);
{
lua_pushcfunction(L, [](auto L) {
lua_pushnumber(L, std::sin(luaL_checknumber(L, 1)));
return 1;
});
lua_setfield(L, -2, "sin");
lua_pushcfunction(L, [](auto L) {
lua_pushnumber(L, std::cos(luaL_checknumber(L, 1)));
return 1;
});
lua_setfield(L, -2, "cos");
lua_pushcfunction(L, [](auto L) {
lua_pushnumber(L, std::tan(luaL_checknumber(L, 1)));
return 1;
});
lua_setfield(L, -2, "tan");
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");
}
lua_setfield(L, -2, "__index");
lua_setmetatable(L, -2);
}
static void PushTableLib(lua_State* L) noexcept {
lua_newuserdata(L, 0);
lua_createtable(L, 0, 0);
lua_createtable(L, 0, 0);
{
// table.setmetatable(table, meta_table)
lua_pushcfunction(L, [](auto L) {
luaL_checktype(L, 1, LUA_TTABLE);
luaL_checktype(L, 2, LUA_TTABLE);
lua_settop(L, 2);
lua_setmetatable(L, 1);
return 1;
});
lua_setfield(L, -2, "setmetatable");
}
lua_setfield(L, -2, "__index");
lua_setmetatable(L, -2);
}
static void PushTimeLib(lua_State* L) noexcept {
lua_newuserdata(L, 0);
lua_createtable(L, 0, 0);
lua_createtable(L, 0, 0);
{
// time.now()
lua_pushcfunction(L, [](auto L) {
const auto now = nf7::Env::Clock::now().time_since_epoch();
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now);
lua_pushnumber(L, static_cast<double>(ms.count())/1000.);
return 1;
});
lua_setfield(L, -2, "now");
}
lua_setfield(L, -2, "__index");
lua_setmetatable(L, -2);
}

View File

@@ -9,22 +9,91 @@
#include <lua.hpp>
#include "common/node_root_lambda.hh"
#include "common/value.hh"
namespace nf7::luajit {
void PushGlobalTable(lua_State*) noexcept;
void PushImmEnv(lua_State*) noexcept;
// ---- utility
inline bool MatchMetaName(lua_State* L, int idx, const char* type) noexcept {
if (0 == lua_getmetatable(L, idx)) {
return false;
}
luaL_getmetatable(L, type);
const bool ret = lua_rawequal(L, -1, -2);
lua_pop(L, 2);
return ret;
}
template <typename T, typename... Args>
inline T& NewUserData(lua_State* L, Args&&... args) noexcept {
return *(new (lua_newuserdata(L, sizeof(T))) T(std::forward<Args>(args)...));
}
// ---- reference conversion
template <typename T>
inline T* ToRef(lua_State* L, int idx, const char* type) noexcept {
return MatchMetaName(L, idx, type)? reinterpret_cast<T*>(lua_touserdata(L, idx)): nullptr;
}
template <typename T>
inline T& CheckRef(lua_State* L, int idx, const char* type) {
return *reinterpret_cast<T*>(luaL_checkudata(L, idx, type));
}
// ---- Value conversion
void PushValue(lua_State*, const nf7::Value&) noexcept;
void PushVector(lua_State*, const nf7::Value::ConstVector&) noexcept;
void PushMutableVector(lua_State*, std::vector<uint8_t>&&) noexcept;
std::optional<nf7::Value> ToValue(lua_State*, int) noexcept;
std::optional<nf7::Value::ConstVector> ToVector(lua_State*, int) noexcept;
std::optional<std::vector<uint8_t>> ToMutableVector(lua_State*, int) noexcept;
inline nf7::Value CheckValue(lua_State* L, int idx) {
auto v = ToValue(L, idx);
if (!v) luaL_error(L, "expected nf7::Value");
return std::move(*v);
}
void PushVector(lua_State*, const nf7::Value::ConstVector&) noexcept;
inline std::optional<nf7::Value::ConstVector> ToVector(lua_State* L, int idx) noexcept {
auto ptr = ToRef<nf7::Value::ConstVector>(L, idx, "nf7::Value::ConstVector");
if (!ptr) return std::nullopt;
return *ptr;
}
void PushMutableVector(lua_State*, std::vector<uint8_t>&&) noexcept;
inline std::optional<std::vector<uint8_t>> ToMutableVector(lua_State* L, int idx) noexcept {
auto ptr = ToRef<std::vector<uint8_t>>(L, idx, "nf7::Value::MutableVector");
if (!ptr) return std::nullopt;
return std::move(*ptr);
}
void PushNodeRootLambda(
lua_State*, const std::shared_ptr<nf7::NodeRootLambda>&) noexcept;
inline const std::shared_ptr<nf7::NodeRootLambda>& CheckNodeRootLambda(lua_State* L, int idx) {
return CheckRef<std::shared_ptr<nf7::NodeRootLambda>>(L, idx, "nf7::NodeRootLambda");
}
inline void ToStringList(lua_State* L, int idx, std::vector<std::string>& v) noexcept {
v.clear();
if (!lua_istable(L, idx)) {
if (auto str = lua_tostring(L, idx)) {
v.emplace_back(str);
}
return;
}
const size_t n = lua_objlen(L, idx);
v.reserve(n);
for (int i = 1; i <= static_cast<int>(n); ++i) {
lua_rawgeti(L, idx, i);
if (auto str = lua_tostring(L, -1)) {
v.push_back(str);
}
lua_pop(L, 1);
}
}
// ---- overloaded Push function for template
template <typename T>
void Push(lua_State* L, T v) noexcept {
if constexpr (std::is_integral<T>::value) {
@@ -52,7 +121,11 @@ inline void Push(lua_State* L, const std::vector<uint8_t>& v) noexcept {
inline void Push(lua_State* L, std::vector<uint8_t>&& v) noexcept {
luajit::PushMutableVector(L, std::move(v));
}
inline void Push(lua_State* L, const std::shared_ptr<nf7::NodeRootLambda>& la) noexcept {
luajit::PushNodeRootLambda(L, la);
}
// pushes all args and returns a number of them
inline int PushAll(lua_State*) noexcept {
return 0;
}
@@ -67,50 +140,9 @@ int PushAll(lua_State* L, T v, Args&&... args) noexcept {
}
template <typename T>
inline void PushWeakPtr(lua_State* L, const std::weak_ptr<T>& wptr) noexcept {
new (lua_newuserdata(L, sizeof(wptr))) std::weak_ptr<T>(wptr);
}
template <typename T>
inline void PushWeakPtrDeleter(lua_State* L, const std::weak_ptr<T>& = {}) noexcept {
lua_pushcfunction(L, [](auto L) {
reinterpret_cast<std::weak_ptr<T>*>(lua_touserdata(L, 1))->~weak_ptr();
return 0;
});
}
inline bool MatchMetaName(lua_State* L, int idx, const char* type) noexcept {
if (0 == lua_getmetatable(L, idx)) {
return false;
}
luaL_getmetatable(L, type);
const bool ret = lua_rawequal(L, -1, -2);
lua_pop(L, 2);
return ret;
}
template <typename T>
inline T* ToRef(lua_State* L, int idx, const char* type) noexcept {
return MatchMetaName(L, idx, type)? reinterpret_cast<T*>(lua_touserdata(L, idx)): nullptr;
}
template <typename T>
inline std::shared_ptr<T> CheckWeakPtr(lua_State* L, int idx, const char* type) {
auto ptr = reinterpret_cast<std::weak_ptr<T>*>(luaL_checkudata(L, idx, type));
if (auto ret = ptr->lock()) {
return ret;
} else {
luaL_error(L, "object expired: %s", typeid(T).name());
return nullptr;
}
}
template <typename T>
inline T& CheckRef(lua_State* L, int idx, const char* type) {
return *reinterpret_cast<T*>(luaL_checkudata(L, idx, type));
}
inline nf7::Value CheckValue(lua_State* L, int idx) {
auto v = ToValue(L, idx);
if (!v) luaL_error(L, "expected nf7::Value");
return std::move(*v);
}
// ---- global table
void PushGlobalTable(lua_State*) noexcept;
void PushImmEnv(lua_State*) noexcept;
void PushImmTable(lua_State*) noexcept;
} // namespace nf7

View File

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

View File

@@ -44,4 +44,8 @@ class Ref final : public nf7::Value::Data {
int idx_;
};
inline void Push(lua_State* L, const std::shared_ptr<Ref>& ref) noexcept {
ref->PushSelf(L);
}
} // namespace nf7::luajit

105
common/luajit_std.hh Normal file
View File

@@ -0,0 +1,105 @@
#include <lua.hpp>
#include "common/luajit.hh"
namespace nf7::luajit {
inline void PushStdTable(lua_State* L) noexcept {
luaL_openlibs(L);
lua_newuserdata(L, 0);
lua_createtable(L, 0, 0);
lua_createtable(L, 0, 0);
{
// ---- time lib ----
// now()
lua_pushcfunction(L, [](auto L) {
const auto now = nf7::Env::Clock::now().time_since_epoch();
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now);
lua_pushnumber(L, static_cast<double>(ms.count())/1000.);
return 1;
});
lua_setfield(L, -2, "now");
// ---- value lib ----
// value(entity) -> value
lua_pushcfunction(L, [](auto L) {
if (lua_isstring(L, 2)) {
const auto type = std::string_view {lua_tostring(L, 2)};
if (type == "integer" || type == "int") {
PushValue(L, static_cast<nf7::Value::Integer>(luaL_checkinteger(L, 1)));
} else {
return luaL_error(L, "unknown type specifier: %s", type);
}
} else {
PushValue(L, CheckValue(L, 1));
}
return 1;
});
lua_setfield(L, -2, "value");
// mvector(vector or mutable vector) -> mutable vector
lua_pushcfunction(L, [](auto L) {
if (auto imm = ToVector(L, 1)) {
if (imm->use_count() == 1) {
PushMutableVector(L, std::move(const_cast<std::vector<uint8_t>&>(**imm)));
} else {
PushMutableVector(L, std::vector<uint8_t> {**imm});
}
return 1;
} else if (auto mut = ToMutableVector(L, 1)) {
PushMutableVector(L, std::vector<uint8_t> {*mut});
return 1;
} else {
PushMutableVector(L, {});
return 1;
}
});
lua_setfield(L, -2, "mvector");
// ---- lua std libs ----
const auto Copy =
[L](const char* name, const char* expr, bool imm) {
luaL_loadstring(L, expr);
lua_call(L, 0, 1);
if (imm) {
PushImmTable(L);
lua_setmetatable(L, -2);
}
lua_setfield(L, -2, name);
};
Copy("assert", "return assert", false);
Copy("error", "return error", false);
Copy("ipairs", "return ipairs", false);
Copy("loadstring", "return loadstring", false);
Copy("next", "return next", false);
Copy("pairs", "return pairs", false);
Copy("pcall", "return pcall", false);
Copy("rawequal", "return rawequal", false);
Copy("rawget", "return rawget", false);
Copy("select", "return select", false);
Copy("setfenv", "return setfenv", false);
Copy("setmetatable", "return setmetatable", false);
Copy("tonumber", "return tonumber", false);
Copy("tostring", "return tostring", false);
Copy("type", "return type", false);
Copy("unpack", "return unpack", false);
Copy("_VERSION", "return _VERSION", false);
Copy("xpcall", "return xpcall", false);
Copy("bit", "return require(\"bit\")", true);
Copy("coroutine", "return coroutine", true);
Copy("math", "return math", true);
Copy("string", "return string", true);
Copy("table", "return table", true);
}
lua_setfield(L, -2, "__index");
lua_setmetatable(L, -2);
}
} // namespace nf7::luajit

View File

@@ -1,15 +1,19 @@
#include "common/luajit_thread.hh"
#include "common/luajit_thread_lambda.hh"
#include <chrono>
#include <sstream>
#include <tuple>
#include <unordered_set>
#include <tracy/Tracy.hpp>
#include "common/node.hh"
#include "common/node_root_lambda.hh"
namespace nf7::luajit {
constexpr size_t kInstructionLimit = 10000000;
constexpr size_t kBufferSizeMax = 64 * 1024 * 1024;
constexpr size_t kInstructionLimit = 100000;
// Pushes a metatable for Thread object, available on global table as 'nf7'.
@@ -20,9 +24,7 @@ lua_State* Thread::Init(lua_State* L) noexcept {
assert(state_ == kInitial);
th_ = lua_newthread(L);
PushImmEnv(L);
lua_setfenv(L, -2);
th_ref_.emplace(ctx_, ljq_, L);
th_ref_.emplace(shared_from_this(), ljq_, L);
state_ = kPaused;
return th_;
@@ -33,44 +35,59 @@ void Thread::Resume(lua_State* L, int narg) noexcept {
if (state_ == kAborted) return;
assert(L == th_);
assert(state_ == kPaused);
(void) L;
static const auto kHook = [](auto L, auto) {
luaL_error(L, "reached instruction limit (<=1e7)");
};
lua_sethook(th_, kHook, LUA_MASKCOUNT, kInstructionLimit);
PushGlobalTable(th_);
PushWeakPtr(th_, weak_from_this());
PushMeta(th_);
lua_setmetatable(th_, -2);
lua_setfield(th_, -2, "nf7");
lua_pop(th_, 1);
// set global table
PushGlobalTable(L);
NewUserData<std::weak_ptr<Thread>>(L, weak_from_this());
PushMeta(L);
lua_setmetatable(L, -2);
lua_setfield(L, -2, "nf7");
lua_pop(L, 1);
state_ = kRunning;
k.unlock();
active_ = true;
const auto ret = lua_resume(th_, narg);
active_ = false;
yield_ctx_.reset();
k.unlock();
int ret;
{
ZoneScopedN("lua_resume");
ret = lua_resume(L, narg);
}
k.lock();
active_ = false;
if (state_ == kAborted) return;
switch (ret) {
case 0:
th_ref_ = std::nullopt;
state_ = kFinished;
break;
case LUA_YIELD:
state_ = kPaused;
break;
default:
th_ref_ = std::nullopt;
state_ = kAborted;
}
if (!std::exchange(skip_handle_, false)) {
handler_(*this, th_);
if (!std::exchange(skip_handler_, false)) {
k.unlock();
handler_(*this, L);
}
}
void Thread::Abort() noexcept {
std::unique_lock<std::mutex> k(mtx_);
state_ = kAborted;
th_ref_ = std::nullopt;
auto wctx = std::move(yield_ctx_);
yield_ctx_.reset();
k.unlock();
if (auto ctx = wctx.lock()) {
if (ctx.get() != this) {
ctx->Abort();
}
}
}
@@ -119,26 +136,52 @@ Thread::Handler Thread::CreateNodeLambdaHandler(
static void PushMeta(lua_State* L) noexcept {
if (luaL_newmetatable(L, Thread::kTypeName)) {
PushWeakPtrDeleter<Thread>(L);
lua_pushcfunction(L, [](auto L) {
CheckRef<std::weak_ptr<Thread>>(L, 1, Thread::kTypeName).~weak_ptr();
return 0;
});
lua_setfield(L, -2, "__gc");
lua_createtable(L, 0, 0);
{
// nf7:import(npath)
lua_pushcfunction(L, [](auto L) {
auto th = Thread::GetPtr(L, 1);
auto im = th->importer();
if (!im) {
return luaL_error(L, "import is not available in the current thread");
}
if (const auto name = lua_tostring(L, 2)) {
auto fu = im->Import(th, name);
fu.ThenIf([L, th](auto& obj) {
th->ExecResume(L, obj);
}).template Catch<nf7::Exception>([L, th](auto&) {
if (auto log = th->logger()) {
log->Warn("import failed, returning nil");
}
th->ExecResume(L);
});
return th->Yield(L);
} else {
return luaL_error(L, "path should be a string");
}
});
lua_setfield(L, -2, "import");
// nf7:resolve(path)
lua_pushcfunction(L, [](auto L) {
auto th = Thread::GetPtr(L, 1);
auto base = th->ctx()->initiator();
auto base = th->initiator();
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 {
th->ExecResume(L, th->env().GetFileOrThrow(base).ResolveOrThrow(path).id());
} catch (nf7::File::NotFoundException&) {
th->ExecResume(L, 0);
}
});
th->ExpectYield(L);
return lua_yield(L, 0);
return th->Yield(L);
});
lua_setfield(L, -2, "resolve");
@@ -147,7 +190,7 @@ static void PushMeta(lua_State* L) noexcept {
auto th = Thread::GetPtr(L, 1);
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)});
return 1;
});
@@ -159,11 +202,13 @@ static void PushMeta(lua_State* L) noexcept {
const auto id = luaL_checkinteger(L, 2);
std::string iface = luaL_checkstring(L, 3);
th->env().ExecSub(th->ctx(), [th, L, id, iface = std::move(iface)]() {
th->env().ExecSub(th, [th, L, id, iface = std::move(iface)]() {
try {
auto& f = th->env().GetFileOrThrow(static_cast<nf7::File::Id>(id));
if (iface == "node") {
Thread::Lambda::CreateAndPush(L, th, f);
th->ExecResume(
L, nf7::NodeRootLambda::Create(
th, f.template interfaceOrThrow<nf7::Node>()));
} else {
throw nf7::Exception {"unknown interface: "+iface};
}
@@ -171,21 +216,20 @@ static void PushMeta(lua_State* L) noexcept {
th->ExecResume(L, nullptr, e.msg());
}
});
th->ExpectYield(L);
return lua_yield(L, 0);
return th->Yield(L);
});
lua_setfield(L, -2, "query");
// nf7:sleep(sec)
lua_pushcfunction(L, [](auto L) {
auto th = Thread::GetPtr(L, 1);
const auto sec = luaL_checknumber(L, 2);
const auto time = nf7::Env::Clock::now() +
std::chrono::milliseconds(static_cast<uint64_t>(sec*1000));
th->ljq()->Push(th->ctx(), [th, L](auto) { th->ExecResume(L); }, time);
th->ljq()->Push(th, [th, L](auto) { th->ExecResume(L); }, time);
th->ExpectYield(L);
return lua_yield(L, 0);
return th->Yield(L);
});
lua_setfield(L, -2, "sleep");

View File

@@ -14,6 +14,7 @@
#include "nf7.hh"
#include "common/context_owner.hh"
#include "common/future.hh"
#include "common/logger_ref.hh"
#include "common/luajit.hh"
@@ -23,45 +24,50 @@
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:
static constexpr const char* kTypeName = "nf7::luajit::Thread";
enum State { kInitial, kRunning, kPaused, kFinished, kAborted, };
using Handler = std::function<void(Thread&, lua_State*)>;
// Registry keeps an objects used in the Thread and deletes immediately when the Thread ends.
class RegistryItem;
class Lambda;
template <typename T> class Lock;
class Importer;
class Exception final : public nf7::Exception {
public:
using nf7::Exception::Exception;
};
// Creates a handler to finalize a promise.
// Creates a handler that finalizes a promise.
template <typename T>
static inline Handler CreatePromiseHandler(
nf7::Future<T>::Promise& pro, std::function<T(lua_State*)>&&) noexcept;
// Creates a handler to emit yielded value to Node::Lambda.
// Creates a handler that emits yielded value to Node::Lambda.
static Handler CreateNodeLambdaHandler(
const std::shared_ptr<nf7::Node::Lambda>& caller,
const std::shared_ptr<nf7::Node::Lambda>& callee) noexcept;
// must be called on luajit thread
static std::shared_ptr<Thread> GetPtr(lua_State* L, int idx) {
auto th = CheckWeakPtr<Thread>(L, idx, kTypeName);
auto th = CheckRef<std::weak_ptr<Thread>>(L, idx, kTypeName).lock();
if (th) {
th->EnsureActive(L);
return th;
} else {
luaL_error(L, "thread expired");
return nullptr;
}
}
Thread() = delete;
Thread(const std::shared_ptr<nf7::Context>& ctx,
Thread(const std::shared_ptr<nf7::Context>& parent,
const std::shared_ptr<nf7::luajit::Queue>& ljq,
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(Thread&&) = delete;
@@ -72,6 +78,15 @@ class Thread final : public std::enable_shared_from_this<Thread> {
assert(state_ == kInitial);
logger_ = logger;
}
void Install(const std::shared_ptr<Importer>& importer) noexcept {
assert(state_ == kInitial);
importer_ = importer;
}
void Install(const Thread& th) noexcept {
assert(state_ == kInitial);
logger_ = th.logger_;
importer_ = th.importer_;
}
// must be called on luajit thread
lua_State* Init(lua_State* L) noexcept;
@@ -81,9 +96,11 @@ class Thread final : public std::enable_shared_from_this<Thread> {
void Resume(lua_State* L, int narg) noexcept;
// must be called on luajit thread
// handler_ won't be called on next yielding
void ExpectYield(lua_State*) noexcept {
skip_handle_ = true;
// handler_ won't be called on this yielding
int Yield(lua_State* L, const std::shared_ptr<nf7::Context>& ctx = nullptr) {
yield_ctx_ = ctx;
skip_handler_ = true;
return lua_yield(L, 0);
}
// must be called on luajit thread
@@ -93,41 +110,33 @@ class Thread final : public std::enable_shared_from_this<Thread> {
}
}
// must be called on luajit thread
void Register(lua_State*, const std::shared_ptr<RegistryItem>& item) noexcept {
registry_.push_back(item);
}
void Forget(lua_State*, const RegistryItem& item) noexcept {
registry_.erase(
std::remove_if(registry_.begin(), registry_.end(),
[&item](auto& x) { return x.get() == &item; }),
registry_.end());
}
// thread-safe
void Abort() noexcept;
void Abort() noexcept override;
// queue a task that exec Resume()
// thread-safe
template <typename... Args>
void ExecResume(lua_State* L, Args&&... args) noexcept {
auto self = shared_from_this();
ljq_->Push(ctx_, [this, L, self, args...](auto) mutable {
ljq_->Push(self, [this, L, args...](auto) mutable {
Resume(L, luajit::PushAll(L, std::forward<Args>(args)...));
});
}
nf7::Env& env() noexcept { return ctx_->env(); }
const std::shared_ptr<nf7::Context>& ctx() const noexcept { return ctx_; }
std::string GetDescription() const noexcept override {
return "LuaJIT thread";
}
const std::shared_ptr<nf7::luajit::Queue>& ljq() const noexcept { return ljq_; }
const std::shared_ptr<nf7::LoggerRef>& logger() const noexcept { return logger_; }
const std::shared_ptr<Importer>& importer() const noexcept { return importer_; }
State state() const noexcept { return state_; }
private:
// initialized by constructor
std::mutex mtx_;
std::shared_ptr<nf7::Context> ctx_;
std::shared_ptr<nf7::luajit::Queue> ljq_;
Handler handler_;
@@ -141,40 +150,44 @@ class Thread final : public std::enable_shared_from_this<Thread> {
// installed features
std::shared_ptr<nf7::LoggerRef> logger_;
std::shared_ptr<Importer> importer_;
// mutable params
std::vector<std::shared_ptr<RegistryItem>> registry_;
bool active_ = false; // true while executing lua_resume
bool skip_handle_ = false; // handler_ won't be called on next yield
bool skip_handler_ = false;
std::weak_ptr<nf7::Context> yield_ctx_;
};
class Thread::RegistryItem {
class Thread::Importer {
public:
RegistryItem() = default;
virtual ~RegistryItem() = default;
RegistryItem(const RegistryItem&) = delete;
RegistryItem(RegistryItem&&) = delete;
RegistryItem& operator=(const RegistryItem&) = delete;
RegistryItem& operator=(RegistryItem&&) = delete;
Importer() = default;
virtual ~Importer() = default;
Importer(const Importer&) = delete;
Importer(Importer&&) = delete;
Importer& operator=(const Importer&) = delete;
Importer& operator=(Importer&&) = delete;
// be called on luajit thread
virtual nf7::Future<std::shared_ptr<luajit::Ref>> Import(
const std::shared_ptr<luajit::Thread>&, std::string_view) noexcept = 0;
};
template <typename T>
Thread::Handler Thread::CreatePromiseHandler(
nf7::Future<T>::Promise& pro, std::function<T(lua_State*)>&& f) noexcept {
return [&pro, f = std::move(f)](auto& self, auto L) {
return [pro = pro, f = std::move(f)](auto& self, auto L) mutable {
switch (self.state()) {
case kPaused:
pro.Throw(std::make_exception_ptr<nf7::Exception>({"unexpected yield"}));
pro.template Throw<nf7::Exception>("unexpected yield");
break;
case kFinished:
pro.Wrap([&]() { return f(L); });
break;
case kAborted:
pro.Throw(std::make_exception_ptr<nf7::Exception>({lua_tostring(L, -1)}));
pro.template Throw<nf7::Exception>(lua_tostring(L, -1));
break;
default:
assert(false);

View File

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

View File

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

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

169
common/mutex.hh Normal file
View File

@@ -0,0 +1,169 @@
#pragma once
#include <deque>
#include <functional>
#include <memory>
#include <utility>
#include "common/life.hh"
#include "common/future.hh"
namespace nf7 {
// nf7::Mutex is not thread-safe except Mutex::Lock's destructor.
class Mutex final {
public:
class Sync;
class Lock;
template <typename T> class Resource;
Mutex() noexcept : life_(*this) {
}
// It's guaranteed that the promise is finalized in a sub task or is done immediately.
nf7::Future<std::shared_ptr<Lock>> AcquireLock(
const std::shared_ptr<nf7::Context>& ctx, bool ex = false) noexcept {
if (auto ret = TryAcquireLock(ctx, ex)) {
return {ret};
} else {
if (ex || pends_.size() == 0 || pends_.back().ex) {
pends_.push_back({.pro = {ctx}, .ctx = ctx, .ex = ex});
}
return pends_.back().pro.future();
}
}
std::shared_ptr<Lock> TryAcquireLock(
const std::shared_ptr<nf7::Context>& ctx, bool ex = false) noexcept {
auto k = TryAcquireLock_(ctx, ex);
if (k) {
onLock();
}
return k;
}
const char* status() const noexcept {
return sync_.expired()? "free": ex_? "exlocked": "locked";
}
size_t pendings() const noexcept {
return pends_.size();
}
std::function<void()> onLock = [](){};
std::function<void()> onUnlock = [](){};
private:
nf7::Life<Mutex> life_;
bool ex_ = false;
std::weak_ptr<Sync> sync_;
struct Item final {
nf7::Future<std::shared_ptr<Lock>>::Promise pro;
std::shared_ptr<nf7::Context> ctx;
bool ex;
};
std::deque<Item> pends_;
std::shared_ptr<Lock> TryAcquireLock_(
const std::shared_ptr<nf7::Context>& ctx, bool ex) noexcept {
auto sync = sync_.lock();
if (sync) {
if (ex_ || ex) return nullptr;
} else {
sync = std::make_shared<Sync>(*this);
ex_ = ex;
sync_ = sync;
}
return std::make_shared<Mutex::Lock>(ctx, sync);
}
};
class Mutex::Sync {
public:
friend nf7::Mutex;
Sync() = delete;
Sync(nf7::Mutex& mtx) noexcept : mtx_(mtx.life_) {
}
Sync(const Sync&) = delete;
Sync(Sync&&) = delete;
Sync& operator=(const Sync&) = delete;
Sync& operator=(Sync&&) = delete;
~Sync() noexcept {
if (mtx_) {
auto& pends = mtx_->pends_;
if (pends.size() > 0) {
auto item = std::move(pends.front());
pends.pop_front();
mtx_->ex_ = false;
mtx_->sync_ = {};
auto k = mtx_->TryAcquireLock_(item.ctx, item.ex);
assert(k);
item.pro.Return(std::move(k));
} else {
mtx_->onUnlock();
}
}
}
private:
nf7::Life<nf7::Mutex>::Ref mtx_;
};
class Mutex::Lock {
public:
Lock(const std::shared_ptr<nf7::Context>& ctx,
const std::shared_ptr<Mutex::Sync>& sync) noexcept :
ctx_(ctx), sync_(sync) {
}
Lock(const Lock&) = default;
Lock(Lock&&) = default;
Lock& operator=(const Lock&) = default;
Lock& operator=(Lock&&) = default;
~Lock() noexcept {
// Ensure that the Sync's destructor is called on worker thread.
ctx_->env().ExecSub(
ctx_, [sync = std::move(sync_)]() mutable { sync = nullptr; });
}
private:
std::shared_ptr<nf7::Context> ctx_;
std::shared_ptr<Mutex::Sync> sync_;
};
template <typename T>
class Mutex::Resource {
public:
Resource() = delete;
Resource(const std::shared_ptr<Mutex::Lock>& k, T&& v) noexcept :
lock_(k), value_(std::move(v)) {
}
Resource(const std::shared_ptr<Mutex::Lock>& k, const T& v) noexcept :
Resource(k, T {v}) {
}
Resource(const Resource&) = default;
Resource(Resource&&) = default;
Resource& operator=(const Resource&) = default;
Resource& operator=(Resource&&) = default;
T& operator*() noexcept { return value_; }
const T& operator*() const noexcept { return value_; }
T* operator->() noexcept { return &value_; }
const T* operator->() const noexcept { return &value_; }
const std::shared_ptr<Mutex::Lock>& lock() const noexcept { return lock_; }
const T& value() const noexcept { return value_; }
private:
std::shared_ptr<Mutex::Lock> lock_;
T value_;
};
} // namespace nf7

View File

@@ -11,7 +11,7 @@
namespace nf7 {
class NativeFile final : public nf7::Context {
class NFile final {
public:
class Exception final : public nf7::Exception {
using nf7::Exception::Exception;
@@ -23,17 +23,16 @@ class NativeFile final : public nf7::Context {
};
using Flags = uint8_t;
NativeFile() = delete;
NativeFile(nf7::Env& env, nf7::File::Id id,
const std::filesystem::path& path, Flags flags) :
Context(env, id), path_(path), flags_(flags) {
NFile() = delete;
NFile(const std::filesystem::path& path, Flags flags) :
path_(path), flags_(flags) {
Init();
}
~NativeFile() noexcept;
NativeFile(const NativeFile&) = delete;
NativeFile(NativeFile&&) = delete;
NativeFile& operator=(const NativeFile&) = delete;
NativeFile& operator=(NativeFile&&) = delete;
~NFile() noexcept;
NFile(const NFile&) = delete;
NFile(NFile&&) = delete;
NFile& operator=(const NFile&) = delete;
NFile& operator=(NFile&&) = delete;
size_t Read(size_t offset, uint8_t* buf, size_t size);
size_t Write(size_t offset, const uint8_t* buf, size_t size);

View File

@@ -1,4 +1,4 @@
#include "common/native_file.hh"
#include "common/nfile.hh"
extern "C" {
#include <fcntl.h>
@@ -11,7 +11,7 @@ extern "C" {
namespace nf7 {
void NativeFile::Init() {
void NFile::Init() {
int flags = 0;
if ((flags_ & kRead) && (flags_ & kWrite)) {
flags |= O_RDWR | O_CREAT;
@@ -23,45 +23,45 @@ void NativeFile::Init() {
int fd = open(path_.string().c_str(), flags, 0600);
if (fd < 0) {
throw NativeFile::Exception {"open failure"};
throw NFile::Exception {"open failure"};
}
handle_ = static_cast<uint64_t>(fd);
}
NativeFile::~NativeFile() noexcept {
NFile::~NFile() noexcept {
const auto fd = static_cast<int>(handle_);
if (close(fd) == -1) {
// ;(
}
}
size_t NativeFile::Read(size_t offset, uint8_t* buf, size_t size) {
size_t NFile::Read(size_t offset, uint8_t* buf, size_t size) {
const auto fd = static_cast<int>(handle_);
const auto off = static_cast<off_t>(offset);
if (lseek(fd, off, SEEK_SET) == off-1) {
throw NativeFile::Exception {"lseek failure"};
throw NFile::Exception {"lseek failure"};
}
const auto ret = read(fd, buf, size);
if (ret == -1) {
throw NativeFile::Exception {"read failure"};
throw NFile::Exception {"read failure"};
}
return static_cast<size_t>(ret);
}
size_t NativeFile::Write(size_t offset, const uint8_t* buf, size_t size) {
size_t NFile::Write(size_t offset, const uint8_t* buf, size_t size) {
const auto fd = static_cast<int>(handle_);
const auto off = static_cast<off_t>(offset);
if (lseek(fd, off, SEEK_SET) == off-1) {
throw nf7::NativeFile::Exception {"lseek failure"};
throw nf7::NFile::Exception {"lseek failure"};
}
const auto ret = write(fd, buf, size);
if (ret == -1) {
throw nf7::NativeFile::Exception {"write failure"};
throw nf7::NFile::Exception {"write failure"};
}
return static_cast<size_t>(ret);
}
size_t NativeFile::Truncate(size_t size) {
size_t NFile::Truncate(size_t size) {
const auto fd = static_cast<int>(handle_);
if (ftruncate(fd, static_cast<off_t>(size)) == 0) {
throw nf7::NativeFile::Exception {"ftruncate failure"};
throw nf7::NFile::Exception {"ftruncate failure"};
}
return size;
}

59
common/nfile_watcher.hh Normal file
View File

@@ -0,0 +1,59 @@
#pragma once
#include <functional>
#include <filesystem>
#include <optional>
#include <vector>
#include "common/file_base.hh"
namespace nf7 {
class NFileWatcher final : public nf7::FileBase::Feature {
public:
NFileWatcher() = delete;
NFileWatcher(nf7::FileBase& f) noexcept : nf7::FileBase::Feature(f) {
}
NFileWatcher(const NFileWatcher&) = delete;
NFileWatcher(NFileWatcher&&) = delete;
NFileWatcher& operator=(const NFileWatcher&) = delete;
NFileWatcher& operator=(NFileWatcher&&) = delete;
void Watch(const std::filesystem::path& npath) noexcept {
npaths_.push_back(npath);
lastmod_ = std::nullopt;
}
void Clear() noexcept {
npaths_.clear();
lastmod_ = std::nullopt;
}
std::function<void()> onMod;
protected:
void Update() noexcept override {
auto latest = std::filesystem::file_time_type::duration::min();
for (const auto& npath : npaths_) {
try {
const auto lastmod = std::filesystem::last_write_time(npath).time_since_epoch();
latest = std::max(latest, lastmod);
} catch (std::filesystem::filesystem_error&) {
}
}
if (!lastmod_) {
lastmod_ = latest;
}
if (*lastmod_ < latest) {
onMod();
lastmod_ = latest;
}
}
private:
std::vector<std::filesystem::path> npaths_;
std::optional<std::filesystem::file_time_type::duration> lastmod_;
};
} // namespace nf7

View File

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

99
common/node.h Normal file
View File

@@ -0,0 +1,99 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
// DLL must have the following definition:
// `void nf7_init(nf7_init_t*) { }`
#ifdef __cplusplus
extern "C" {
#endif
typedef struct nf7_vtable_t nf7_vtable_t;
typedef struct nf7_init_t nf7_init_t;
typedef struct nf7_ctx_t nf7_ctx_t;
typedef struct nf7_node_t nf7_node_t;
typedef struct nf7_node_msg_t nf7_node_msg_t;
typedef struct nf7_value_t nf7_value_t;
typedef struct nf7_vtable_t {
// ---- entrypoint methods ----
struct {
void (*register_node)(nf7_init_t*, const nf7_node_t*);
} init;
// ---- context methods ----
struct {
// thread-safe
void (*exec_async)(nf7_ctx_t*, void*, void (*f)(nf7_ctx_t*, void*), uint64_t ms);
void (*exec_emit)(nf7_ctx_t*, const char* name, const nf7_value_t*, uint64_t ms);
} ctx;
// ---- value accessor/mutator ----
struct {
nf7_value_t* (*create) (const nf7_value_t*);
void (*destroy)(nf7_value_t*);
uint8_t (*get_type)(const nf7_value_t*);
# define NF7_PULSE UINT8_C(0)
# define NF7_BOOLEAN UINT8_C(1)
# define NF7_INTEGER UINT8_C(2)
# define NF7_SCALAR UINT8_C(3)
# define NF7_STRING UINT8_C(4)
# define NF7_VECTOR UINT8_C(5)
# define NF7_TUPLE UINT8_C(6)
# define NF7_UNKNOWN UINT8_MAX
// A result of value_get_type should be checked before calling the followings.
bool (*get_boolean)(const nf7_value_t*, bool*);
bool (*get_integer)(const nf7_value_t*, int64_t*);
bool (*get_scalar) (const nf7_value_t*, double*);
const char* (*get_string)(const nf7_value_t*, size_t*);
const uint8_t* (*get_vector)(const nf7_value_t*, size_t*);
const nf7_value_t* (*get_tuple) (const nf7_value_t*, const char*);
void (*set_pulse) (nf7_value_t*);
void (*set_boolean)(nf7_value_t*, bool);
void (*set_integer)(nf7_value_t*, int64_t);
void (*set_scalar) (nf7_value_t*, double);
char* (*set_string) (nf7_value_t*, size_t);
uint8_t* (*set_vector) (nf7_value_t*, size_t);
void (*set_tuple) (nf7_value_t*, const char**, nf7_value_t**);
} value;
} nf7_vtable_t;
typedef struct nf7_init_t {
const nf7_vtable_t* vtable;
} nf7_init_t;
typedef struct nf7_ctx_t {
nf7_value_t* value;
void* ptr;
} nf7_ctx_t;
typedef struct nf7_node_t {
const char* name;
const char* desc;
const char** inputs; // null terminated string array
const char** outputs; // null terminated string array
void* (*init)(); // returned pointer will be set to ctx.ptr
void (*deinit)(void*);
void (*handle)(const nf7_node_msg_t*);
} nf7_node_t;
typedef struct nf7_node_msg_t {
const char* name;
nf7_value_t* value;
nf7_ctx_t* ctx;
} nf7_node_msg_t;
#ifdef __cplusplus
}
#endif

View File

@@ -25,10 +25,38 @@ class Node : public File::Interface {
kNone = 0,
kCustomNode = 1 << 0,
kMenu = 1 << 1,
kMenu_DirItem = 1 << 2, // use DirItem::UpdateMenu() method instead of Node's
};
using Flags = uint8_t;
struct Meta final {
public:
Meta() = default;
Meta(std::vector<std::string>&& i, std::vector<std::string>&& o) noexcept :
inputs(std::move(i)), outputs(std::move(o)) {
}
Meta(const std::vector<std::string>& i, const std::vector<std::string>& o) noexcept :
inputs(i), outputs(o) {
}
Meta(const Meta&) = default;
Meta(Meta&&) = default;
Meta& operator=(const Meta&) = default;
Meta& operator=(Meta&&) = default;
std::vector<std::string> inputs, outputs;
};
static void ValidateSockets(std::span<const std::string> v) {
for (auto itr = v.begin(); itr < v.end(); ++itr) {
if (v.end() != std::find(itr+1, v.end(), *itr)) {
throw nf7::Exception {"name duplication: "+*itr};
}
}
for (auto& s : v) {
nf7::File::Path::ValidateTerm(s);
}
}
Node(Flags f) noexcept : flags_(f) { }
Node(const Node&) = default;
Node(Node&&) = default;
@@ -40,13 +68,12 @@ class Node : public File::Interface {
virtual void UpdateNode(Editor&) noexcept { }
virtual void UpdateMenu(Editor&) noexcept { }
// The returned span is alive until next operation to the file.
virtual std::span<const std::string> GetInputs() const noexcept = 0;
virtual std::span<const std::string> GetOutputs() const noexcept = 0;
// don't call too often because causes heap allocation
virtual Meta GetMeta() const noexcept = 0;
Flags flags() const noexcept { return flags_; }
protected:
private:
Flags flags_;
};
@@ -73,6 +100,26 @@ class Node::Editor {
class Node::Lambda : public nf7::Context {
public:
struct Msg final {
public:
Msg() = delete;
Msg(std::string&& n, nf7::Value&& v, std::shared_ptr<Lambda>&& s) noexcept :
name(std::move(n)), value(std::move(v)), sender(std::move(s)) {
}
Msg(std::string_view n, const nf7::Value& v, const std::shared_ptr<Lambda>& s) noexcept :
name(n), value(v), sender(s) {
}
Msg(const Msg&) = default;
Msg(Msg&&) = default;
Msg& operator=(const Msg&) = default;
Msg& operator=(Msg&&) = default;
std::string name;
nf7::Value value;
std::shared_ptr<Lambda> sender;
};
Lambda(nf7::File& f, const std::shared_ptr<nf7::Context>& parent = nullptr) noexcept :
Lambda(f.env(), f.id(), parent) {
}
@@ -81,8 +128,11 @@ class Node::Lambda : public nf7::Context {
parent_(std::dynamic_pointer_cast<Node::Lambda>(parent)) {
}
virtual void Handle(
std::string_view, const nf7::Value&, const std::shared_ptr<Lambda>&) noexcept {
virtual void Handle(const Msg&) noexcept {
}
void Handle(std::string_view k, const nf7::Value& v,
const std::shared_ptr<nf7::Node::Lambda>& sender) noexcept {
return Handle({k, v, sender});
}
std::shared_ptr<Node::Lambda> parent() const noexcept { return parent_.lock(); }

View File

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

View File

@@ -1,116 +1,94 @@
#pragma once
#include <algorithm>
#include <cassert>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <string_view>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
#include "nf7.hh"
#include "common/future.hh"
#include "common/node.hh"
#include "common/value.hh"
namespace nf7 {
class NodeRootLambda final : public nf7::Node::Lambda,
class NodeRootLambda : public nf7::Node::Lambda,
public std::enable_shared_from_this<NodeRootLambda> {
public:
struct Builder;
using Pair = std::pair<std::string, nf7::Value>;
NodeRootLambda(const NodeRootLambda&) = delete;
NodeRootLambda(NodeRootLambda&&) = delete;
NodeRootLambda& operator=(const NodeRootLambda&) = delete;
NodeRootLambda& operator=(NodeRootLambda&&) = delete;
~NodeRootLambda() noexcept {
target_ = nullptr;
for (auto& pro : pro_) {
pro.second.Throw(std::make_exception_ptr(
nf7::Exception {"output was never satisified"}));
static std::shared_ptr<NodeRootLambda> Create(
const std::shared_ptr<nf7::Context>& ctx, nf7::Node& n) noexcept {
auto ret = std::make_shared<NodeRootLambda>(ctx->env(), ctx->initiator(), ctx);
ret->target_ = n.CreateLambda(ret);
return ret;
}
}
private:
std::shared_ptr<nf7::Node::Lambda> target_;
std::unordered_map<std::string, nf7::Future<nf7::Value>::Promise> pro_;
std::unordered_map<std::string, std::function<void(const nf7::Value&)>> handler_;
using nf7::Node::Lambda::Lambda;
void Handle(std::string_view name, const nf7::Value& v,
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override {
const auto sname = std::string {name};
auto pitr = pro_.find(sname);
if (pitr != pro_.end()) {
pitr->second.Return(nf7::Value {v});
pro_.erase(pitr);
}
auto hitr = handler_.find(sname);
if (hitr != handler_.end()) {
hitr->second(v);
}
}
};
struct NodeRootLambda::Builder final {
public:
Builder() = delete;
Builder(nf7::File& f, nf7::Node& n,
const std::shared_ptr<nf7::Context>& ctx = nullptr) noexcept :
prod_(new NodeRootLambda {f, ctx}), target_(n.CreateLambda(prod_)), node_(&n) {
prod_->target_ = target_;
~NodeRootLambda() noexcept {
Abort();
}
void CheckOutput(std::string_view name) const {
auto out = node_->GetOutputs();
if (out.end() == std::find(out.begin(), out.end(), name)) {
throw nf7::Exception {"required output is missing: "+std::string {name}};
void Handle(const nf7::Node::Lambda::Msg& in) noexcept override {
std::unique_lock<std::mutex> lk {mtx_};
if (names_.contains(in.name)) {
names_.clear();
if (auto pro = std::exchange(pro_, std::nullopt)) {
lk.unlock();
pro->Return({in.name, in.value});
}
}
void CheckInput(std::string_view name) const {
auto in = node_->GetInputs();
if (in.end() == std::find(in.begin(), in.end(), name)) {
throw nf7::Exception {"required input is missing: "+std::string {name}};
} else {
q_.push_back({in.name, in.value});
}
}
nf7::Future<nf7::Value> Receive(const std::string& name) {
assert(!built_);
CheckOutput(name);
auto [itr, added] =
prod_->pro_.try_emplace(name, nf7::Future<nf7::Value>::Promise {});
assert(added);
return itr->second.future();
}
void Listen(const std::string& name, std::function<void(const nf7::Value&)>&& f) {
assert(!built_);
CheckOutput(name);
prod_->handler_[name] = std::move(f);
// thread-safe
void ExecSend(std::string_view k, const nf7::Value& v) noexcept {
env().ExecSub(shared_from_this(), [this, k = std::string {k}, v = v]() {
target_->Handle(k, v, shared_from_this());
});
}
std::shared_ptr<NodeRootLambda> Build() noexcept {
assert(!built_);
built_ = true;
return prod_;
// thread-safe
nf7::Future<Pair> Select(std::unordered_set<std::string>&& names) noexcept {
std::unique_lock<std::mutex> k(mtx_);
assert(!pro_);
names_.clear();
for (auto itr = q_.begin(); itr < q_.end(); ++itr) {
if (names.contains(itr->first)) {
auto p = std::move(*itr);
q_.erase(itr);
k.unlock();
return {std::move(p)};
}
}
pro_.emplace();
names_ = std::move(names);
return pro_->future();
}
void Send(std::string_view name, const nf7::Value& v) {
assert(built_);
CheckInput(name);
target_->Handle(name, v, prod_);
void Abort() noexcept override {
target_->Abort();
pro_ = std::nullopt;
}
private:
bool built_ = false;
std::shared_ptr<NodeRootLambda> prod_;
std::mutex mtx_;
std::shared_ptr<nf7::Node::Lambda> target_;
nf7::Node* const node_;
std::vector<Pair> q_;
std::unordered_set<std::string> names_;
std::optional<nf7::Future<Pair>::Promise> pro_;
};
} // namespace nf7

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

102
common/ring_buffer.hh Normal file
View File

@@ -0,0 +1,102 @@
#pragma once
#include <cstdint>
#include <cstring>
#include <tuple>
#include <vector>
namespace nf7 {
class RingBuffer final {
public:
RingBuffer() = delete;
RingBuffer(uint64_t unit, uint64_t bufn) noexcept :
buf_(unit*bufn), unit_(unit), bufn_(bufn) {
}
RingBuffer(const RingBuffer&) = delete;
RingBuffer(RingBuffer&&) = default;
RingBuffer& operator=(const RingBuffer&) = delete;
RingBuffer& operator=(RingBuffer&&) = default;
template <typename T>
uint64_t Mix(uint64_t begin, const T* ptr, uint64_t n) noexcept {
assert(unit_ == sizeof(T));
if (begin < cur_) {
const auto drop = cur_ - begin;
if (drop >= n) {
return cur_;
}
ptr += drop;
n -= drop;
begin = cur_;
}
if (begin > cur_) {
const auto skip = begin - cur_;
n = std::min(bufn_ - skip, n);
}
auto buf = reinterpret_cast<T*>(buf_.data());
const auto [c, r, l] = CalcCursor(begin, n);
for (uint64_t i = 0; i < r; ++i) {
buf[c+i] += ptr[i];
}
for (uint64_t i = 0; i < l; ++i) {
buf[i] += ptr[r+i];
}
return begin + n;
}
void Take(uint8_t* ptr, uint64_t n) noexcept {
const auto [c, r, l] = CalcCursor(cur_, n);
std::memcpy(&ptr[0*unit_], &buf_[c*unit_], r*unit_);
std::memcpy(&ptr[r*unit_], &buf_[0*unit_], l*unit_);
std::memset(&buf_[c*unit_], 0, r*unit_);
std::memset(&buf_[0*unit_], 0, l*unit_);
cur_ += n;
}
uint64_t Peek(uint64_t begin, uint8_t* ptr, uint64_t n) noexcept {
if (cur_ > bufn_) {
const auto actual_begin = std::max(begin, cur_-bufn_);
const auto pad = std::min(n, actual_begin - begin);
std::memset(ptr, 0, pad*unit_);
begin = actual_begin;
ptr += pad*unit_;
n -= pad;
}
n = std::min(n, bufn_);
const auto [c, r, l] = CalcCursor(begin, n);
std::memcpy(&ptr[0*unit_], &buf_[c*unit_], r*unit_);
std::memcpy(&ptr[r*unit_], &buf_[0*unit_], l*unit_);
return begin + n;
}
void Write(const uint8_t* ptr, uint64_t n) noexcept {
const auto [c, r, l] = CalcCursor(cur_, n);
std::memcpy(&buf_[c*unit_], &ptr[0*unit_], r*unit_);
std::memcpy(&buf_[0*unit_], &ptr[r*unit_], l*unit_);
cur_ += n;
}
uint64_t unit() const noexcept { return unit_; }
uint64_t bufn() const noexcept { return bufn_; }
uint64_t cur() const noexcept { return cur_; }
private:
std::vector<uint8_t> buf_;
uint64_t unit_;
uint64_t bufn_;
uint64_t cur_ = 0;
std::tuple<uint64_t, uint64_t, uint64_t> CalcCursor(
uint64_t t, uint64_t n) noexcept {
assert(n <= bufn_);
const auto c = t % bufn_;
const auto r = std::min(bufn_ - c, n);
const auto l = n > r? n - r: 0;
return {c, r, l};
}
};
} // namespace nf7

View File

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

95
common/task.hh Normal file
View File

@@ -0,0 +1,95 @@
#pragma once
#include <memory>
#include <optional>
#include <utility>
#include "nf7.hh"
#include "common/future.hh"
namespace nf7 {
template <typename T>
class Task : public nf7::Context,
public std::enable_shared_from_this<Task<T>> {
public:
class Holder;
using Future = nf7::Future<T>;
using Coro = typename Future::Coro;
using nf7::Context::Context;
Task(const Task&) = delete;
Task(Task&&) = delete;
Task& operator=(const Task&) = delete;
Task& operator=(Task&&) = delete;
void Start() noexcept {
coro_ = Proc();
fu_ = coro_->Start(self());
}
void Abort() noexcept {
coro_->Abort();
}
auto self() noexcept {
return std::enable_shared_from_this<Task<T>>::shared_from_this();
}
std::optional<Future>& fu() noexcept { return *fu_; }
protected:
virtual Coro Proc() noexcept = 0;
private:
std::optional<Coro> coro_;
std::optional<Future> fu_;
};
// all operations are not thread-safe
template <typename T>
class Task<T>::Holder final {
public:
Holder() = default;
~Holder() noexcept {
Abort();
}
Holder(const Holder&) = delete;
Holder(Holder&&) = delete;
Holder& operator=(const Holder&) = delete;
Holder& operator=(Holder&&) = delete;
bool CleanUp() noexcept {
return !!std::exchange(fu_, std::nullopt);
}
void Abort() noexcept {
if (auto task = task_.lock()) {
task->Abort();
}
}
template <typename U, typename... Args>
nf7::Future<T> StartIf(Args&&... args) noexcept {
if (fu_) return *fu_;
auto task = std::make_shared<U>(std::forward<Args>(args)...);
task->Start();
task_ = task;
fu_ = task->fu();
return *fu_;
}
std::optional<nf7::Future<T>>& fu() noexcept { return fu_; }
const std::optional<nf7::Future<T>>& fu() const noexcept { return fu_; }
private:
std::weak_ptr<Task<T>> task_;
std::optional<nf7::Future<T>> fu_;
};
} // namespace nf7

View File

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

View File

@@ -39,6 +39,11 @@ class TimedQueue {
return ret.task;
}
bool idle(nf7::Env::Time now = nf7::Env::Clock::now()) const noexcept {
const auto t = next();
return !t || *t > now;
}
std::optional<nf7::Env::Time> next() const noexcept {
std::unique_lock<std::mutex> k(mtx_);
return next_();
@@ -71,41 +76,4 @@ class TimedQueue {
std::priority_queue<Item, std::vector<Item>, Comp> q_;
};
template <typename T>
class TimedWaitQueue final : private TimedQueue<T> {
public:
TimedWaitQueue() = default;
TimedWaitQueue(const TimedWaitQueue&) = delete;
TimedWaitQueue(TimedWaitQueue&&) = delete;
TimedWaitQueue& operator=(const TimedWaitQueue&) = delete;
TimedWaitQueue& operator=(TimedWaitQueue&&) = delete;
void Push(nf7::Env::Time time, T&& task) noexcept {
TimedQueue<T>::Push(time, std::move(task));
cv_.notify_all();
}
using TimedQueue<T>::Pop;
void Notify() noexcept {
cv_.notify_all();
}
void Wait() noexcept {
std::unique_lock<std::mutex> k(mtx_);
if (auto t = next_()) {
cv_.wait_until(k, *t);
} else {
cv_.wait(k);
}
}
using TimedQueue<T>::next;
using TimedQueue<T>::size;
private:
using TimedQueue<T>::mtx_;
using TimedQueue<T>::next_;
std::condition_variable cv_;
};
} // namespace nf7

23
common/util_algorithm.hh Normal file
View File

@@ -0,0 +1,23 @@
#pragma once
#include <algorithm>
#include <vector>
namespace nf7::util {
template <typename T>
inline size_t Uniq(std::vector<T>& v) noexcept {
size_t n = 0;
for (auto itr = v.begin(); itr < v.end();) {
if (v.end() != std::find(itr+1, v.end(), *itr)) {
itr = v.erase(itr);
++n;
} else {
++itr;
}
}
return n;
}
} // namespace nf7::util

View File

@@ -1,73 +0,0 @@
#pragma once
#include <algorithm>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <vector>
namespace nf7::util {
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 void Uniq(std::vector<std::string>& v) noexcept {
for (auto itr = v.begin(); itr < v.end();) {
if (v.end() != std::find(itr+1, v.end(), *itr)) {
itr = v.erase(itr);
} else {
++itr;
}
}
}
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

@@ -62,31 +62,26 @@ class Value {
Value& operator=(Scalar v) noexcept { value_ = v; return *this; }
Value(Boolean v) noexcept : value_(v) { }
Value& operator=(Boolean v) noexcept { value_ = v; return *this; }
Value(std::string_view v) noexcept : value_(std::string {v}) { }
Value& operator=(std::string_view v) noexcept { value_ = std::string(v); return *this; }
Value(String&& v) noexcept : value_(std::move(v)) { }
Value& operator=(String&& v) noexcept { value_ = std::move(v); return *this; }
Value(const Vector& v) noexcept { value_ = v; }
Value& operator=(const Vector& v) noexcept { value_ = v; return *this; }
Value(Vector&& v) noexcept { value_ = std::move(v); }
Value& operator=(Vector&& v) noexcept { value_ = std::move(v); return *this; }
Value(const ConstVector& v) noexcept { value_ = std::const_pointer_cast<std::vector<uint8_t>>(v); }
Value& operator=(const ConstVector& v) noexcept { value_ = std::const_pointer_cast<std::vector<uint8_t>>(v); return *this; }
Value(std::vector<uint8_t>&& v) noexcept { value_ = std::make_shared<std::vector<uint8_t>>(std::move(v)); }
Value(const Vector& v) noexcept : value_(v? v: std::make_shared<std::vector<uint8_t>>()) { }
Value& operator=(const Vector& v) noexcept { value_ = v? v: std::make_shared<std::vector<uint8_t>>(); return *this; }
Value(const ConstVector& v) noexcept : value_(v? std::const_pointer_cast<std::vector<uint8_t>>(v): std::make_shared<std::vector<uint8_t>>()) { }
Value& operator=(const ConstVector& v) noexcept { value_ = v? std::const_pointer_cast<std::vector<uint8_t>>(v): std::make_shared<std::vector<uint8_t>>(); return *this; }
Value(std::vector<uint8_t>&& v) noexcept : value_(std::make_shared<std::vector<uint8_t>>(std::move(v))) { }
Value& operator=(std::vector<uint8_t>&& v) noexcept { value_ = std::make_shared<std::vector<uint8_t>>(std::move(v)); return *this; }
Value(const Tuple& v) noexcept : value_(v) { }
Value& operator=(const Tuple& v) noexcept { value_ = v; return *this; }
Value(Tuple&& v) noexcept : value_(std::move(v)) { }
Value& operator=(Tuple&& v) noexcept { value_ = std::move(v); return *this; }
Value(std::vector<TuplePair>&& p) noexcept { value_ = std::make_shared<std::vector<TuplePair>>(std::move(p)); }
Value(const Tuple& v) noexcept : value_(v? v: std::make_shared<std::vector<TuplePair>>()) { }
Value& operator=(const Tuple& v) noexcept { value_ = v? v: std::make_shared<std::vector<TuplePair>>(); return *this; }
Value(const ConstTuple& v) noexcept : value_(v? v: std::make_shared<std::vector<TuplePair>>()) { }
Value& operator=(const ConstTuple& v) noexcept { value_ = v? v: std::make_shared<std::vector<TuplePair>>(); return *this; }
Value(std::vector<TuplePair>&& p) noexcept : value_(std::make_shared<std::vector<TuplePair>>(std::move(p))) { }
Value& operator=(std::vector<TuplePair>&& p) noexcept { value_ = std::make_shared<std::vector<TuplePair>>(std::move(p)); return *this; }
Value(std::vector<nf7::Value>&& v) noexcept {
std::vector<TuplePair> pairs;
pairs.reserve(v.size());
std::transform(v.begin(), v.end(), std::back_inserter(pairs),
[](auto& x) { return TuplePair {"", std::move(x)}; });
value_ = std::make_shared<std::vector<TuplePair>>(std::move(pairs));
}
Value(std::vector<nf7::Value>&& v) noexcept { *this = std::move(v); }
Value& operator=(std::vector<nf7::Value>&& v) noexcept {
std::vector<TuplePair> pairs;
pairs.reserve(v.size());
@@ -95,46 +90,69 @@ class Value {
value_ = std::make_shared<std::vector<TuplePair>>(std::move(pairs));
return *this;
}
Value(const DataPtr& v) noexcept : value_(v) { }
Value& operator=(const DataPtr& v) noexcept { value_ = v; return *this; }
Value(DataPtr&& v) noexcept : value_(std::move(v)) { }
Value& operator=(DataPtr&& v) noexcept { value_ = std::move(v); return *this; }
auto Visit(auto visitor) const noexcept {
return std::visit(visitor, value_);
}
bool isPulse() const noexcept { return std::holds_alternative<Pulse>(value_); }
bool isBoolean() const noexcept { return std::holds_alternative<Boolean>(value_); }
bool isInteger() const noexcept { return std::holds_alternative<Integer>(value_); }
bool isScalar() const noexcept { return std::holds_alternative<Scalar>(value_); }
bool isString() const noexcept { return std::holds_alternative<String>(value_); }
bool isVector() const noexcept { return std::holds_alternative<Vector>(value_); }
bool isTuple() const noexcept { return std::holds_alternative<Tuple>(value_); }
bool isVector() const noexcept { return std::holds_alternative<ConstVector>(value_); }
bool isTuple() const noexcept { return std::holds_alternative<ConstTuple>(value_); }
bool isData() const noexcept { return std::holds_alternative<DataPtr>(value_); }
// direct accessors
Integer integer() const { return get<Integer>(); }
Boolean boolean() const { return get<Boolean>(); }
Scalar scalar() const { return get<Scalar>(); }
const String& string() const { return get<String>(); }
const ConstVector vector() const { return get<Vector>(); }
const ConstTuple tuple() const { return get<Tuple>(); }
const ConstVector& vector() const { return get<ConstVector>(); }
const ConstTuple& tuple() const { return get<ConstTuple>(); }
const DataPtr& data() const { return get<DataPtr>(); }
const auto& value() const noexcept { return value_; }
template <typename I>
I integer() const {
const auto ret = integer();
if constexpr (std::is_unsigned<I>::value) {
if (ret < 0) {
throw IncompatibleException("integer underflow");
// direct reference accessor
Integer& integer() { return get<Integer>(); }
Boolean& boolean() { return get<Boolean>(); }
Scalar& scalar() { return get<Scalar>(); }
String& string() { return get<String>(); }
// conversion accessor
template <typename N>
N integer() const {
return SafeCast<N>(integer());
}
} else {
if (ret != static_cast<Integer>(static_cast<I>(ret))) {
throw IncompatibleException("integer out of range");
template <typename N>
N scalar() const {
return SafeCast<N>(scalar());
}
template <typename N>
N integerOrScalar() const {
try {
return SafeCast<N>(integer());
} catch (nf7::Exception&) {
return SafeCast<N>(scalar());
}
}
return static_cast<I>(ret);
template <typename N>
N scalarOrInteger() const {
try {
return SafeCast<N>(scalar());
} catch (nf7::Exception&) {
return SafeCast<N>(integer());
}
}
template <typename T>
std::shared_ptr<T> data() const {
if (auto ptr = std::dynamic_pointer_cast<T>(data())) return ptr;
throw IncompatibleException("data pointer downcast failure");
}
// tuple element accessor
const Value& tuple(size_t idx) const {
auto& tup = *tuple();
return idx < tup.size()? tup[idx].second:
@@ -147,19 +165,24 @@ class Value {
return itr < tup.end()? itr->second:
throw IncompatibleException("unknown tuple field: "+std::string {name});
}
template <typename T>
std::shared_ptr<T> data() const {
if (auto ptr = std::dynamic_pointer_cast<T>(data())) return ptr;
throw IncompatibleException("data pointer downcast failure");
Value tupleOr(auto idx, const Value& v) const noexcept {
try {
return tuple(idx);
} catch (nf7::Exception&) {
return v;
}
}
Integer& integer() { return get<Integer>(); }
Boolean& boolean() { return get<Boolean>(); }
Scalar& scalar() { return get<Scalar>(); }
String& string() { return get<String>(); }
Vector vectorUniq() { return getUniq<Vector>(); }
Tuple tupleUniq() { return getUniq<Tuple>(); }
// extended accessor
nf7::File& file(const nf7::File& base) const {
if (isInteger()) {
return base.env().GetFileOrThrow(integerOrScalar<nf7::File::Id>());
} else if (isString()) {
return base.ResolveOrThrow(string());
} else {
throw IncompatibleException {"expected file id or file path"};
}
}
const char* typeName() const noexcept {
struct Visitor final {
@@ -169,11 +192,11 @@ class Value {
auto operator()(Integer) noexcept { return "integer"; }
auto operator()(Scalar) noexcept { return "scalar"; }
auto operator()(String) noexcept { return "string"; }
auto operator()(Vector) noexcept { return "vector"; }
auto operator()(Tuple) noexcept { return "tuple"; }
auto operator()(ConstVector) noexcept { return "vector"; }
auto operator()(ConstTuple) noexcept { return "tuple"; }
auto operator()(DataPtr) noexcept { return "data"; }
};
return Visit(Visitor{});
return std::visit(Visitor {}, value_);
}
template <typename Ar>
@@ -183,34 +206,44 @@ class Value {
}
private:
std::variant<Pulse, Boolean, Integer, Scalar, String, Vector, Tuple, DataPtr> value_;
std::variant<Pulse, Boolean, Integer, Scalar, String, ConstVector, ConstTuple, DataPtr> value_;
template <typename T>
const T& get() const
try {
return std::get<T>(value_);
} catch (std::bad_variant_access&) {
throw IncompatibleException(
std::string{"expected "}+typeid(T).name()+" but it's "+typeName());
const T& get() const {
return const_cast<Value&>(*this).get<T>();
}
template <typename T>
T& get()
try {
return std::get<T>(value_);
} catch (std::bad_variant_access&) {
throw IncompatibleException(
std::string{"expected "}+typeid(T).name()+" but it's "+typeName());
std::stringstream st;
st << "expected " << typeid(T).name() << " but it's " << typeName();
throw IncompatibleException(st.str());
}
template <typename T>
T getUniq() {
auto v = std::move(get<T>());
if (v.use_count() == 1) {
return v;
} else {
return std::make_shared<typename T::element_type>(*v);
template <typename R, typename N>
static R SafeCast(N in) {
const auto ret = static_cast<R>(in);
const auto retn = static_cast<N>(ret);
if constexpr (std::is_unsigned<R>::value) {
if (in < 0) {
throw IncompatibleException("integer underflow");
}
}
if constexpr (std::is_integral<R>::value && std::is_integral<N>::value) {
if (in != retn) {
throw IncompatibleException("integer out of range");
}
}
if constexpr (std::is_integral<R>::value && std::is_floating_point<N>::value) {
if (std::max(retn, in) - std::min(retn, in) > 1) {
throw IncompatibleException("bad precision while conversion of floating point");
}
}
return ret;
}
};
class Value::Data {
@@ -261,6 +294,22 @@ struct serializer<
throw nf7::DeserializeException("cannot deserialize Value::Vector");
}
};
template <size_t F>
struct serializer<
type_prop::not_a_fundamental,
ser_case::use_internal_serializer,
F,
nf7::Value::ConstVector> {
public:
template <typename Archive>
static Archive& save(Archive&, const nf7::Value::ConstVector&) {
throw nf7::Exception("cannot serialize Value::Vector");
}
template <typename Archive>
static Archive& load(Archive&, nf7::Value::ConstVector&) {
throw nf7::DeserializeException("cannot deserialize Value::Vector");
}
};
template <size_t F>
struct serializer<
@@ -280,6 +329,26 @@ struct serializer<
return ar;
}
};
template <size_t F>
struct serializer<
type_prop::not_a_fundamental,
ser_case::use_internal_serializer,
F,
nf7::Value::ConstTuple> {
public:
template <typename Archive>
static Archive& save(Archive& ar, const nf7::Value::ConstTuple& tup) {
ar(*tup);
return ar;
}
template <typename Archive>
static Archive& load(Archive& ar, nf7::Value::ConstTuple& tup) {
auto ptr = std::make_shared<std::vector<nf7::Value::TuplePair>>();
ar(*ptr);
tup = std::move(ptr);
return ar;
}
};
template <size_t F>
struct serializer<

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

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

42
common/yas_enum.hh Normal file
View File

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

View File

@@ -30,11 +30,10 @@ struct serializer<
static Archive& load(Archive& ar, std::unique_ptr<nf7::File>& f) {
std::string name;
ar(name);
auto& type = nf7::File::registry(name);
try {
typename Archive::ChunkGuard guard {ar};
f = type.Deserialize(ar);
f = nf7::File::registry(name).Deserialize(ar);
guard.ValidateEnd();
} catch (...) {
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>
struct serializer<
type_prop::not_a_fundamental,

View File

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

File diff suppressed because it is too large Load Diff

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

1452
file/gl_obj.cc Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,106 +1,196 @@
#include <atomic>
#include <chrono>
#include <memory>
#include <optional>
#include <utility>
#include <imgui.h>
#include <lua.hpp>
#include <tracy/Tracy.hpp>
#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/logger_ref.hh"
#include "common/luajit.hh"
#include "common/luajit_queue.hh"
#include "common/ptr_selector.hh"
#include "common/queue.hh"
#include "common/thread.hh"
using namespace std::literals;
namespace nf7 {
namespace {
class LuaContext final : public nf7::File, public nf7::DirItem {
class LuaContext final : public nf7::FileBase, public nf7::DirItem {
public:
static inline const GenericTypeInfo<LuaContext> kType = {
"LuaJIT/Context", {"nf7::DirItem",}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("Drives LuaJIT thread and task queue.");
ImGui::Bullet(); ImGui::TextUnformatted(
"implements nf7::luajit::Queue");
ImGui::Bullet(); ImGui::TextUnformatted(
"create multiple contexts to execute LuaJIT paralelly");
ImGui::Bullet(); ImGui::TextUnformatted(
"the thread remains alive after file deletion until unused");
}
static inline const nf7::GenericTypeInfo<nf7::LuaContext> kType = {
"LuaJIT/Context", {"nf7::DirItem",},
"drives LuaJIT thread and task queue"};
class Queue;
LuaContext(nf7::Env& env) :
File(kType, env), DirItem(DirItem::kTooltip) {
q_ = std::make_shared<Queue>(*this);
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()) {
ar(async_);
}
void Serialize(Serializer&) const noexcept override {
void Serialize(nf7::Serializer& ar) const noexcept override {
ar(async_);
}
std::unique_ptr<File> Clone(Env& env) const noexcept override {
return std::make_unique<LuaContext>(env);
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<LuaContext>(env, async_);
}
void PostHandle(const nf7::File::Event&) noexcept override;
void PostUpdate() noexcept override;
void UpdateMenu() noexcept override;
void UpdateTooltip() noexcept override;
File::Interface* interface(const std::type_info& t) noexcept override {
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector<
nf7::DirItem, nf7::luajit::Queue>(t).Select(this, q_.get());
}
private:
nf7::LoggerRef log_;
std::shared_ptr<Queue> q_;
bool async_;
};
class LuaContext::Queue final : public nf7::luajit::Queue,
public std::enable_shared_from_this<LuaContext::Queue> {
public:
struct SharedData final {
lua_State* L;
std::atomic_flag lock;
std::optional<nf7::Env::Time> begin;
};
struct Runner final {
Runner(Queue& owner) noexcept : owner_(&owner) {
Runner(const std::shared_ptr<SharedData>& data) noexcept : data_(data) {
}
void operator()(Task&& t) {
t(owner_->L);
auto& k = data_->lock;
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:
Queue* const owner_;
std::shared_ptr<SharedData> data_;
bool require_gc_ = false;
};
using Thread = nf7::Thread<Runner, Task>;
Queue() = delete;
Queue(LuaContext& f) :
L(luaL_newstate()), env_(&f.env()),
th_(std::make_shared<Thread>(f, Runner {*this})) {
if (!L) throw nf7::Exception("failed to create new Lua state");
Queue(LuaContext& f, bool async) {
auto L = luaL_newstate();
if (!L) {
throw nf7::Exception("failed to create new Lua state");
}
lua_pushthread(L);
nf7::luajit::PushImmEnv(L);
lua_setfenv(L, -2);
lua_pop(L, 1);
data_ = std::make_shared<SharedData>();
data_->L = L;
th_ = std::make_shared<Thread>(f, Runner {data_});
SetAsync(async);
}
~Queue() noexcept {
th_->Push(
std::make_shared<nf7::GenericContext>(*env_, 0, "deleting lua_State"),
[L = L](auto) { lua_close(L); }
);
std::make_shared<nf7::GenericContext>(th_->env(), 0, "deleting lua_State"),
[data = data_](auto) {
lua_close(data->L);
data->L = nullptr;
});
}
Queue(const Queue&) = delete;
Queue(Queue&&) = delete;
Queue& operator=(const 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 {
th_->Push(ctx, std::move(task), t);
}
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:
lua_State* L;
Env* const env_;
std::shared_ptr<Thread> th_;
std::shared_ptr<SharedData> data_;
};
void LuaContext::PostHandle(const nf7::File::Event& e) noexcept {
switch (e.type) {
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 {
if (ImGui::MenuItem("async", nullptr, &async_)) {
q_->SetAsync(async_);
}
}
void LuaContext::UpdateTooltip() noexcept {
ImGui::Text("tasks done: %zu", static_cast<size_t>(q_->tasksDone()));
if (q_) {

View File

@@ -1,262 +0,0 @@
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include <imgui.h>
#include <imgui_stdlib.h>
#include <ImNodes.h>
#include <yas/serialize.hpp>
#include <yas/types/std/string.hpp>
#include "nf7.hh"
#include "common/dir_item.hh"
#include "common/file_base.hh"
#include "common/generic_type_info.hh"
#include "common/generic_memento.hh"
#include "common/gui_file.hh"
#include "common/gui_node.hh"
#include "common/gui_popup.hh"
#include "common/life.hh"
#include "common/logger_ref.hh"
#include "common/luajit_queue.hh"
#include "common/luajit_ref.hh"
#include "common/luajit_thread.hh"
#include "common/memento.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
using namespace std::literals;
namespace nf7 {
namespace {
class InlineNode final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
public:
static inline const nf7::GenericTypeInfo<InlineNode> kType =
{"LuaJIT/InlineNode", {"nf7::DirItem", "nf7::Node"}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("Defines new Node using Lua object factory.");
ImGui::Bullet();
ImGui::TextUnformatted("refers nf7::luajit::Queue through linked LuaJIT/Obj");
}
class Lambda;
struct Data {
Data() noexcept { }
std::string script;
std::vector<std::string> inputs = {"in"};
std::vector<std::string> outputs = {"out"};
};
InlineNode(nf7::Env& env, Data&& data = {}) noexcept :
nf7::FileBase(kType, env, {&socket_popup_}),
nf7::DirItem(nf7::DirItem::kWidget),
nf7::Node(nf7::Node::kCustomNode),
life_(*this),
log_(std::make_shared<nf7::LoggerRef>(*this)),
mem_(std::move(data), *this) {
nf7::FileBase::Install(*log_);
socket_popup_.onSubmit = [this](auto&& i, auto&& o) {
this->data().inputs = std::move(i);
this->data().outputs = std::move(o);
mem_.Commit();
};
}
InlineNode(nf7::Deserializer& ar) : InlineNode(ar.env()) {
ar(data().script, data().inputs, data().outputs);
}
void Serialize(nf7::Serializer& ar) const noexcept override {
ar(data().script, data().inputs, data().outputs);
}
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<InlineNode>(env, Data {data()});
}
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
std::span<const std::string> GetInputs() const noexcept override {
return data().inputs;
}
std::span<const std::string> GetOutputs() const noexcept override {
return data().outputs;
}
void UpdateMenu() noexcept override;
void UpdateNode(nf7::Node::Editor&) noexcept override;
void UpdateWidget() noexcept override;
File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector<
nf7::DirItem, nf7::Memento, nf7::Node>(t).Select(this, &mem_);
}
private:
nf7::Life<InlineNode> life_;
std::shared_ptr<nf7::LoggerRef> log_;
nf7::GenericMemento<Data> mem_;
const Data& data() const noexcept { return mem_.data(); }
Data& data() noexcept { return mem_.data(); }
nf7::gui::IOSocketListPopup socket_popup_;
};
class InlineNode::Lambda final : public nf7::Node::Lambda,
public std::enable_shared_from_this<InlineNode::Lambda> {
public:
Lambda(InlineNode& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept :
nf7::Node::Lambda(f, parent), file_(f.life_), log_(f.log_) {
}
void Handle(std::string_view k, const nf7::Value& v,
const std::shared_ptr<nf7::Node::Lambda>& caller) noexcept override
try {
file_.EnforceAlive();
auto ljq = file_->
ResolveUpwardOrThrow("_luajit").
interfaceOrThrow<nf7::luajit::Queue>().self();
std::optional<std::string> scr;
auto& mem = file_->mem_;
if (last_ != std::exchange(last_, mem.Save()->id())) {
scr = mem.last().script;
}
auto self = shared_from_this();
auto th = std::make_shared<nf7::luajit::Thread>(
self, ljq,
nf7::luajit::Thread::CreateNodeLambdaHandler(caller, shared_from_this()));
th->Install(log_);
th_.emplace_back(th);
auto ctx = std::make_shared<nf7::GenericContext>(*file_);
auto p = std::make_pair(std::string {k}, std::move(v));
ljq->Push(self, [this, ctx, ljq, caller, th, scr = std::move(scr), p = std::move(p)](auto L) {
auto thL = th->Init(L);
// push function
if (scr) {
if (0 != luaL_loadstring(thL, scr->c_str())) {
log_->Error("luajit parse error: "s+lua_tostring(thL, -1));
return;
}
lua_pushvalue(thL, -1);
func_.emplace(ctx, ljq, thL);
} else {
if (!func_) {
log_->Error("last cache is broken");
return;
}
func_->PushSelf(thL);
}
// push args
lua_pushstring(thL, p.first.c_str()); // key
nf7::luajit::PushValue(thL, p.second); // value
// push ctx table
if (ctxtable_ && ctxtable_->ljq() != ljq) {
ctxtable_ = std::nullopt;
log_->Warn("LuaJIT queue changed, ctxtable is cleared");
}
if (ctxtable_) {
ctxtable_->PushSelf(thL);
} else {
lua_createtable(thL, 0, 0);
lua_pushvalue(thL, -1);
ctxtable_.emplace(ctx, ljq, thL);
}
// start function
th->Resume(thL, 3);
});
} catch (nf7::LifeExpiredException&) {
} catch (nf7::Exception& e) {
log_->Error(e.msg());
}
void Abort() noexcept override {
for (auto& wth : th_) {
if (auto th = wth.lock()) {
th->Abort();
}
}
}
private:
// synchronized with filesystem
nf7::Life<InlineNode>::Ref file_;
std::shared_ptr<nf7::LoggerRef> log_;
std::optional<nf7::Memento::Tag::Id> last_;
std::vector<std::weak_ptr<nf7::luajit::Thread>> th_;
// used on luajit thread
std::optional<nf7::luajit::Ref> func_;
std::optional<nf7::luajit::Ref> ctxtable_;
};
std::shared_ptr<nf7::Node::Lambda> InlineNode::CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
return std::make_shared<Lambda>(*this, parent);
}
void InlineNode::UpdateMenu() noexcept {
if (ImGui::MenuItem("I/O list")) {
socket_popup_.Open(data().inputs, data().outputs);
}
}
void InlineNode::UpdateNode(nf7::Node::Editor&) noexcept {
const auto em = ImGui::GetFontSize();
ImGui::TextUnformatted("LuaJIT/InlineNode");
ImGui::SameLine();
if (ImGui::SmallButton("I/O list")) {
socket_popup_.Open(data().inputs, data().outputs);
}
nf7::gui::NodeInputSockets(data().inputs);
ImGui::SameLine();
ImGui::InputTextMultiline("##script", &data().script, {24*em, 8*em});
if (ImGui::IsItemDeactivatedAfterEdit()) {
mem_.Commit();
}
ImGui::SameLine();
nf7::gui::NodeOutputSockets(data().outputs);
socket_popup_.Update();
}
void InlineNode::UpdateWidget() noexcept {
ImGui::TextUnformatted("LuaJIT/InlineNode");
if (ImGui::Button("I/O list")) {
socket_popup_.Open(data().inputs, data().outputs);
}
ImGui::InputTextMultiline("script", &data().script);
if (ImGui::IsItemDeactivatedAfterEdit()) {
mem_.Commit();
}
socket_popup_.Update();
}
}
} // namespace nf7

View File

@@ -1,38 +1,39 @@
#include <algorithm>
#include <exception>
#include <chrono>
#include <filesystem>
#include <memory>
#include <optional>
#include <typeinfo>
#include <variant>
#include <utility>
#include <vector>
#include <imgui.h>
#include <imgui_stdlib.h>
#include <ImNodes.h>
#include <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 "nf7.hh"
#include "common/dir_item.hh"
#include "common/file_base.hh"
#include "common/file_holder.hh"
#include "common/future.hh"
#include "common/generic_context.hh"
#include "common/generic_type_info.hh"
#include "common/generic_config.hh"
#include "common/generic_memento.hh"
#include "common/gui_file.hh"
#include "common/gui_popup.hh"
#include "common/generic_type_info.hh"
#include "common/gui.hh"
#include "common/life.hh"
#include "common/logger_ref.hh"
#include "common/luajit_nfile_importer.hh"
#include "common/luajit_queue.hh"
#include "common/luajit_ref.hh"
#include "common/luajit_thread.hh"
#include "common/memento.hh"
#include "common/node.hh"
#include "common/node_root_lambda.hh"
#include "common/ptr_selector.hh"
#include "common/util_string.hh"
using namespace std::literals;
@@ -41,85 +42,68 @@ using namespace std::literals;
namespace nf7 {
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:
static inline const GenericTypeInfo<Node> kType =
{"LuaJIT/Node", {"nf7::DirItem"}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("Defines new Node using Lua object factory.");
ImGui::Bullet();
ImGui::TextUnformatted("refers nf7::luajit::Queue through linked LuaJIT/Obj");
}
static inline const nf7::GenericTypeInfo<Node> kType = {
"LuaJIT/Node", {"nf7::DirItem", "nf7::Node"},
"defines new pure Node without creating nfile"
};
class Lambda;
struct Data {
nf7::FileHolder::Tag obj;
std::string desc;
std::vector<std::string> inputs;
std::vector<std::string> outputs;
Data() noexcept { }
std::string Stringify() const noexcept;
void Parse(const std::string&);
std::string script;
std::vector<std::string> inputs = {"in"};
std::vector<std::string> outputs = {"out"};
};
Node(Env& env, Data&& data = {}) noexcept :
nf7::FileBase(kType, env, {&obj_, &obj_editor_, &socket_popup_}),
nf7::DirItem(nf7::DirItem::kTooltip | nf7::DirItem::kWidget),
nf7::Node(nf7::Node::kNone),
Node(nf7::Env& env, Data&& data = {}) noexcept :
nf7::FileBase(kType, env),
nf7::GenericConfig(mem_),
nf7::DirItem(nf7::DirItem::kTooltip),
nf7::Node(nf7::Node::kCustomNode),
life_(*this),
log_(std::make_shared<nf7::LoggerRef>(*this)),
obj_(*this, "obj_factory", mem_),
obj_editor_(obj_, [](auto& t) { return t.flags().contains("nf7::Node"); }),
mem_(std::move(data), *this) {
nf7::FileBase::Install(*log_);
mem_.data().obj.SetTarget(obj_);
mem_.CommitAmend();
socket_popup_.onSubmit = [this](auto&& i, auto&& o) {
this->env().ExecMain(
std::make_shared<nf7::GenericContext>(*this),
[this, i = std::move(i), o = std::move(o)]() {
mem_.data().inputs = std::move(i);
mem_.data().outputs = std::move(o);
mem_.Commit();
});
};
obj_.onEmplace = obj_.onChildUpdate = [this]() {
if (fu_) {
log_->Info("factory update detected, dropping cache");
}
fu_ = std::nullopt;
mem_(*this, std::move(data)),
importer_(std::make_shared<nf7::luajit::NFileImporter>(env.npath())) {
mem_.onCommit = mem_.onRestore = [this]() {
cache_ = std::nullopt;
};
}
Node(nf7::Deserializer& ar) : Node(ar.env()) {
ar(obj_, data().desc, data().inputs, data().outputs);
nf7::util::Uniq(data().inputs);
nf7::util::Uniq(data().outputs);
ar(mem_->script, mem_->inputs, mem_->outputs);
nf7::Node::ValidateSockets(mem_->inputs);
nf7::Node::ValidateSockets(mem_->outputs);
}
void Serialize(nf7::Serializer& ar) const noexcept override {
ar(obj_, data().desc, data().inputs, data().outputs);
ar(mem_->script, mem_->inputs, mem_->outputs);
}
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<Node>(env, Data {data()});
return std::make_unique<Node>(env, Data {mem_.data()});
}
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
std::span<const std::string> GetInputs() const noexcept override {
return data().inputs;
}
std::span<const std::string> GetOutputs() const noexcept override {
return data().outputs;
nf7::Node::Meta GetMeta() const noexcept override {
return nf7::Node::Meta {mem_->inputs, mem_->outputs};
}
nf7::Future<std::shared_ptr<nf7::luajit::Ref>> Build() noexcept;
void PostUpdate() noexcept override;
void UpdateTooltip() noexcept override;
void UpdateWidget() noexcept override;
void UpdateNode(nf7::Node::Editor&) noexcept override;
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector<
nf7::DirItem, nf7::Memento, nf7::Node>(t).Select(this, &mem_);
nf7::Config, nf7::DirItem, nf7::Memento, nf7::Node>(t).Select(this, &mem_);
}
private:
@@ -127,63 +111,40 @@ class Node final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
std::shared_ptr<nf7::LoggerRef> log_;
nf7::FileHolder obj_;
nf7::gui::FileHolderEditor obj_editor_;
nf7::gui::IOSocketListPopup socket_popup_;
nf7::GenericMemento<Data> mem_;
const Data& data() const noexcept { return mem_.data(); }
Data& data() noexcept { return mem_.data(); }
// factory context
std::shared_ptr<nf7::NodeRootLambda> factory_;
std::optional<nf7::Future<nf7::Value>> fu_;
std::optional<nf7::Future<std::shared_ptr<nf7::luajit::Ref>>> cache_;
std::filesystem::file_time_type last_build_ = {};
std::shared_ptr<nf7::luajit::NFileImporter> importer_;
nf7::ContextOwner la_owner_;
};
class Node::Lambda final : public nf7::Node::Lambda,
public std::enable_shared_from_this<Node::Lambda> {
public:
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(std::string_view k, const nf7::Value& v,
const std::shared_ptr<nf7::Node::Lambda>& caller) noexcept override
void Handle(const nf7::Node::Lambda::Msg& in) noexcept override
try {
f_.EnforceAlive();
auto self = shared_from_this();
if (!f_->fu_) {
auto& n = f_->obj_.GetFileOrThrow().interfaceOrThrow<nf7::Node>();
auto b = nf7::NodeRootLambda::Builder {*f_, n};
f_->fu_ = b.Receive("product");
f_->factory_ = b.Build();
b.Send("create", nf7::Value::Pulse {});
f_->fu_->ThenSub(self, [this](auto) { if (f_) f_->factory_ = nullptr; });
}
assert(f_->fu_);
f_->fu_->ThenSub(self, [this, k = std::string {k}, v = v, caller](auto fu) mutable {
try {
auto ref = fu.value().template data<nf7::luajit::Ref>();
CallFunc(ref, std::move(k), std::move(v), caller);
} catch (nf7::Exception& e) {
log_->Error("failed to call lua function: "+e.msg());
}
f_->Build().
ThenIf(self, [this, in](auto& func) mutable {
if (f_) StartThread(in, func);
});
} catch (nf7::LifeExpiredException&) {
} catch (nf7::Exception& e) {
log_->Error(e.msg());
} catch (nf7::ExpiredException&) {
}
void Abort() noexcept override {
for (auto& wth : th_) {
if (auto th = wth.lock()) {
th->Abort();
}
}
th_owner_.AbortAll();
}
private:
@@ -191,114 +152,161 @@ class Node::Lambda final : public nf7::Node::Lambda,
std::shared_ptr<nf7::LoggerRef> log_;
std::vector<std::weak_ptr<nf7::luajit::Thread>> th_;
std::mutex mtx_;
std::shared_ptr<nf7::Context> table_ctx_;
std::optional<nf7::luajit::Ref> table_;
std::optional<nf7::luajit::Ref> ctxtable_;
nf7::ContextOwner th_owner_;
void CallFunc(const std::shared_ptr<nf7::luajit::Ref>& func,
std::string&& k, nf7::Value&& v,
const std::shared_ptr<nf7::Node::Lambda>& caller) {
auto self = shared_from_this();
th_.erase(
std::remove_if(th_.begin(), th_.end(), [](auto& x) { return x.expired(); }),
th_.end());
void StartThread(const nf7::Node::Lambda::Msg& in,
const std::shared_ptr<nf7::luajit::Ref>& func) noexcept {
auto ljq = func->ljq();
auto th = std::make_shared<nf7::luajit::Thread>(
self, ljq,
nf7::luajit::Thread::CreateNodeLambdaHandler(caller, shared_from_this()));
auto self = shared_from_this();
auto hndl = nf7::luajit::Thread::CreateNodeLambdaHandler(in.sender, self);
auto th = th_owner_.Create<nf7::luajit::Thread>(self, ljq, std::move(hndl));
th->Install(log_);
th_.emplace_back(th);
auto ctx = std::make_shared<nf7::GenericContext>(env(), initiator());
ljq->Push(self, [this, ctx, ljq, k = std::move(k), v = std::move(v), caller, func, th](auto L) mutable {
auto thL = th->Init(L);
func->PushSelf(thL);
// push args
lua_pushstring(thL, k.c_str());
nf7::luajit::PushValue(thL, v);
// push context table
if (ctxtable_ && ctxtable_->ljq() != ljq) {
ctxtable_ = std::nullopt;
ljq->Push(self, [this, ljq, th, func, in](auto L) mutable {
{
std::unique_lock<std::mutex> k {mtx_};
if (!table_ || table_->ljq() != ljq) {
lua_createtable(L, 0, 0);
table_.emplace(table_ctx_, ljq, L);
}
if (!ctxtable_) {
lua_createtable(thL, 0, 0);
lua_pushvalue(thL, -1);
ctxtable_.emplace(ctx, ljq, thL);
} else {
ctxtable_->PushSelf(thL);
}
// execute
th->Resume(thL, 3);
L = th->Init(L);
func->PushSelf(L);
nf7::luajit::PushAll(L, in.name, in.value);
table_->PushSelf(L);
th->Resume(L, 3);
});
}
};
std::shared_ptr<nf7::Node::Lambda> Node::CreateLambda(
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
try {
if (cache_ && !cache_->error()) {
return *cache_;
}
last_build_ = std::chrono::file_clock::now();
nf7::Future<std::shared_ptr<nf7::luajit::Ref>>::Promise pro;
auto ctx = std::make_shared<nf7::GenericContext>(*this, "lambda function builder");
auto ljq =
ResolveUpwardOrThrow("_luajit").
interfaceOrThrow<nf7::luajit::Queue>().self();
// create new thread
auto handler = luajit::Thread::CreatePromiseHandler<std::shared_ptr<luajit::Ref>>(pro, [ctx, ljq](auto L) {
if (lua_gettop(L) == 1 && lua_isfunction(L, 1)) {
return std::make_shared<nf7::luajit::Ref>(ctx, ljq, L);
} else {
throw nf7::Exception {"lambda script must return a function"};
}
});
auto th = std::make_shared<nf7::luajit::Thread>(ctx, ljq, std::move(handler));
th->Install(log_);
th->Install(importer_);
// start the thread
ljq->Push(ctx, [ctx, ljq, th, pro, script = mem_->script](auto L) mutable {
ZoneScopedN("build function for Node");
L = th->Init(L);
if (0 == luaL_loadstring(L, script.c_str())) {
th->Resume(L, 0);
} else {
pro.Throw<nf7::Exception>(lua_tostring(L, -1));
}
});
cache_ = pro.future().
Catch<nf7::Exception>([log = log_](auto& e) {
log->Error(e);
});
return *cache_;
} catch (nf7::Exception&) {
return {std::current_exception()};
}
void Node::PostUpdate() noexcept {
if (cache_ && cache_->done()) {
if (last_build_ < importer_->GetLatestMod()) {
cache_ = std::nullopt;
}
}
}
void Node::UpdateTooltip() noexcept {
ImGui::Text("factory:");
ImGui::Indent();
obj_editor_.Tooltip();
ImGui::Unindent();
ImGui::Spacing();
ImGui::Text("input:");
ImGui::Indent();
for (const auto& name : data().inputs) {
ImGui::Bullet(); ImGui::TextUnformatted(name.c_str());
const char* state = "unused";
if (cache_) {
state =
cache_->done()? "ready":
cache_->yet()? "building": "broken";
}
if (data().inputs.empty()) {
ImGui::TextDisabled("(nothing)");
}
ImGui::Unindent();
ImGui::Text("output:");
ImGui::Indent();
for (const auto& name : data().outputs) {
ImGui::Bullet(); ImGui::TextUnformatted(name.c_str());
}
if (data().outputs.empty()) {
ImGui::TextDisabled("(nothing)");
}
ImGui::Unindent();
ImGui::Text("description:");
ImGui::Indent();
if (data().desc.empty()) {
ImGui::TextDisabled("(empty)");
} else {
ImGui::TextUnformatted(data().desc.c_str());
}
ImGui::Unindent();
ImGui::Text("state: %s", state);
}
void Node::UpdateWidget() noexcept {
void Node::UpdateNode(nf7::Node::Editor&) noexcept {
const auto em = ImGui::GetFontSize();
ImGui::TextUnformatted("LuaJIT/Node: config");
obj_editor_.ButtonWithLabel("obj factory");
ImGui::TextUnformatted("LuaJIT/Node");
ImGui::SameLine();
if (ImGui::SmallButton("config")) {
ImGui::OpenPopup("ConfigPopup");
}
if (ImGui::BeginPopup("ConfigPopup")) {
static nf7::gui::ConfigEditor ed;
ed(*this);
ImGui::EndPopup();
}
ImGui::InputTextMultiline("description", &data().desc, {0, 4*em});
nf7::gui::NodeInputSockets(mem_->inputs);
ImGui::SameLine();
ImGui::InputTextMultiline("##script", &mem_->script, {24*em, 8*em});
if (ImGui::IsItemDeactivatedAfterEdit()) {
mem_.Commit();
}
ImGui::SameLine();
nf7::gui::NodeOutputSockets(mem_->outputs);
}
if (ImGui::Button("I/O list")) {
socket_popup_.Open(data().inputs, data().outputs);
}
ImGui::Spacing();
obj_editor_.ItemWidget("obj factory");
std::string Node::Data::Stringify() const noexcept {
YAML::Emitter st;
st << YAML::BeginMap;
st << YAML::Key << "inputs";
st << YAML::Value << inputs;
st << YAML::Key << "outputs";
st << YAML::Value << outputs;
st << YAML::Key << "script";
st << YAML::Value << YAML::Literal << script;
st << YAML::EndMap;
return std::string {st.c_str(), st.size()};
}
void Node::Data::Parse(const std::string& str)
try {
const auto yaml = YAML::Load(str);
socket_popup_.Update();
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>();
nf7::Node::ValidateSockets(d.inputs);
nf7::Node::ValidateSockets(d.outputs);
*this = std::move(d);
} catch (YAML::Exception& e) {
throw nf7::Exception {e.what()};
}
}

112
file/node_comment.cc Normal file
View File

@@ -0,0 +1,112 @@
#include <memory>
#include <string>
#include <typeinfo>
#include <utility>
#include <imgui.h>
#include <imgui_stdlib.h>
#include <yas/serialize.hpp>
#include <yas/types/std/string.hpp>
#include <yas/types/utility/usertype.hpp>
#include "nf7.hh"
#include "common/file_base.hh"
#include "common/generic_memento.hh"
#include "common/generic_type_info.hh"
#include "common/memento.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
namespace nf7 {
namespace {
class Comment final : public nf7::FileBase, public nf7::Node {
public:
static inline const nf7::GenericTypeInfo<Comment> kType = {
"Node/Comment", {"nf7::Node",},
"adds comments for your future",
};
struct Data {
std::string text;
void serialize(auto& ar) {
ar(text);
}
};
Comment(nf7::Env& env, Data&& d = {}) noexcept :
nf7::FileBase(kType, env),
nf7::Node(nf7::Node::kCustomNode),
mem_(*this, std::move(d)) {
}
Comment(nf7::Deserializer& ar) : Comment(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<Comment>(env, Data {mem_.data()});
}
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept override {
return std::make_shared<nf7::Node::Lambda>(*this, parent);
}
nf7::Node::Meta GetMeta() const noexcept override {
return {{}, {}};
}
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::GenericMemento<Data> mem_;
void Editor() noexcept;
};
void Comment::UpdateNode(nf7::Node::Editor&) noexcept {
ImGui::TextUnformatted("Node/Comment");
ImGui::SameLine();
if (ImGui::SmallButton("edit")) {
ImGui::OpenPopup("Editor");
}
ImGui::Spacing();
ImGui::Indent();
ImGui::TextUnformatted(mem_->text.c_str());
ImGui::Unindent();
if (ImGui::BeginPopup("Editor")) {
Editor();
ImGui::EndPopup();
}
}
void Comment::UpdateMenu(nf7::Node::Editor&) noexcept {
if (ImGui::BeginMenu("edit")) {
Editor();
ImGui::EndMenu();
}
}
void Comment::Editor() noexcept {
const auto em = ImGui::GetFontSize();
ImGui::InputTextMultiline("##text", &mem_->text, ImVec2 {16*em, 4*em});
if (ImGui::IsItemDeactivatedAfterEdit()) {
mem_.Commit();
}
}
}
} // namespace nf7

428
file/node_dll.cc Normal file
View File

@@ -0,0 +1,428 @@
#include <cassert>
#include <filesystem>
#include <memory>
#include <optional>
#include <typeinfo>
#include <utility>
#include <yas/serialize.hpp>
#include <yas/types/utility/usertype.hpp>
#include "nf7.hh"
#include "common/dir.hh"
#include "common/dir_item.hh"
#include "common/dll.hh"
#include "common/file_base.hh"
#include "common/future.hh"
#include "common/generic_context.hh"
#include "common/generic_dir.hh"
#include "common/generic_memento.hh"
#include "common/generic_type_info.hh"
#include "common/gui.hh"
#include "common/gui_dnd.hh"
#include "common/life.hh"
#include "common/logger_ref.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
#include "common/yas_std_filesystem.hh"
#include "common/node.h"
namespace nf7 {
namespace {
namespace adaptor {
struct InitParam {
nf7_init_t base;
std::shared_ptr<nf7::DLL> dll;
std::vector<const nf7_node_t*> nodes;
};
struct Context {
nf7_ctx_t base;
std::shared_ptr<nf7::Node::Lambda> caller;
std::shared_ptr<nf7::Node::Lambda> callee;
};
} // namespace adaptor
static const nf7_vtable_t kVtable = {
.init = {
.register_node = [](nf7_init_t* ptr, const nf7_node_t* n) {
auto& p = *reinterpret_cast<adaptor::InitParam*>(ptr);
p.nodes.push_back(n);
},
},
.ctx = {
.exec_async = [](nf7_ctx_t* ptr, void* udata, void (*f)(nf7_ctx_t*, void*), uint64_t ms) {
auto& p = *reinterpret_cast<adaptor::Context*>(ptr);
const auto time = ms?
nf7::Env::Clock::now() + std::chrono::milliseconds(ms):
nf7::Env::Time::min();
p.callee->env().ExecAsync(p.callee, [udata, f, p]() mutable {
nf7::Value temp;
p.base.value = reinterpret_cast<nf7_value_t*>(&temp);
f(&p.base, udata);
}, time);
},
.exec_emit = [](nf7_ctx_t* ptr, const char* n, const nf7_value_t* vptr, uint64_t ms) {
auto& p = *reinterpret_cast<adaptor::Context*>(ptr);
auto name = std::string {n};
auto& v = *reinterpret_cast<const nf7::Value*>(vptr);
const auto time = ms?
nf7::Env::Clock::now() + std::chrono::milliseconds(ms):
nf7::Env::Time::min();
p.callee->env().ExecSub(p.callee, [p, name, v]() mutable {
p.caller->Handle(name, v, p.callee);
}, time);
},
},
.value = {
.create = [](const nf7_value_t* vptr) {
if (vptr) {
const auto& v = *reinterpret_cast<const nf7::Value*>(vptr);
return reinterpret_cast<nf7_value_t*>(new nf7::Value {v});
} else {
return reinterpret_cast<nf7_value_t*>(new nf7::Value {});
}
},
.destroy = [](nf7_value_t* vptr) {
delete reinterpret_cast<nf7::Value*>(vptr);
},
.get_type = [](const nf7_value_t* vptr) {
struct Visitor {
uint8_t operator()(nf7::Value::Pulse) { return NF7_PULSE; }
uint8_t operator()(nf7::Value::Boolean) { return NF7_BOOLEAN; }
uint8_t operator()(nf7::Value::Integer) { return NF7_INTEGER; }
uint8_t operator()(nf7::Value::Scalar) { return NF7_SCALAR; }
uint8_t operator()(const nf7::Value::String&) { return NF7_STRING; }
uint8_t operator()(const nf7::Value::ConstVector&) { return NF7_VECTOR; }
uint8_t operator()(const nf7::Value::ConstTuple&) { return NF7_TUPLE; }
uint8_t operator()(const nf7::Value::DataPtr&) { return NF7_UNKNOWN; }
};
const auto& v = *reinterpret_cast<const nf7::Value*>(vptr);
return std::visit(Visitor {}, v.value());
},
.get_boolean = [](const nf7_value_t* vptr, bool* ret) {
auto& v = *reinterpret_cast<const nf7::Value*>(vptr);
if (!v.isBoolean()) return false;
*ret = v.boolean();
return true;
},
.get_integer = [](const nf7_value_t* vptr, int64_t* ret) {
auto& v = *reinterpret_cast<const nf7::Value*>(vptr);
if (!v.isInteger()) return false;
*ret = v.integer();
return true;
},
.get_scalar = [](const nf7_value_t* vptr, double* ret) {
auto& v = *reinterpret_cast<const nf7::Value*>(vptr);
if (!v.isScalar()) return false;
*ret = v.scalar();
return true;
},
.get_string = [](const nf7_value_t* vptr, size_t* n) -> const char* {
auto& v = *reinterpret_cast<const nf7::Value*>(vptr);
if (!v.isString()) return nullptr;
auto& str = v.string();
if (n) *n = str.size();
return str.data();
},
.get_vector = [](const nf7_value_t* vptr, size_t* n) -> const uint8_t* {
auto& v = *reinterpret_cast<const nf7::Value*>(vptr);
if (!v.isVector()) return nullptr;
auto& vec = v.vector();
*n = vec->size();
return vec->data();
},
.get_tuple = [](const nf7_value_t* vptr, const char* name) -> const nf7_value_t* {
auto& v = *reinterpret_cast<const nf7::Value*>(vptr);
try {
return reinterpret_cast<const nf7_value_t*>(&v.tuple(name));
} catch (nf7::Exception&) {
return nullptr;
}
},
.set_pulse = [](nf7_value_t* vptr) {
auto& v = *reinterpret_cast<nf7::Value*>(vptr);
v = nf7::Value::Pulse {};
},
.set_boolean = [](nf7_value_t* vptr, nf7::Value::Boolean b) {
auto& v = *reinterpret_cast<nf7::Value*>(vptr);
v = b;
},
.set_integer = [](nf7_value_t* vptr, nf7::Value::Integer i) {
auto& v = *reinterpret_cast<nf7::Value*>(vptr);
v = i;
},
.set_scalar = [](nf7_value_t* vptr, nf7::Value::Scalar s) {
auto& v = *reinterpret_cast<nf7::Value*>(vptr);
v = s;
},
.set_string = [](nf7_value_t* vptr, size_t n) {
auto& v = *reinterpret_cast<nf7::Value*>(vptr);
v = nf7::Value::String(n, ' ');
return v.string().data();
},
.set_vector = [](nf7_value_t* vptr, size_t n) {
auto& v = *reinterpret_cast<nf7::Value*>(vptr);
auto vec = std::vector<uint8_t>(n);
auto ret = vec.data();
v = std::move(vec);
assert(v.vector()->data() == ret);
return ret;
},
.set_tuple = [](nf7_value_t* vptr, const char** names, nf7_value_t** ret) {
const char** itr = names;
while (*itr) ++itr;
const auto n = static_cast<size_t>(itr-names);
std::vector<nf7::Value::TuplePair> ps;
ps.reserve(n);
for (size_t i = 0; i < n; ++i) {
ps.emplace_back(names[i], nf7::Value {});
ret[i] = reinterpret_cast<nf7_value_t*>(&ps.back().second);
}
auto& v = *reinterpret_cast<nf7::Value*>(vptr);
v = std::move(ps);
},
},
};
class Loader final : public nf7::FileBase, public nf7::DirItem {
public:
static inline const nf7::GenericTypeInfo<Loader> kType = {
"Node/DLL", {"nf7::DirItem",}, "loads a dynamic link library and defines new Node"};
class Node;
struct Data {
std::filesystem::path npath;
void serialize(auto& ar) {
ar(npath);
}
};
Loader(nf7::Env& env, Data&& d = {}) noexcept :
nf7::FileBase(kType, env),
nf7::DirItem(nf7::DirItem::kMenu |
nf7::DirItem::kTree),
life_(*this),
log_(std::make_shared<nf7::LoggerRef>(*this)),
mem_(*this, std::move(d)),
dir_(*this) {
mem_.onCommit = mem_.onRestore = [this]() { Open(); };
}
Loader(nf7::Deserializer& ar) noexcept : Loader(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<Loader>(env, Data {mem_.data()});
}
void UpdateMenu() noexcept override;
void UpdateTree() noexcept override;
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector<nf7::DirItem>(t).Select(this);
}
private:
nf7::Life<Loader> life_;
std::shared_ptr<nf7::LoggerRef> log_;
nf7::GenericMemento<Data> mem_;
nf7::GenericDir dir_;
std::optional<nf7::Future<adaptor::InitParam>> open_fu_;
nf7::Future<adaptor::InitParam> Open() noexcept;
};
class Loader::Node final : public nf7::File, public nf7::DirItem, public nf7::Node {
public:
static inline const nf7::GenericTypeInfo<Loader::Node> kType = {
"Node/DLL/Node", {"nf7::DirItem",}, "Node defined by a dynamic link library"};
class Lambda;
Node(nf7::Env& env,
const std::shared_ptr<nf7::DLL>& dll,
const nf7_node_t& meta) noexcept :
nf7::File(kType, env),
nf7::DirItem(nf7::DirItem::kTooltip),
nf7::Node(nf7::Node::kNone),
life_(*this),
dll_(dll), meta_(meta) {
}
void Serialize(nf7::Serializer&) const noexcept override {
}
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<Loader::Node>(env, dll_, meta_);
}
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept override;
nf7::Node::Meta GetMeta() const noexcept override {
return {GetSockList(meta_.inputs), GetSockList(meta_.outputs)};
}
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector<nf7::DirItem, nf7::Node>(t).Select(this);
}
private:
nf7::Life<Loader::Node> life_;
std::shared_ptr<nf7::DLL> dll_;
const nf7_node_t& meta_;
static std::vector<std::string> GetSockList(const char** arr) noexcept {
std::vector<std::string> ret;
auto itr = arr;
while (*itr) ++itr;
ret.reserve(static_cast<size_t>(itr-arr));
itr = arr;
while (*itr) ret.push_back(*(itr++));
return ret;
}
};
class Loader::Node::Lambda final : public nf7::Node::Lambda,
public std::enable_shared_from_this<Loader::Node::Lambda> {
public:
Lambda(Loader::Node& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept :
nf7::Node::Lambda(f, parent),
dll_(f.dll_), meta_(f.meta_), ptr_(meta_.init? meta_.init(): nullptr) {
}
~Lambda() noexcept {
if (meta_.deinit) {
meta_.deinit(ptr_);
}
}
void Handle(const nf7::Node::Lambda::Msg& in) noexcept override {
nf7::Value v = in.value;
nf7::Value temp;
adaptor::Context ctx = {
.base = {
.value = reinterpret_cast<nf7_value_t*>(&temp),
.ptr = ptr_,
},
.caller = in.sender,
.callee = shared_from_this(),
};
const nf7_node_msg_t msg = {
.name = in.name.c_str(),
.value = reinterpret_cast<nf7_value_t*>(&v),
.ctx = &ctx.base,
};
meta_.handle(&msg);
}
private:
std::shared_ptr<nf7::DLL> dll_;
const nf7_node_t& meta_;
void* ptr_;
};
std::shared_ptr<nf7::Node::Lambda> Loader::Node::CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
return std::make_shared<Loader::Node::Lambda>(*this, parent);
}
nf7::Future<adaptor::InitParam> Loader::Open() noexcept {
if (open_fu_ && open_fu_->yet()) return *open_fu_;
const auto npath = env().npath() / mem_->npath;
const auto ctx = std::make_shared<nf7::GenericContext>(*this, "loading DLL");
nf7::Future<adaptor::InitParam>::Promise pro {ctx};
nf7::DLL::Create(ctx, env().npath() / mem_->npath).
Chain(pro, [](auto& dll) {
auto f = dll->template Resolve<void, const nf7_init_t*>("nf7_init");
adaptor::InitParam p = {
.base = {
.vtable = &kVtable,
},
.dll = dll,
.nodes = {},
};
f(&p.base);
return p;
});
open_fu_ = pro.future();
open_fu_->ThenIf(ctx, [this, f = life_.ref()](auto& p) {
if (!f) return;
dir_.Clear();
for (auto meta : p.nodes) {
// TODO: validate meta
dir_.Add(meta->name, std::make_unique<Loader::Node>(env(), p.dll, *meta));
}
}).
Catch<nf7::Exception>(ctx, [log = log_](auto&) {
log->Warn("failed to load dynamic library");
});
return *open_fu_;
}
void Loader::UpdateMenu() noexcept {
if (ImGui::BeginMenu("config")) {
if (nf7::gui::NPathButton("npath", mem_->npath, env())) {
mem_.Commit();
}
ImGui::EndMenu();
}
}
void Loader::UpdateTree() noexcept {
for (const auto& item : dir_.items()) {
const auto& name = item.first;
auto& file = *item.second;
constexpr auto kFlags =
ImGuiTreeNodeFlags_SpanFullWidth |
ImGuiTreeNodeFlags_NoTreePushOnOpen |
ImGuiTreeNodeFlags_Leaf;
ImGui::TreeNodeEx(item.second.get(), kFlags, "%s", name.c_str());
// tooltip
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
nf7::gui::FileTooltip(file);
ImGui::EndTooltip();
}
// dnd source
if (ImGui::BeginDragDropSource()) {
gui::dnd::Send(gui::dnd::kFilePath, file.abspath());
ImGui::TextUnformatted(file.type().name().c_str());
ImGui::SameLine();
ImGui::TextDisabled(file.abspath().Stringify().c_str());
ImGui::EndDragDropSource();
}
// context menu
if (ImGui::BeginPopupContextItem()) {
nf7::gui::FileMenuItems(file);
ImGui::EndPopup();
}
}
}
}
} // namespace nf7

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,152 +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(std::string_view name, const nf7::Value&,
const std::shared_ptr<nf7::Node::Lambda>& caller) noexcept override {
if (!f_) return;
if (name == "in") {
caller->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_type_info.hh"
#include "common/generic_watcher.hh"
#include "common/gui.hh"
#include "common/gui_dnd.hh"
#include "common/gui_node.hh"
#include "common/gui_popup.hh"
#include "common/life.hh"
#include "common/logger_ref.hh"
#include "common/memento.hh"
@@ -37,16 +36,7 @@ namespace {
class Ref final : public nf7::FileBase, public nf7::Node {
public:
static inline const nf7::GenericTypeInfo<Ref> kType = {
"Node/Ref", {"nf7::Node"}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("Refers other Node.");
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Node");
ImGui::Bullet(); ImGui::TextUnformatted(
"the referencee's changes won't be applied to active lambdas "
"until their recreation");
ImGui::Bullet(); ImGui::TextUnformatted(
"press 'sync' button on Node UI to resolve socket issues");
}
"Node/Ref", {"nf7::Node"}, "refers other Node"};
class Lambda;
@@ -58,47 +48,30 @@ class Ref final : public nf7::FileBase, public nf7::Node {
};
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),
life_(*this),
log_(std::make_shared<nf7::LoggerRef>(*this)),
mem_(std::move(data), *this),
config_popup_(*this) {
nf7::FileBase::Install(*log_);
mem_.onRestore = mem_.onCommit = [this]() { SetUpWatcher(); };
mem_(*this, std::move(data)) {
mem_.onRestore = mem_.onCommit = [this]() {
SetUpWatcher();
};
}
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 {
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 {
return std::make_unique<Ref>(env, Data {data()});
return std::make_unique<Ref>(env, Data {mem_.data()});
}
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
std::span<const std::string> GetInputs() const noexcept override {
return data().inputs;
}
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;
}
nf7::Node::Meta GetMeta() const noexcept override {
return {mem_->inputs, mem_->outputs};
}
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_;
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
nf7::File& target() const {
auto& f = ResolveOrThrow(data().target);
auto& f = ResolveOrThrow(mem_->target);
if (&f == this) throw nf7::Exception("self reference");
return f;
}
// socket synchronization
bool SyncQuiet() noexcept {
auto& dsti = data().inputs;
auto& dsto = data().outputs;
auto& dsti = mem_->inputs;
auto& dsto = mem_->outputs;
bool mod = false;
try {
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());
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());
dsto = std::vector<std::string>{srco.begin(), srco.end()};
} catch (nf7::Exception& e) {
@@ -181,7 +136,7 @@ class Ref final : public nf7::FileBase, public nf7::Node {
// referencee operation
void ExecChangeTarget(Path&& p) noexcept {
auto& target = mem_.data().target;
auto& target = mem_->target;
if (p == target) return;
env().ExecMain(
@@ -217,36 +172,48 @@ class Ref::Lambda final : public Node::Lambda,
Node::Lambda(f, parent), f_(f.life_), log_(f.log_) {
}
void Handle(std::string_view name, const Value& v,
const std::shared_ptr<Node::Lambda>& caller) noexcept override
void Handle(const nf7::Node::Lambda::Msg& in) noexcept override
try {
if (!f_) return;
auto parent = this->parent();
if (!parent) return;
if (caller == base_) {
parent->Handle(name, v, shared_from_this());
}
if (caller == parent) {
if (!base_) {
// check if target file is changed
auto& target = f_->target();
if (target.id() != target_id_) {
if (depth() > kMaxDepth) {
log_->Error("stack overflow");
return;
throw nf7::Exception {"stack overflow"};
}
base_ = f_->target().
target_id_ = target.id();
target_ = target.
interfaceOrThrow<nf7::Node>().
CreateLambda(shared_from_this());
}
base_->Handle(name, v, shared_from_this());
// output from the target
if (in.sender == target_) {
parent->Handle(in.name, in.value, shared_from_this());
return;
}
} catch (nf7::Exception& e) {
log_->Error("failed to call referencee: "+e.msg());
// 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 {
if (base_) {
base_->Abort();
if (target_) {
target_->Abort();
target_ = nullptr;
}
}
@@ -255,7 +222,8 @@ class Ref::Lambda final : public Node::Lambda,
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(
@@ -278,26 +246,23 @@ void Ref::UpdateNode(Node::Editor&) noexcept {
ExecSync();
}
const auto pathstr = mem_.data().target.Stringify();
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;
for (const auto& v : data().inputs) {
for (const auto& v : mem_->inputs) {
iw = std::max(iw, ImGui::CalcTextSize(v.c_str()).x);
}
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);
}
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})) {
config_popup_.Open();
auto newpath = mem_->target;
ImGui::SetNextItemWidth(w);
if (nf7::gui::PathButton("##target", newpath, *this)) {
ExecChangeTarget(std::move(newpath));
}
if (ImGui::BeginDragDropTarget()) {
if (auto p = gui::dnd::Accept<Path>(gui::dnd::kFilePath)) {
@@ -308,7 +273,7 @@ void Ref::UpdateNode(Node::Editor&) noexcept {
const auto right = ImGui::GetCursorPosX() + w;
ImGui::BeginGroup();
for (const auto& name : data().inputs) {
for (const auto& name : mem_->inputs) {
if (ImNodes::BeginInputSlot(name.c_str(), 1)) {
gui::NodeSocket();
ImGui::SameLine();
@@ -319,7 +284,7 @@ void Ref::UpdateNode(Node::Editor&) noexcept {
ImGui::EndGroup();
ImGui::SameLine();
ImGui::BeginGroup();
for (const auto& name : data().outputs) {
for (const auto& name : mem_->outputs) {
const auto tw = ImGui::CalcTextSize(name.c_str()).x;
ImGui::SetCursorPosX(right-(tw+style.ItemSpacing.x+em));
@@ -331,31 +296,18 @@ void Ref::UpdateNode(Node::Editor&) noexcept {
}
}
ImGui::EndGroup();
config_popup_.Update();
}
void Ref::UpdateMenu(nf7::Node::Editor& ed) noexcept {
if (ImGui::MenuItem("sync")) {
ExecSync();
}
if (ImGui::MenuItem("replace target")) {
config_popup_.Open();
}
try {
auto& f = target();
auto& n = f.interfaceOrThrow<nf7::Node>();
auto d = f.interface<nf7::DirItem>();
const bool dmenu = n.flags() & nf7::Node::kMenu_DirItem;
const bool menu = n.flags() & nf7::Node::kMenu;
if ((dmenu || menu) && ImGui::BeginMenu("target")) {
if (dmenu) {
assert(d);
ImGui::Separator();
d->UpdateMenu();
}
if (menu) {
if (ImGui::BeginMenu("target")) {
nf7::gui::FileMenuItems(f);
if (n.flags() & nf7::Node::kMenu) {
ImGui::Separator();
n.UpdateMenu(ed);
}
@@ -365,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

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 "common/file_base.hh"
#include "common/file_holder.hh"
#include "common/generic_context.hh"
#include "common/generic_memento.hh"
#include "common/generic_type_info.hh"
#include "common/gui_file.hh"
#include "common/gui_popup.hh"
#include "common/gui.hh"
#include "common/gui_value.hh"
#include "common/life.hh"
#include "common/ptr_selector.hh"
@@ -31,17 +29,13 @@
namespace nf7 {
namespace {
class Adaptor final : public nf7::FileBase, public nf7::Sequencer {
class Adaptor final : public nf7::FileBase,
public nf7::Sequencer {
public:
static inline const nf7::GenericTypeInfo<Adaptor> kType =
{"Sequencer/Adaptor", {"nf7::Sequencer"}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("Wraps and Adapts other Sequencer.");
ImGui::Bullet(); ImGui::TextUnformatted(
"implements nf7::Sequencer");
ImGui::Bullet(); ImGui::TextUnformatted(
"changes will be applied to active lambdas immediately");
}
static inline const nf7::GenericTypeInfo<Adaptor> kType = {
"Sequencer/Adaptor", {"nf7::Sequencer"},
"wraps and adapts other Sequencer",
};
class Session;
class Lambda;
@@ -56,35 +50,32 @@ class Adaptor final : public nf7::FileBase, public nf7::Sequencer {
}
};
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, Var>> input_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 :
nf7::FileBase(kType, env, {&target_, &target_editor_}),
Sequencer(Sequencer::kCustomItem |
Adaptor(nf7::Env& env, Data&& d = {}) noexcept :
nf7::FileBase(kType, env),
nf7::Sequencer(Sequencer::kCustomItem |
Sequencer::kTooltip |
Sequencer::kParamPanel),
life_(*this),
target_(*this, "target", mem_),
target_editor_(target_,
[](auto& t) { return t.flags().contains("nf7::Sequencer"); }),
mem_(std::move(data), *this) {
mem_.data().target.SetTarget(target_);
mem_.CommitAmend();
life_(*this), mem_(*this, std::move(d)) {
}
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 {
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 {
return std::make_unique<Adaptor>(env, Data {data()});
return std::make_unique<Adaptor>(env, Data {mem_.data()});
}
std::shared_ptr<nf7::Sequencer::Lambda> CreateLambda(
@@ -101,14 +92,8 @@ class Adaptor final : public nf7::FileBase, public nf7::Sequencer {
private:
nf7::Life<Adaptor> life_;
nf7::FileHolder target_;
nf7::gui::FileHolderEditor target_editor_;
nf7::GenericMemento<Data> mem_;
const Data& data() const noexcept { return mem_.data(); }
Data& data() noexcept { return mem_.data(); }
};
@@ -116,10 +101,10 @@ class Adaptor::Session final : public nf7::Sequencer::Session {
public:
// ensure that Adaptor is alive
Session(Adaptor& f, const std::shared_ptr<nf7::Sequencer::Session>& parent) noexcept : parent_(parent) {
for (auto& p : f.data().input_imm) {
for (auto& p : f.mem_->input_imm) {
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.peek) {
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;
}
}
@@ -184,7 +169,7 @@ class Adaptor::Lambda final : public nf7::Sequencer::Lambda,
try {
f_.EnforceAlive();
auto& target = f_->target_.GetFileOrThrow();
auto& target = f_->ResolveOrThrow(f_->mem_->path);
auto& seq = target.interfaceOrThrow<nf7::Sequencer>();
if (!la_ || target.id() != cached_id_) {
la_ = seq.CreateLambda(shared_from_this());
@@ -215,25 +200,29 @@ class Adaptor::Editor final : public nf7::Sequencer::Editor {
void Adaptor::UpdateItem(Sequencer::Editor&) noexcept {
try {
auto& seq = target_.GetFileOrThrow().interfaceOrThrow<nf7::Sequencer>();
auto& seq = ResolveOrThrow(mem_->path).interfaceOrThrow<nf7::Sequencer>();
if (seq.flags() & nf7::Sequencer::kCustomItem) {
Adaptor::Editor ed;
seq.UpdateItem(ed);
}
} catch (nf7::Exception&) {
ImGui::Text("%s", target_editor_.GetDisplayText().c_str());
} catch (nf7::File::NotFoundException&) {
ImGui::TextUnformatted("file missing");
} catch (nf7::File::NotImplementedException&) {
ImGui::TextUnformatted("file does not have Sequencer interface");
}
}
void Adaptor::UpdateParamPanel(Sequencer::Editor&) noexcept {
bool commit = false;
auto& imm = data().input_imm;
auto& inputs = data().input_map;
auto& outputs = data().output_map;
auto& imm = mem_->input_imm;
auto& inputs = mem_->input_map;
auto& outputs = mem_->output_map;
const auto em = ImGui::GetFontSize();
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)) {
ImGui::TableSetupColumn("left", ImGuiTableColumnFlags_WidthStretch, 1.f);
@@ -390,7 +379,7 @@ void Adaptor::UpdateParamPanel(Sequencer::Editor&) noexcept {
ImGui::Spacing();
try {
auto& seq = target_.GetFileOrThrow().interfaceOrThrow<nf7::Sequencer>();
auto& seq = ResolveOrThrow(mem_->path).interfaceOrThrow<nf7::Sequencer>();
if (seq.flags() & nf7::Sequencer::kParamPanel) {
Adaptor::Editor ed;
seq.UpdateParamPanel(ed);
@@ -406,4 +395,3 @@ void Adaptor::UpdateTooltip(Sequencer::Editor&) noexcept {
}
} // namespace nf7

View File

@@ -12,12 +12,10 @@
#include <yas/types/std/vector.hpp>
#include "common/file_base.hh"
#include "common/file_holder.hh"
#include "common/gui.hh"
#include "common/generic_context.hh"
#include "common/generic_memento.hh"
#include "common/generic_type_info.hh"
#include "common/gui_file.hh"
#include "common/gui_popup.hh"
#include "common/life.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
@@ -30,46 +28,40 @@ namespace {
class Call final : public nf7::FileBase, public nf7::Sequencer {
public:
static inline const nf7::GenericTypeInfo<Call> kType = {
"Sequencer/Call", {"nf7::Sequencer"}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("Calls a Node.");
ImGui::Bullet(); ImGui::TextUnformatted(
"implements nf7::Sequencer");
ImGui::Bullet(); ImGui::TextUnformatted(
"changes will be applied to active lambdas immediately");
}
"Sequencer/Call", {"nf7::Sequencer"},
"calls an external Node as a Sequencer",
};
class Lambda;
class SessionLambda;
struct Data {
nf7::FileHolder::Tag callee;
nf7::File::Path callee;
std::string expects;
bool pure;
void serialize(auto& ar) {
ar(callee, expects, pure);
}
};
Call(nf7::Env& env, Data&& data = {}) noexcept :
FileBase(kType, env, {&callee_, &callee_editor_}),
nf7::FileBase(kType, env),
Sequencer(Sequencer::kCustomItem |
Sequencer::kTooltip |
Sequencer::kParamPanel),
life_(*this),
callee_(*this, "callee", mem_),
callee_editor_(callee_,
[](auto& t) { return t.flags().contains("nf7::Node"); }),
mem_(std::move(data), *this) {
mem_.data().callee.SetTarget(callee_);
mem_.CommitAmend();
mem_(*this, std::move(data)) {
}
Call(nf7::Deserializer& ar) : Call(ar.env()) {
ar(callee_, data().expects, data().pure);
ar(mem_.data());
}
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 {
return std::make_unique<Call>(env, Data {data()});
return std::make_unique<Call>(env, Data {mem_.data()});
}
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;
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
return InterfaceSelector<
return nf7::InterfaceSelector<
nf7::Memento, nf7::Sequencer>(t).Select(this, &mem_);
}
private:
nf7::Life<Call> life_;
nf7::FileHolder callee_;
nf7::gui::FileHolderEditor callee_editor_;
nf7::GenericMemento<Data> mem_;
Data& data() noexcept { return mem_.data(); }
const Data& data() const noexcept { return mem_.data(); }
};
@@ -127,7 +113,7 @@ class Call::SessionLambda final : public nf7::Node::Lambda {
assert(!ss_);
ss_ = ss;
const auto ex = f.data().expects;
const auto ex = f.mem_->expects;
size_t begin = 0;
for (size_t i = 0; i <= ex.size(); ++i) {
if (i == ex.size() || ex[i] == '\n') {
@@ -140,12 +126,10 @@ class Call::SessionLambda final : public nf7::Node::Lambda {
}
FinishIf();
}
void Handle(std::string_view name, const nf7::Value& val,
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override {
void Handle(const nf7::Node::Lambda::Msg& in) noexcept override {
if (!ss_) return;
ss_->Send(name, nf7::Value {val});
expects_.erase(std::string {name});
ss_->Send(in.name, nf7::Value {in.value});
expects_.erase(in.name);
FinishIf();
}
void Abort() noexcept override {
@@ -177,8 +161,8 @@ try {
if (abort_) return;
file_.EnforceAlive();
auto& data = file_->data();
auto& callee = file_->callee_.GetFileOrThrow();
auto& data = file_->mem_.data();
auto& callee = file_->ResolveOrThrow(data.callee);
auto& node = callee.interfaceOrThrow<nf7::Node>();
if (!ssla_) {
@@ -191,7 +175,8 @@ try {
}
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)) {
la_->Handle(name, *v, ssla_);
}
@@ -201,11 +186,7 @@ try {
ssla_ = nullptr;
la_ = nullptr;
}
} catch (nf7::LifeExpiredException&) {
ss->Finish();
} catch (nf7::FileHolder::EmptyException&) {
ss->Finish();
} catch (nf7::File::NotImplementedException&) {
} catch (nf7::Exception&) {
ss->Finish();
}
void Call::Lambda::Abort() noexcept {
@@ -221,30 +202,35 @@ void Call::Lambda::Abort() 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 {
const auto em = ImGui::GetFontSize();
bool commit = false;
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()) {
mem_.Commit();
commit = true;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("session ends right after receiving these outputs");
}
if (ImGui::Checkbox("pure", &data().pure)) {
mem_.Commit();
if (ImGui::Checkbox("pure", &mem_->pure)) {
commit = true;
}
if (ImGui::IsItemHovered()) {
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 {

View File

@@ -25,12 +25,11 @@
#include "common/file_base.hh"
#include "common/generic_context.hh"
#include "common/generic_type_info.hh"
#include "common/gui_context.hh"
#include "common/gui_file.hh"
#include "common/gui_popup.hh"
#include "common/gui.hh"
#include "common/gui_timeline.hh"
#include "common/gui_window.hh"
#include "common/life.hh"
#include "common/logger_ref.hh"
#include "common/memento.hh"
#include "common/memento_recorder.hh"
#include "common/node.hh"
@@ -48,11 +47,8 @@ namespace {
class TL final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
public:
static inline const nf7::GenericTypeInfo<TL> kType = {
"Sequencer/Timeline", {"nf7::DirItem"}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("Timeline data");
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Node");
}
"Sequencer/Timeline", {"nf7::DirItem"},
};
struct Timing;
@@ -63,60 +59,41 @@ class TL final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
class Session;
class Lambda;
class ConfigModifyCommand;
using ItemId = uint64_t;
TL(nf7::Env& env,
std::vector<std::unique_ptr<Layer>>&& layers = {},
ItemId next = 1,
const nf7::gui::Window* win = nullptr) noexcept :
nf7::FileBase(kType, env, {&popup_socket_, &popup_add_item_}),
nf7::DirItem(nf7::DirItem::kMenu | nf7::DirItem::kWidget),
nf7::Node(nf7::Node::kMenu_DirItem),
life_(*this),
ItemId next = 1) noexcept :
nf7::FileBase(kType, env),
nf7::DirItem(nf7::DirItem::kMenu),
nf7::Node(nf7::Node::kNone),
life_(*this), log_(*this),
layers_(std::move(layers)), next_(next),
win_(*this, "Timeline Editor", win), tl_("timeline"),
popup_add_item_(*this) {
ApplySeqSocketChanges();
popup_socket_.onSubmit = [this](auto&& i, auto&& o) {
ExecChangeSeqSocket(std::move(i), std::move(o));
};
win_(*this, "Timeline Editor"), tl_("timeline") {
win_.onUpdate = [this]() { TimelineEditor(); };
}
~TL() noexcept {
history_.Clear();
}
TL(nf7::Deserializer& ar) : TL(ar.env()) {
ar(seq_inputs_, seq_outputs_, win_, tl_, layers_);
ar(win_, tl_, layers_);
AssignId();
ApplySeqSocketChanges();
}
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::shared_ptr<nf7::Node::Lambda> CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
std::span<const std::string> GetInputs() const noexcept override {
return inputs_;
}
std::span<const std::string> GetOutputs() const noexcept override {
return outputs_;
nf7::Node::Meta GetMeta() const noexcept override {
return {{"exec"}, {"result"}};
}
void Handle(const nf7::File::Event& ev) noexcept;
void Update() noexcept override;
void PostHandle(const nf7::File::Event& ev) noexcept;
void PostUpdate() 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 {
return nf7::InterfaceSelector<nf7::DirItem, nf7::Node>(t).Select(this);
@@ -124,14 +101,13 @@ class TL final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
private:
nf7::Life<TL> life_;
nf7::LoggerRef log_;
nf7::SquashedHistory history_;
std::shared_ptr<TL::Lambda> lambda_;
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_layer_;
@@ -139,44 +115,12 @@ class TL final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
uint64_t cursor_ = 0;
std::vector<std::unique_ptr<Layer>> layers_;
std::vector<std::string> seq_inputs_;
std::vector<std::string> seq_outputs_;
ItemId next_;
nf7::gui::Window win_;
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
bool param_panel_request_focus_ = false;
TL::Item* param_panel_target_ = nullptr;
@@ -204,7 +148,7 @@ class TL final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
void ExecApplyLayerOfSelected() noexcept;
void MoveDisplayLayerOfSelected(int64_t diff) noexcept;
// history
// history operation
void ExecUnDo() noexcept {
env().ExecMain(
std::make_shared<nf7::GenericContext>(*this, "reverting commands to undo"),
@@ -216,19 +160,17 @@ class TL final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
[this]() { history_.ReDo(); });
}
// instant running
void MoveCursorTo(uint64_t t) noexcept;
void AttachLambda(const std::shared_ptr<TL::Lambda>&) noexcept;
// socket operation
void ExecChangeSeqSocket(std::vector<std::string>&&, std::vector<std::string>&&) noexcept;
void ApplySeqSocketChanges() noexcept {
inputs_ = seq_inputs_;
inputs_.push_back("_exec");
// gui
void TimelineEditor() noexcept;
void ParamPanel() noexcept;
void LambdaSelector() noexcept;
void ItemAdder() noexcept;
outputs_ = seq_outputs_;
}
void HandleTimelineAction() noexcept;
};
@@ -419,6 +361,14 @@ class TL::Layer final {
return std::make_unique<TL::Layer>(std::move(items), enabled_, height_);
}
// MoveItem don't update Item::layer() neither Item::displayLayer()
void MoveItemTo(TL::Item& item, TL::Layer& dst) noexcept {
dst.AddItem(RemoveItem(item));
}
void ReorderItem(TL::Item& item) noexcept {
AddItem(RemoveItem(item));
}
void Attach(TL& f, TL::Layer* prev, TL::Layer* next) noexcept {
assert(!owner_);
@@ -436,19 +386,6 @@ class TL::Layer final {
next_ = nullptr;
}
// Even after this, the item refers previous layer.
// To replace to new one, call item.MoveTo().
void MoveItemTo(TL::Layer& target, TL::Item& item) noexcept {
auto itr = std::find_if(items_.begin(), items_.end(),
[&](auto& x) { return x.get() == &item; });
if (itr == items_.end()) return;
auto uptr = std::move(*itr);
items_.erase(itr);
target.items_.push_back(std::move(uptr));
}
TL::Item* GetAt(uint64_t t) const noexcept {
auto itr = std::find_if(
items_.begin(), items_.end(),
@@ -542,6 +479,26 @@ class TL::Layer final {
// GUI temporary parameters
size_t index_;
float offset_y_;
// Add/RemoveItem don't update item.layer() field.
// Use ItemSwapCommand or Item::MoveTo() to do it.
void AddItem(std::unique_ptr<TL::Item>&& item) noexcept {
const auto border = item->timing().end();
auto itr = std::find_if(items_.begin(), items_.end(),
[&](auto& x) { return border <= x->timing().begin(); });
items_.insert(itr, std::move(item));
}
std::unique_ptr<TL::Item> RemoveItem(TL::Item& item) noexcept {
auto itr = std::find_if(items_.begin(), items_.end(),
[&](auto& x) { return x.get() == &item; });
if (itr == items_.end()) {
return nullptr;
}
auto uptr = std::move(*itr);
items_.erase(itr);
return uptr;
}
};
void TL::AssignId() {
next_ = 1;
@@ -569,8 +526,7 @@ class TL::Lambda final : public Node::Lambda,
Node::Lambda(f, parent), owner_(f.life_) {
}
void Handle(std::string_view, const nf7::Value&,
const std::shared_ptr<Node::Lambda>&) noexcept override;
void Handle(const nf7::Node::Lambda::Msg& in) noexcept override;
std::shared_ptr<TL::Session> CreateSession(uint64_t t) noexcept {
if (depth() != 0 && owner_ && owner_->lambda_.get() == this) {
@@ -617,11 +573,11 @@ class TL::Lambda final : public Node::Lambda,
auto caller = parent();
if (!caller) return;
for (const auto& name : owner_->seq_outputs_) {
auto itr = vars.find(name);
if (itr == vars.end()) continue;
caller->Handle(name, itr->second, shared_from_this());
std::vector<nf7::Value::TuplePair> tup;
for (auto& p : vars) {
tup.emplace_back(p.first, p.second);
}
caller->Handle("result", nf7::Value {std::move(tup)}, shared_from_this());
}
void Abort() noexcept {
@@ -765,21 +721,22 @@ class TL::Session final : public Sequencer::Session,
static_cast<nf7::Value::Scalar>(t.dur());
}
};
void TL::Lambda::Handle(std::string_view name, const nf7::Value& v,
const std::shared_ptr<Node::Lambda>&) noexcept {
if (name == "_exec") {
void TL::Lambda::Handle(const nf7::Node::Lambda::Msg& in) noexcept {
if (in.name == "_exec") {
if (!owner_) return;
uint64_t t;
if (v.isInteger()) {
const auto ti = std::max(v.integer(), int64_t{0});
if (in.value.isInteger()) {
const auto ti = std::max(in.value.integer(), int64_t{0});
t = static_cast<uint64_t>(ti);
} else {
// TODO: error
owner_->log_.Error("_exec takes a frame index");
return;
}
CreateSession(t)->StartNext();
} else {
vars_[std::string {name}] = v;
vars_[std::string {in.name}] = in.value;
}
}
void TL::MoveCursorTo(uint64_t time) noexcept {
@@ -805,7 +762,6 @@ class TL::Editor final : public nf7::Sequencer::Editor {
public:
Editor(TL::Item& item) noexcept : item_(&item) {
}
// TODO
private:
TL::Item* const item_;
@@ -936,28 +892,15 @@ class TL::Layer::ItemSwapCommand final : public nf7::History::Command {
TL::Item* const ptr_;
void Swap() {
auto& items = layer_->items_;
if (item_) {
const auto& t = item_->timing();
auto itr = std::find_if(
items.begin(), items.end(),
[t = t.begin()](auto& x) { return t <= x->timing().begin(); });
if (itr != items.end()) {
if (t.end() > (*itr)->timing().begin()) {
throw nf7::History::CorruptException {"timing overlap"};
}
}
item_->Attach(*layer_->owner_, *layer_);
items.insert(itr, std::move(item_));
layer_->AddItem(std::move(item_));
} else {
auto itr = std::find_if(items.begin(), items.end(),
[ptr = ptr_](auto& x) { return x.get() == ptr; });
if (itr == items.end()) {
item_ = layer_->RemoveItem(*ptr_);
if (!item_) {
throw nf7::History::CorruptException {"target item missing"};
}
item_ = std::move(*itr);
item_->Detach();
items.erase(itr);
}
}
};
@@ -983,8 +926,7 @@ class TL::Layer::ItemTimingSwapCommand final : public nf7::History::Command {
void Exec() noexcept {
std::swap(item_->timing(), timing_);
item_->displayTiming() = item_->timing();
// TODO: reorder item
item_->layer().ReorderItem(*item_);
}
};
void TL::ExecApplyTimingOfSelected() noexcept {
@@ -1070,11 +1012,11 @@ class TL::Layer::ItemMoveCommand final : public nf7::History::Command {
src_(&src), dst_(&dst), item_(&item) {
}
void Apply() noexcept override {
src_->MoveItemTo(*dst_, *item_);
dst_->AddItem(src_->RemoveItem(*item_));
item_->MoveTo(*dst_);
}
void Revert() noexcept override {
dst_->MoveItemTo(*src_, *item_);
src_->AddItem(dst_->RemoveItem(*item_));
item_->MoveTo(*src_);
}
@@ -1124,81 +1066,25 @@ void TL::MoveDisplayLayerOfSelected(int64_t diff) noexcept {
layers.emplace_back(item, &layer);
}
for (auto& p : layers) {
p.first->displayLayer().MoveItemTo(*p.second, *p.first);
p.first->DisplayOn(*p.second);
auto& item = *p.first;
auto& src = item.displayLayer();
auto& dst = *p.second;
src.MoveItemTo(item, dst);
item.DisplayOn(dst);
}
}
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::vector<std::unique_ptr<TL::Layer>> layers;
layers.reserve(layers_.size());
ItemId next = 1;
for (const auto& layer : layers_) layers.push_back(layer->Clone(env, next));
return std::make_unique<TL>(env, std::move(layers), next, &win_);
for (const auto& layer : layers_) {
layers.push_back(layer->Clone(env, next));
}
return std::make_unique<TL>(env, std::move(layers), next);
}
void TL::Handle(const Event& ev) noexcept {
nf7::FileBase::Handle(ev);
void TL::PostHandle(const Event& ev) noexcept {
switch (ev.type) {
case Event::kAdd:
if (layers_.size() == 0) {
@@ -1228,51 +1114,42 @@ void TL::Handle(const Event& ev) noexcept {
}
}
void TL::Update() noexcept {
nf7::FileBase::Update();
void TL::PostUpdate() noexcept {
// 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& item : layer->items()) {
item->file().Update();
}
}
UpdateEditorWindow();
UpdateParamPanelWindow();
// squash queued commands
if (history_.Squash()) {
env().ExecMain(std::make_shared<nf7::GenericContext>(*this),
[this]() { Touch(); });
}
}
void TL::UpdateMenu() noexcept {
if (ImGui::MenuItem("editor", nullptr, &win_.shown()) && win_.shown()) {
win_.SetFocus();
}
if (ImGui::MenuItem("I/O list")) {
popup_socket_.Open(seq_inputs_, seq_outputs_);
}
win_.MenuItem();
}
void TL::UpdateWidget() noexcept {
ImGui::TextUnformatted("Sequencer/Timeline");
if (ImGui::Button("Editor")) {
win_.SetFocus();
}
if (ImGui::Button("I/O list")) {
popup_socket_.Open(seq_inputs_, seq_outputs_);
}
popup_socket_.Update();
}
void TL::UpdateEditorWindow() noexcept {
if (win_.shownInCurrentFrame()) {
const auto em = ImGui::GetFontSize();
ImGui::SetNextWindowSizeConstraints({32*em, 16*em}, {1e8, 1e8});
}
if (win_.Begin()) {
UpdateLambdaSelector();
void TL::TimelineEditor() noexcept {
LambdaSelector();
// timeline
if (tl_.Begin()) {
@@ -1295,9 +1172,10 @@ void TL::UpdateEditorWindow() noexcept {
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 (ImGui::BeginMenu("add new item")) {
ItemAdder();
ImGui::EndMenu();
}
}
if (selected_.size()) {
@@ -1313,10 +1191,6 @@ void TL::UpdateEditorWindow() noexcept {
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();
}
@@ -1397,15 +1271,25 @@ void TL::UpdateEditorWindow() noexcept {
}
}
}
}
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 =
lambda_? nf7::gui::GetParentContextDisplayName(*lambda_): "(unselected)";
if (ImGui::BeginCombo("##lambda", current_lambda.c_str())) {
if (lambda_) {
if (ImGui::Selectable("detach from current lambda")) {
if (ImGui::Selectable("detach current lambda")) {
AttachLambda(nullptr);
}
ImGui::Separator();
@@ -1430,6 +1314,84 @@ void TL::UpdateLambdaSelector() noexcept {
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 {
auto item = reinterpret_cast<TL::Item*>(tl_.actionTarget());
const auto action_time = tl_.actionTime();
@@ -1492,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 {
index_ = idx;
@@ -1569,6 +1505,7 @@ void TL::Layer::UpdateHeader(size_t idx) noexcept {
}
}
}
void TL::Item::Update() noexcept {
assert(owner_);
assert(layer_);
@@ -1584,6 +1521,8 @@ void TL::Item::Update() noexcept {
if (ImGui::MenuItem("remove")) {
layer_->ExecRemoveItem(*this);
}
nf7::gui::FileMenuItems(*file_);
if (seq_->flags() & nf7::Sequencer::kMenu) {
ImGui::Separator();
seq_->UpdateMenu(ed);
@@ -1610,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

View File

@@ -1,129 +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(std::string_view name, const nf7::Value& v,
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override {
if (name == "save") {
env().ExecMain(shared_from_this(), [this]() {
env().Save();
});
} else if (name == "exit") {
env().Exit();
} else if (name == "abort") {
std::abort();
} else if (name == "panic") {
try {
if (v.isString()) {
throw nf7::Exception {v.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_view>
#include <unordered_set>
#include <vector>
#include <imgui.h>
#include <imgui_internal.h>
@@ -11,16 +12,19 @@
#include <yas/types/std/unordered_set.hpp>
#include <yas/types/std/string.hpp>
#include <tracy/Tracy.hpp>
#include "nf7.hh"
#include "common/config.hh"
#include "common/dir.hh"
#include "common/dir_item.hh"
#include "common/file_base.hh"
#include "common/generic_context.hh"
#include "common/generic_dir.hh"
#include "common/generic_type_info.hh"
#include "common/gui.hh"
#include "common/gui_dnd.hh"
#include "common/gui_file.hh"
#include "common/gui_popup.hh"
#include "common/gui_window.hh"
#include "common/ptr_selector.hh"
#include "common/yas_nf7.hh"
@@ -30,240 +34,96 @@ namespace nf7 {
namespace {
class Dir final : public nf7::FileBase,
public nf7::Dir,
public nf7::DirItem {
public:
static inline const GenericTypeInfo<Dir> kType = {"System/Dir", {"nf7::DirItem"}};
static constexpr const char* kTypeDescription = "generic directory";
static inline const nf7::GenericTypeInfo<Dir> kType = {
"System/Dir", {"nf7::DirItem"}, "generic directory",
};
using ItemMap = std::map<std::string, std::unique_ptr<File>>;
Dir(nf7::Env& env, ItemMap&& items = {}, const gui::Window* src = nullptr) noexcept :
nf7::FileBase(kType, env, {&widget_popup_, &add_popup_, &rename_popup_}),
Dir(nf7::Env& env, nf7::GenericDir::ItemMap&& items = {}) noexcept :
nf7::FileBase(kType, env),
nf7::DirItem(nf7::DirItem::kTree |
nf7::DirItem::kMenu |
nf7::DirItem::kTooltip |
nf7::DirItem::kDragDropTarget),
items_(std::move(items)), win_(*this, "TreeView System/Dir", src),
widget_popup_(*this), add_popup_(*this), rename_popup_(*this) {
dir_(*this, std::move(items)), win_(*this, "Tree View") {
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()) {
ar(opened_, win_);
uint64_t size;
ar(size);
for (size_t i = 0; i < size; ++i) {
std::string name;
ar(name);
std::unique_ptr<nf7::File> f;
try {
ar(f);
items_[name] = std::move(f);
} catch (nf7::Exception&) {
env().Throw(std::current_exception());
}
}
ar(dir_, opened_, win_);
}
void Serialize(nf7::Serializer& ar) const noexcept override {
ar(opened_, win_);
ar(static_cast<uint64_t>(items_.size()));
for (auto& p : items_) {
ar(p.first, p.second);
}
ar(dir_, opened_, win_);
}
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
ItemMap items;
for (const auto& item : items_) {
items[item.first] = item.second->Clone(env);
}
return std::make_unique<Dir>(env, std::move(items));
return std::make_unique<Dir>(env, dir_.CloneItems(env));
}
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 UpdateMenu() noexcept override;
void UpdateTooltip() noexcept override;
void UpdateDragDropTarget() noexcept override;
void Handle(const Event& ev) noexcept override {
nf7::FileBase::Handle(ev);
void PostHandle(const Event& ev) noexcept override {
switch (ev.type) {
case Event::kAdd:
// force to show window if this is the root
if (name() == "$") {
win_.shown() = true;
win_.Show();
}
for (const auto& item : items_) item.second->MoveUnder(*this, item.first);
break;
case Event::kRemove:
for (const auto& item : items_) item.second->Isolate();
break;
case Event::kReqFocus:
win_.SetFocus();
break;
return;
default:
break;
return;
}
}
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:
// persistent params
ItemMap items_;
nf7::GenericDir dir_;
std::unordered_set<std::string> opened_;
gui::Window win_;
std::unordered_set<std::string> opened_;
std::vector<std::pair<std::string, std::unique_ptr<nf7::File>>> trash_;
// GUI popup
class WidgetPopup final :
public nf7::FileBase::Feature, private nf7::gui::Popup {
public:
WidgetPopup(Dir& owner) noexcept :
nf7::gui::Popup("WidgetPopup"), owner_(&owner) {
static bool TestFlags(nf7::File& f, nf7::DirItem::Flags flags) noexcept
try {
return f.interfaceOrThrow<nf7::DirItem>().flags() & flags;
} catch (nf7::Exception&) {
return false;
}
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 {
for (const auto& item : items_) {
ImGuiTreeNodeFlags flags =
ImGuiTreeNodeFlags_NoTreePushOnOpen |
ImGuiTreeNodeFlags_SpanFullWidth;
for (const auto& item : dir_.items()) {
const auto& name = item.first;
auto& file = *item.second;
ImGui::PushID(&file);
auto* ditem = file.interface<nf7::DirItem>();
if (ditem && !(ditem->flags() & DirItem::kTree)) {
flags |= ImGuiTreeNodeFlags_Leaf;
const auto flags = ditem? ditem->flags(): 0;
ImGuiTreeNodeFlags node_flags =
ImGuiTreeNodeFlags_NoTreePushOnOpen |
ImGuiTreeNodeFlags_SpanFullWidth;
if (!(flags & DirItem::kTree)) {
node_flags |= ImGuiTreeNodeFlags_Leaf;
}
const bool opened = opened_.contains(name);
@@ -272,72 +132,58 @@ void Dir::UpdateTree() noexcept {
}
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) {
opened_.insert(name);
} else if (opened && !open) {
opened_.erase(name);
}
if (ditem && (ditem->flags() & DirItem::kWidget)) {
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
widget_popup_.Open(file);
}
}
// tooltip
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::TextUnformatted(file.type().name().c_str());
ImGui::SameLine();
ImGui::TextDisabled(file.abspath().Stringify().c_str());
if (ditem && (ditem->flags() & DirItem::kTooltip)) {
ImGui::Indent();
ditem->UpdateTooltip();
ImGui::Unindent();
}
nf7::gui::FileTooltip(file);
ImGui::EndTooltip();
}
// send nf7::File::Event::kReqFocus on double click
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
file.RequestFocus();
}
// context menu
if (ImGui::BeginPopupContextItem()) {
if (ditem && (ditem->flags() & DirItem::kWidget)) {
if (ImGui::MenuItem("open widget")) {
widget_popup_.Open(file);
}
}
if (ImGui::MenuItem("copy path")) {
ImGui::SetClipboardText(file.abspath().Stringify().c_str());
}
ImGui::Separator();
ImGui::BeginDisabled(flags & nf7::DirItem::kImportant);
if (ImGui::MenuItem("remove")) {
env().ExecMain(
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")) {
rename_popup_.Open(name);
if (ImGui::BeginMenu("rename")) {
ItemRenamer(name);
ImGui::EndMenu();
}
if (ImGui::MenuItem("renew")) {
env().ExecMain(
std::make_shared<nf7::GenericContext>(*this, "removing item"),
[this, name]() { Add(name, Remove(name)); });
std::make_shared<nf7::GenericContext>(*this, "renewing item"),
[this, name]() { dir_.Renew(name); });
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("re-initialize the item by re-adding after removing");
}
ImGui::Separator();
if (ImGui::MenuItem("add new sibling")) {
add_popup_.Open();
if (ImGui::MenuItem("clone")) {
env().ExecMain(
std::make_shared<nf7::GenericContext>(*this, "duplicating item"),
[this, name, &file]() { dir_.Add(dir_.GetUniqueName(name), file.Clone(env())); });
}
ImGui::EndDisabled();
if (ditem && (ditem->flags() & DirItem::kMenu)) {
ImGui::Separator();
ditem->UpdateMenu();
}
nf7::gui::FileMenuItems(file);
ImGui::EndPopup();
}
@@ -353,7 +199,7 @@ void Dir::UpdateTree() noexcept {
// displayed contents
if (open) {
ImGui::TreePush(&file);
if (ditem && (ditem->flags() & DirItem::kTree)) {
if (flags & DirItem::kTree) {
ditem->UpdateTree();
}
ImGui::TreePop();
@@ -362,7 +208,7 @@ void Dir::UpdateTree() noexcept {
// dnd target
if (nf7::gui::dnd::IsFirstAccept()) {
if (ditem && (ditem->flags() & DirItem::kDragDropTarget)) {
if (flags & DirItem::kDragDropTarget) {
ImGui::SetCursorPosY(top);
ImGui::Dummy({ImGui::GetContentRegionAvail().x, bottom-top});
if (ImGui::BeginDragDropTarget()) {
@@ -377,21 +223,50 @@ void Dir::UpdateTree() noexcept {
}
}
void Dir::UpdateMenu() noexcept {
if (ImGui::MenuItem("add new child")) {
add_popup_.Open();
if (ImGui::BeginMenu("add new child")) {
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::MenuItem("TreeView", nullptr, &win_.shown());
win_.MenuItem();
}
void Dir::UpdateTooltip() noexcept {
ImGui::Text("children: %zu", items_.size());
ImGui::Text("children: %zu", dir_.items().size());
}
void Dir::UpdateDragDropTarget() noexcept
try {
nf7::File::Path p;
if (auto pay = gui::dnd::Peek<Path>(gui::dnd::kFilePath, 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;
}
@@ -401,94 +276,169 @@ try {
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();
if (pay->IsDelivery()) {
env().ExecMain(
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&) {
}
void Dir::WidgetPopup::Update() noexcept {
if (nf7::gui::Popup::Begin()) {
if (auto item = target_->interface<nf7::DirItem>()) {
ImGui::PushID(item);
item->UpdateWidget();
ImGui::PopID();
}
void Dir::TreeView() noexcept {
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();
}
}
}
void Dir::AddPopup::Update() noexcept {
if (nf7::gui::Popup::Begin()) {
void Dir::ItemAdder() noexcept {
static const nf7::File::TypeInfo* type;
static std::string name;
static bool type_filtered;
if (ImGui::IsWindowAppearing()) {
type = nullptr;
name = dir_.GetUniqueName("new_file");
type_filtered = true;
}
ImGui::TextUnformatted("System/Dir: adding new file...");
if (factory_.Update()) {
ImGui::CloseCurrentPopup();
auto& env = owner_->env();
auto ctx = std::make_shared<nf7::GenericContext>(*owner_, "adding new item");
auto task = [this, &env]() { owner_->Add(factory_.name(), factory_.Create(env)); };
env.ExecMain(ctx, std::move(task));
}
ImGui::EndPopup();
}
}
void Dir::RenamePopup::Update() noexcept {
if (nf7::gui::Popup::Begin()) {
ImGui::TextUnformatted("System/Dir: renaming an exsting item...");
ImGui::InputText("before", &before_);
const auto em = ImGui::GetFontSize();
bool submit = false;
if (ImGui::IsWindowAppearing()) ImGui::SetKeyboardFocusHere();
if (ImGui::InputText("after", &after_, ImGuiInputTextFlags_EnterReturnsTrue)) {
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;
}
bool err = false;
if (!owner_->Find(before_)) {
ImGui::Bullet(); ImGui::TextUnformatted("before is invalid: missing target");
err = true;
}
if (owner_->Find(after_)) {
ImGui::Bullet(); ImGui::TextUnformatted("after is invalid: duplicated name");
err = true;
}
try {
Path::ValidateTerm(after_);
} catch (Exception& e) {
ImGui::Bullet(); ImGui::Text("after is invalid: %s", e.msg().c_str());
err = true;
}
if (!err) {
if (ImGui::Button("ok")) {
submit = true;
constexpr auto kFlags =
ImGuiSelectableFlags_SpanAllColumns |
ImGuiSelectableFlags_AllowItemOverlap;
if (ImGui::Selectable(t.name().c_str(), type == &t, kFlags)) {
type = &t;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"rename '%s' to '%s' on '%s'",
before_.c_str(), after_.c_str(),
owner_->abspath().Stringify().c_str());
ImGui::BeginTooltip();
t.UpdateTooltip();
ImGui::EndTooltip();
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
exec = true;
}
}
}
if (submit) {
if (type_filtered) {
ImGui::Selectable("(show all types)");
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::TextUnformatted("double click to allow you to place system files");
ImGui::TextDisabled(" -- great power brings DESTRUCTION and CREATION");
ImGui::EndTooltip();
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
type_filtered = false;
}
}
}
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())); });
}
}
auto ctx = std::make_shared<nf7::GenericContext>(*owner_, "renaming item");
auto task = [this, before = std::move(before_), after = std::move(after_)]() {
auto f = owner_->Remove(before);
if (!f) throw nf7::Exception {"missing target"};
owner_->Add(after, std::move(f));
};
owner_->env().ExecMain(ctx, std::move(task));
void Dir::ItemRenamer(const std::string& name) noexcept {
static std::string editing_name;
static std::string err;
if (ImGui::IsWindowAppearing()) {
editing_name = name;
err = "";
}
ImGui::EndPopup();
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 <memory>
#include <optional>
#include <string>
#include <string_view>
#include <unordered_map>
@@ -8,113 +9,170 @@
#include <imgui.h>
#include <yaml-cpp/yaml.h>
#include <yas/serialize.hpp>
#include <yas/types/std/vector.hpp>
#include "nf7.hh"
#include "common/dir_item.hh"
#include "common/file_base.hh"
#include "common/file_holder.hh"
#include "common/gui_file.hh"
#include "common/generic_config.hh"
#include "common/generic_context.hh"
#include "common/generic_memento.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_ref.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
#include "common/value.hh"
#include "common/yaml_nf7.hh"
#include "common/yas_nf7.hh"
namespace nf7 {
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:
static inline const nf7::GenericTypeInfo<Event> kType = {
"System/Event", {"nf7::DirItem"}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("Records log output from other files.");
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Node");
}
class Lambda;
struct Data final {
nf7::FileHolder::Tag handler;
struct Data {
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 :
nf7::FileBase(kType, env, {&logger_, &handler_, &handler_editor_}),
nf7::DirItem(nf7::DirItem::kMenu | nf7::DirItem::kWidget),
nf7::Node(nf7::Node::kMenu_DirItem),
life_(*this), logger_(*this),
handler_(*this, "handler", mem_),
handler_editor_(handler_,
[](auto& t) { return t.flags().contains("nf7::Node"); }),
Event(nf7::Env& env, Data&& d = {}) noexcept :
nf7::FileBase(kType, env),
nf7::GenericConfig(mem_),
nf7::DirItem(nf7::DirItem::kMenu |
nf7::DirItem::kTooltip),
log_(*this),
la_root_(std::make_shared<nf7::Node::Lambda>(*this)),
mem_(std::move(data)) {
handler_.onEmplace = [this]() { la_ = nullptr; };
mem_(*this, std::move(d)) {
mem_.onCommit = [this]() { SetUpWatcher(); };
}
Event(nf7::Deserializer& ar) : Event(ar.env()) {
ar(handler_);
ar(mem_.data());
}
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 {
return std::make_unique<Event>(env, Data {data()});
return std::make_unique<Event>(env, Data {mem_.data()});
}
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
std::span<const std::string> GetInputs() const noexcept override {
static const std::vector<std::string> kInputs = {"value"};
return kInputs;
void PostHandle(const nf7::File::Event& e) noexcept override {
switch (e.type) {
case nf7::File::Event::kAdd:
if (mem_->init) {
env().ExecMain(
std::make_shared<nf7::GenericContext>(*this, "trigger init event"),
[this]() {
if (auto la = CreateLambdaIf()) {
la->Handle("init", nf7::Value::Pulse {}, la_root_);
}
});
}
return;
default:
return;
}
std::span<const std::string> GetOutputs() const noexcept override {
return {};
}
void Update() noexcept override;
void PostUpdate() 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 {
return nf7::InterfaceSelector<nf7::DirItem, nf7::Node>(t).Select(this);
return nf7::InterfaceSelector<
nf7::Config, nf7::DirItem, nf7::Node>(t).Select(this);
}
private:
nf7::Life<Event> life_;
nf7::LoggerRef logger_;
nf7::FileHolder handler_;
nf7::gui::FileHolderEditor handler_editor_;
nf7::LoggerRef log_;
std::shared_ptr<nf7::Node::Lambda> la_root_;
std::shared_ptr<nf7::Node::Lambda> la_;
nf7::GenericMemento<Data> mem_;
Data& data() noexcept { return mem_.data(); }
const Data& data() const noexcept { return mem_.data(); }
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
try {
return handler_.GetFileOrThrow().interfaceOrThrow<nf7::Node>().GetInputs();
} catch (nf7::Exception&) {
return {};
nf7::Node& GetHandler() const {
return ResolveOrThrow(mem_->handler).interfaceOrThrow<nf7::Node>();
}
std::shared_ptr<nf7::Node::Lambda> CreateLambdaIf() noexcept {
try {
if (!la_) {
auto& n = handler_.GetFileOrThrow().interfaceOrThrow<nf7::Node>();
la_ = n.CreateLambda(la_root_);
la_ = GetHandler().CreateLambda(la_root_);
}
return la_;
} 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;
return nullptr;
}
@@ -128,44 +186,45 @@ class Event final : public nf7::FileBase, public nf7::DirItem, public nf7::Node
}}, la_root_);
}
}
void TriggerCustomEvent(const nf7::Value& v) noexcept {
void TriggerWatch(const nf7::File::Event& e) noexcept {
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_);
}
}
};
class Event::Lambda final : public nf7::Node::Lambda {
public:
Lambda(Event& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept :
nf7::Node::Lambda(f, parent), f_(f.life_) {
}
void Handle(std::string_view, const nf7::Value& v,
const std::shared_ptr<nf7::Node::Lambda>&) noexcept
void SetUpWatcher() noexcept {
watch_.emplace(*this);
for (const auto& p : mem_->watch)
try {
f_.EnforceAlive();
f_->TriggerCustomEvent(v);
} catch (nf7::Exception&) {
watch_->Watch(ResolveOrThrow(p).id());
} catch (nf7::File::NotFoundException&) {
}
}
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();
void Event::PostUpdate() noexcept {
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) {
const auto& key = io.KeysData[i];
const char* event = nullptr;
@@ -180,18 +239,32 @@ void Event::Update() noexcept {
}
}
}
if (mem_->mouse) {
// TODO
}
}
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;
}
}
void Event::UpdateWidget() noexcept {
ImGui::TextUnformatted("System/Event");
handler_editor_.ButtonWithLabel("handler");
handler_editor_.ItemWidget("handler");
handler_editor_.Update();
void Event::UpdateTooltip() noexcept {
ImGui::Text("handler: %s", mem_->handler.Stringify().c_str());
ImGui::Text("events :");
if (mem_->init) {
ImGui::Bullet(); ImGui::TextUnformatted("init");
}
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

View File

@@ -1,21 +1,36 @@
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstdlib>
#include <memory>
#include <string>
#include <string_view>
#include <typeinfo>
#include <utility>
#include <vector>
#include <imgui.h>
#include <imgui_internal.h>
#include <yaml-cpp/yaml.h>
#include <yas/serialize.hpp>
#include <yas/types/std/string.hpp>
#include <yas/types/std/string_view.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/gui_window.hh"
#include "common/ptr_selector.hh"
#include "common/util_algorithm.hh"
using namespace std::literals;
@@ -24,16 +39,56 @@ using namespace std::literals;
namespace nf7 {
namespace {
class ImGui_ final : public nf7::File, public nf7::DirItem {
class ImGui_ final : public nf7::FileBase,
public nf7::GenericConfig, public nf7::DirItem {
public:
static inline const nf7::GenericTypeInfo<ImGui_> kType = {"System/ImGui", {}};
ImGui_(nf7::Env& env) noexcept :
nf7::File(kType, env), nf7::DirItem(nf7::DirItem::kNone) {
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 :
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()) {
std::string config;
ar(config);
ar(config, mem_.data());
if (config.size() > 0) {
ImGui::LoadIniSettingsFromMemory(config.data(), config.size());
@@ -42,40 +97,170 @@ class ImGui_ final : public nf7::File, public nf7::DirItem {
void Serialize(nf7::Serializer& ar) const noexcept override {
size_t 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 {
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 {
return nf7::InterfaceSelector<nf7::DirItem>(t).Select(this);
return nf7::InterfaceSelector<
nf7::Config, nf7::DirItem>(t).Select(this);
}
private:
nf7::GenericMemento<Data> mem_;
static constexpr size_t kLogoQuads = 4;
static size_t CalcLogoQuads(ImVec2 quads[kLogoQuads*4], float a) noexcept;
void DrawLogo() noexcept;
void Dockspace() noexcept;
};
void ImGui_::Update() noexcept {
constexpr auto kFlags =
ImGuiWindowFlags_NoBackground |
ImGuiWindowFlags_NoBringToFrontOnFocus |
ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoNavFocus;
const auto id = nf7::gui::Window::ConcatId(*this, "Docking Root");
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, {0, 0});
ImGui::SetNextWindowBgAlpha(0.f);
if (ImGui::Begin(id.c_str(), nullptr, kFlags)) {
const auto vp = ImGui::GetMainViewport();
ImGui::SetWindowPos({0, 0}, ImGuiCond_Always);
ImGui::SetWindowSize(vp->Size, ImGuiCond_Always);
ImGui::DockSpace(ImGui::GetID("DockSpace"), {0, 0},
ImGuiDockNodeFlags_PassthruCentralNode);
void ImGui_::PostUpdate() noexcept {
DrawLogo();
Dockspace();
}
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;
}
}
}
}
void ImGui_::DrawLogo() noexcept {
auto d = ImGui::GetBackgroundDrawList();
const auto em = ImGui::GetFontSize();
const auto sz = 6*em;
const auto pos = ImGui::GetWindowViewport()->Size / 2.f;
const auto c = ImGui::GetColorU32(ImVec4 {.9f, .9f, .9f, 1.f});
const auto t = ImGui::GetCurrentContext()->Time;
const auto a = std::min(static_cast<float>(t)/2.f, 1.f);
ImVec2 quads[kLogoQuads*4];
const auto n = CalcLogoQuads(quads, a);
for (size_t i = 0; i < n; ++i) {
d->AddQuadFilled(
quads[i*4+0]*sz + pos,
quads[i*4+1]*sz + pos,
quads[i*4+2]*sz + pos,
quads[i*4+3]*sz + pos, c);
}
}
void ImGui_::Dockspace() noexcept {
const auto em = ImGui::GetFontSize();
ImGui::DockSpaceOverViewport(ImGui::GetMainViewport(), ImGuiDockNodeFlags_PassthruCentralNode);
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);
}
if (mod) {
mem_.Commit();
}
}
size_t ImGui_::CalcLogoQuads(ImVec2 quads[kLogoQuads*4], float a) noexcept {
static const ImVec2 kVerts[kLogoQuads*4] = {
// upper horizontal
{-0.3624801619f, -0.2516071429f},
{ 0.4942659048f, -0.2516071429f},
{ 0.4438690476f, -0.1508134952f},
{-0.412876981f, -0.1508134952f},
// lower horizontal
{-0.4506746f, 0.06337304762f},
{ 0.4060714286f, 0.06337304762f},
{ 0.3556745714f, 0.1641666667f},
{-0.5010714286f, 0.1641666667f},
// left vertical
{-0.1104960286f, -0.8185714286f},
{-0.06009920952f, -0.4720932571f},
{-0.2112896857f, 0.9705159048f},
{-0.3183829333f, 0.523244f},
// right vertical
{0.1981844762f, -0.9760615076f},
{0.3115773333f, -0.5854861143f},
{0.09739085714f, 0.7374305714f},
{0.06589285714f, 0.3405555238f},
};
std::memcpy(quads, kVerts, sizeof(kVerts));
a *= 4.f;
const auto a1 = std::pow(std::clamp(a-0.f, 0.f, 1.f), 5.f);
const auto a2 = std::pow(std::clamp(a-1.f, 0.f, 1.f), 4.f);
# define Linear_(a, b, t) \
quads[i+a] = (quads[i+a]-quads[i+b])*t + quads[i+b]
// upper horizontal
size_t i = 0;
Linear_(1, 0, a1);
Linear_(2, 3, a1);
// lower horizontal
i += 4;
Linear_(0, 1, a1);
Linear_(3, 2, a1);
if (a2 <= 0) return 2;
// left vertical
i += 4;
Linear_(1, 0, std::min(a2*4.f, 1.f));
Linear_(2, 0, a2);
Linear_(3, 0, a2);
// right vertical
i += 4;
Linear_(0, 2, a2);
Linear_(1, 2, a2);
Linear_(3, 2, std::min(a2*4.f, 1.f));
return 4;
# undef Linear_
}
}

View File

@@ -10,17 +10,23 @@
#include <utility>
#include <iostream>
#include <imgui.h>
#include <yaml-cpp/yaml.h>
#include "nf7.hh"
#include "common/dir_item.hh"
#include "common/file_base.hh"
#include "common/generic_config.hh"
#include "common/generic_context.hh"
#include "common/generic_memento.hh"
#include "common/generic_type_info.hh"
#include "common/gui.hh"
#include "common/gui_window.hh"
#include "common/life.hh"
#include "common/logger.hh"
#include "common/logger_ref.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
#include "common/yas_std_atomic.hh"
@@ -30,21 +36,12 @@ using namespace std::literals;
namespace nf7 {
namespace {
class Logger final : public nf7::File,
public nf7::DirItem {
class Logger final : public nf7::FileBase,
public nf7::GenericConfig, public nf7::DirItem {
public:
static inline const nf7::GenericTypeInfo<Logger> kType = {
"System/Logger", {"nf7::DirItem"}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("Records log output from other files.");
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Logger");
ImGui::Bullet(); ImGui::TextUnformatted(
"logged are children and grandchildren of a dir that has this with name '_logger'");
ImGui::Bullet(); ImGui::TextUnformatted(
"recorded logs won't be permanentized");
}
class Node;
"System/Logger", {"nf7::DirItem"}, "records log output from other files",
};
struct Row final {
public:
@@ -66,63 +63,109 @@ class Logger final : public nf7::File,
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 :
File(kType, env), DirItem(DirItem::kMenu),
param_(std::make_shared<Param>(max_rows, propagate, freeze)),
win_(*this, "LogView") {
win_.shown() = true;
}
struct Data {
uint32_t max_rows = 1024;
bool propagate = false;
bool freeze = false;
Logger(nf7::Deserializer& ar) : Logger(ar.env()) {
ar(win_, param_->max_rows, param_->propagate, param_->freeze);
if (param_->max_rows == 0) {
Data() noexcept { }
void serialize(auto& ar) {
ar(max_rows, propagate, freeze);
if (max_rows == 0) {
throw DeserializeException("max_rows must be 1 or more");
}
}
void Serialize(nf7::Serializer& ar) const noexcept override {
ar(win_, param_->max_rows, param_->propagate, param_->freeze);
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()) {
ar(win_, mem_.data());
}
void Serialize(nf7::Serializer& ar) const noexcept override {
ar(win_, mem_.data());
}
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<Logger>(env, Data {mem_.data()});
}
void PostHandle(const nf7::File::Event& ev) noexcept override {
switch (ev.type) {
case Event::kAdd:
store_ = std::make_shared<ItemStore>(*this);
return;
default:
return;
}
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<Logger>(
env, param_->max_rows, param_->propagate, param_->freeze);
}
void Handle(const nf7::File::Event& ev) noexcept override;
void Update() noexcept override;
void UpdateMenu() noexcept override;
void UpdateRowMenu(const Row&) noexcept;
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());
}
private:
std::shared_ptr<Param> param_;
class ItemStore;
std::shared_ptr<ItemStore> store_;
std::deque<Row> rows_;
const char* popup_ = nullptr;
nf7::GenericMemento<Data> mem_;
nf7::gui::Window win_;
// log record management
void DropExceededRows() noexcept {
if (rows_.size() <= param_->max_rows) return;
rows_.erase(rows_.begin(), rows_.end()-param_->max_rows);
if (rows_.size() <= mem_->max_rows) return;
rows_.erase(rows_.begin(), rows_.end()-mem_->max_rows);
}
// stringify
std::string GetPathString(File::Id id) const noexcept
try {
return env().GetFileOrThrow(id).abspath().Stringify();
@@ -147,14 +190,17 @@ class Logger final : public nf7::File,
static std::string GetLocationString(const std::source_location loc) noexcept {
return loc.file_name()+":"s+std::to_string(loc.line());
}
};
class Logger::ItemStore final : public nf7::Context,
// gui
void LogView() noexcept;
class 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(File& f) noexcept : nf7::Context(f) {
}
ItemStore(const ItemStore&) = delete;
ItemStore(ItemStore&&) = delete;
@@ -162,28 +208,28 @@ class Logger::ItemStore final : public nf7::Context,
ItemStore& operator=(ItemStore&&) = delete;
void Write(nf7::Logger::Item&& item) noexcept override {
if (param_->freeze) return;
if (param_->propagate) {
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();
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());
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());
static_cast<intmax_t>(param_.max_rows) - std::distance(itr, items_.end());
rows.erase(rows.begin(), rows.end()-keep);
}
for (; itr < items_.end(); ++itr) {
@@ -205,144 +251,42 @@ class Logger::ItemStore final : public nf7::Context,
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:
std::mutex mtx_;
std::deque<nf7::Logger::Item> items_;
std::shared_ptr<Param> param_;
};
class Logger::Node final : public nf7::FileBase, public nf7::Node {
public:
static inline const nf7::GenericTypeInfo<Logger::Node> kType = {
"System/Logger/Node", {"nf7::Node"}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("Sends message to logger.");
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Node");
}
Node(nf7::Env& env) noexcept :
nf7::FileBase(kType, env, {&logger_}),
nf7::Node(nf7::Node::kNone),
life_(*this), logger_(*this) {
}
Node(nf7::Deserializer& ar) : Node(ar.env()) {
}
void Serialize(nf7::Serializer&) const noexcept override {
}
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<Logger::Node>(env);
}
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept override {
return std::make_shared<Logger::Node::Lambda>(*this, parent);
}
std::span<const std::string> GetInputs() const noexcept override {
static const std::vector<std::string> kInputs = {"msg"};
return kInputs;
}
std::span<const std::string> GetOutputs() const noexcept override {
return {};
}
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
return InterfaceSelector<nf7::Node>(t).Select(this);
}
private:
nf7::Life<Logger::Node> life_;
nf7::LoggerRef logger_;
class Lambda final : public nf7::Node::Lambda {
public:
Lambda(Logger::Node& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept :
nf7::Node::Lambda(f, parent), f_(f.life_) {
}
void Handle(std::string_view, const nf7::Value& v,
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override
try {
f_.EnforceAlive();
if (v.isString()) {
f_->logger_.Info(v.string());
} else {
f_->logger_.Info("["s+v.typeName()+"]");
}
} catch (nf7::Exception&) {
}
private:
nf7::Life<Logger::Node>::Ref f_;
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::UpdateMenu() noexcept {
win_.MenuItem();
}
void Logger::UpdateRowMenu(const Row& row) noexcept {
if (row.file && ImGui::MenuItem("request focus")) {
env().Handle({.id = row.file, .type = nf7::File::Event::kReqFocus,});
}
if (ImGui::MenuItem("copy as text")) {
ImGui::SetClipboardText(row.Stringify().c_str());
}
ImGui::Separator();
if (ImGui::MenuItem("clear")) {
env().ExecMain(
std::make_shared<nf7::GenericContext>(*this), [this]() { rows_.clear(); });
}
}
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()) {
void Logger::LogView() noexcept {
constexpr auto kTableFlags =
ImGuiTableFlags_Resizable |
ImGuiTableFlags_Hideable |
@@ -375,7 +319,11 @@ void Logger::Update() noexcept {
constexpr auto kFlags =
ImGuiSelectableFlags_SpanAllColumns |
ImGuiSelectableFlags_AllowItemOverlap;
ImGui::Selectable(row.level, false, kFlags);
if (ImGui::Selectable(row.level, false, kFlags)) {
if (row.file) {
env().Handle({.id = row.file, .type = nf7::File::Event::kReqFocus,});
}
}
if (ImGui::BeginPopupContextItem()) {
UpdateRowMenu(row);
ImGui::EndPopup();
@@ -383,9 +331,31 @@ void Logger::Update() noexcept {
}
// msg column
if (ImGui::TableNextColumn()) {
ImGui::TextUnformatted(row.msg.c_str());
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::SetTooltip(row.msg.c_str());
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
@@ -420,25 +390,6 @@ void Logger::Update() noexcept {
}
ImGui::EndTable();
}
}
win_.End();
}
void Logger::UpdateMenu() noexcept {
ImGui::MenuItem("shown", nullptr, &win_.shown());
if (ImGui::MenuItem("config")) {
popup_ = "ConfigPopup";
}
}
void Logger::UpdateRowMenu(const Row& row) noexcept {
if (ImGui::MenuItem("copy as text")) {
ImGui::SetClipboardText(row.Stringify().c_str());
}
ImGui::Separator();
if (ImGui::MenuItem("clear")) {
env().ExecMain(
std::make_shared<nf7::GenericContext>(*this), [this]() { rows_.clear(); });
}
}
}

View File

@@ -1,358 +0,0 @@
#include <chrono>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <string_view>
#include <typeinfo>
#include <utility>
#include <vector>
#include <imgui.h>
#include <imgui_stdlib.h>
#include <yas/serialize.hpp>
#include "nf7.hh"
#include "common/dir_item.hh"
#include "common/file_base.hh"
#include "common/generic_context.hh"
#include "common/generic_memento.hh"
#include "common/generic_type_info.hh"
#include "common/gui_popup.hh"
#include "common/gui_window.hh"
#include "common/life.hh"
#include "common/logger_ref.hh"
#include "common/native_file.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
#include "common/thread.hh"
#include "common/yas_std_filesystem.hh"
namespace nf7 {
namespace {
class NativeFile final : public nf7::FileBase,
public nf7::DirItem, public nf7::Node {
public:
static inline const nf7::GenericTypeInfo<NativeFile> kType = {
"System/NativeFile", {"nf7::DirItem", "nf7::Node"}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("Read/Write a file placed on native filesystem.");
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Node");
}
class Lambda;
struct SharedData final {
SharedData(NativeFile& f) noexcept : log(f) {
}
nf7::LoggerRef log;
std::optional<nf7::NativeFile> nfile;
std::atomic<bool> locked = false;
};
struct Runner final {
struct Task {
std::shared_ptr<NativeFile::Lambda> callee;
std::shared_ptr<nf7::Node::Lambda> caller;
std::function<nf7::Value()> func;
std::filesystem::path npath;
nf7::NativeFile::Flags flags;
std::function<void(const std::shared_ptr<SharedData>&)> preproc;
};
Runner(const std::shared_ptr<SharedData>& shared) noexcept :
shared_(shared) {
}
void operator()(Task&&) noexcept;
private:
std::shared_ptr<SharedData> shared_;
};
using Thread = nf7::Thread<Runner, Runner::Task>;
struct Data final {
std::filesystem::path npath;
std::string mode;
};
NativeFile(nf7::Env& env, Data&& data = {}) noexcept :
nf7::FileBase(kType, env, {&config_popup_}),
nf7::DirItem(nf7::DirItem::kMenu |
nf7::DirItem::kTooltip |
nf7::DirItem::kWidget),
nf7::Node(nf7::Node::kMenu_DirItem),
life_(*this),
shared_(std::make_shared<SharedData>(*this)),
th_(std::make_shared<Thread>(*this, Runner {shared_})),
mem_(std::move(data), *this),
config_popup_(*this) {
nf7::FileBase::Install(shared_->log);
mem_.onRestore = [this]() { Refresh(); };
mem_.onCommit = [this]() { Refresh(); };
}
NativeFile(nf7::Deserializer& ar) : NativeFile(ar.env()) {
ar(data().npath, data().mode);
}
void Serialize(nf7::Serializer& ar) const noexcept override {
ar(data().npath, data().mode);
}
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<NativeFile>(env, Data {data()});
}
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
std::span<const std::string> GetInputs() const noexcept override {
static const std::vector<std::string> kInputs = {"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 UpdateWidget() noexcept override;
File::Interface* interface(const std::type_info& t) noexcept override {
return InterfaceSelector<nf7::DirItem, nf7::Node>(t).Select(this);
}
private:
nf7::Life<NativeFile> life_;
std::shared_ptr<SharedData> shared_;
std::shared_ptr<Thread> th_;
std::filesystem::file_time_type lastmod_;
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(NativeFile& 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:
NativeFile* const f_;
std::string npath_;
bool read_, write_;
} config_popup_;
void Refresh() noexcept {
Runner::Task t;
t.preproc = [](auto& shared) { shared->nfile = std::nullopt; };
th_->Push(std::make_shared<nf7::GenericContext>(*this), std::move(t));
}
};
class NativeFile::Lambda final : public nf7::Node::Lambda,
public std::enable_shared_from_this<NativeFile::Lambda> {
public:
Lambda(NativeFile& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept :
nf7::Node::Lambda(f, parent), f_(f.life_), shared_(f.shared_) {
}
~Lambda() noexcept {
}
void Handle(std::string_view, const nf7::Value& v,
const std::shared_ptr<nf7::Node::Lambda>& caller) noexcept override
try {
f_.EnforceAlive();
const auto type = v.tuple("type").string();
if (type == "lock") {
Push(caller, [this]() {
Lock();
return nf7::Value::Pulse {};
});
} else if (type == "unlock") {
Push(caller, [this]() {
shared_->nfile = std::nullopt;
Unlock();
return nf7::Value::Pulse {};
});
} else if (type == "read") {
const auto offset = v.tuple("offset").integer<size_t>();
const auto size = v.tuple("size").integer<size_t>();
Push(caller, [this, offset, size]() {
std::vector<uint8_t> buf;
buf.resize(size);
const auto actual = shared_->nfile->Read(offset, buf.data(), size);
buf.resize(actual);
return nf7::Value {std::move(buf)};
});
} else if (type == "write") {
const auto offset = v.tuple("offset").integer<size_t>();
const auto buf = v.tuple("buf").vector();
Push(caller, [this, offset, buf]() {
const auto ret = shared_->nfile->Write(offset, buf->data(), buf->size());
return nf7::Value {static_cast<nf7::Value::Integer>(ret)};
});
} else if (type == "truncate") {
const auto size = v.tuple("size").integer<size_t>();
Push(caller, [this, size]() {
shared_->nfile->Truncate(size);
return nf7::Value::Pulse {};
});
} else {
throw nf7::Exception {"unknown command type: "+type};
}
} catch (nf7::Exception& e) {
shared_->log.Error(e.msg());
}
void Lock() {
if (!std::exchange(own_lock_, true)) {
if (shared_->locked.exchange(true)) {
throw nf7::Exception {"resource is busy"};
}
}
}
void Unlock() noexcept {
if (std::exchange(own_lock_, false)) {
assert(shared_->locked);
shared_->locked = false;
}
}
bool ownLock() const noexcept { return own_lock_; }
private:
nf7::Life<NativeFile>::Ref f_;
std::shared_ptr<SharedData> shared_;
bool own_lock_ = false;
void Push(const std::shared_ptr<nf7::Node::Lambda>& caller, auto&& f) noexcept {
const auto& mode = f_->data().mode;
nf7::NativeFile::Flags flags = 0;
if (std::string::npos != mode.find('r')) flags |= nf7::NativeFile::kRead;
if (std::string::npos != mode.find('w')) flags |= nf7::NativeFile::kWrite;
auto self = shared_from_this();
f_->th_->Push(self, NativeFile::Runner::Task {
.callee = self,
.caller = caller,
.func = std::move(f),
.npath = f_->data().npath,
.flags = flags,
.preproc = {},
});
}
};
std::shared_ptr<nf7::Node::Lambda> NativeFile::CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
return std::make_shared<NativeFile::Lambda>(*this, parent);
}
void NativeFile::Runner::operator()(Task&& t) noexcept
try {
if (t.preproc) {
t.preproc(shared_);
}
auto callee = t.callee;
auto caller = t.caller;
if (callee && caller) {
callee->Lock();
if (!shared_->nfile) {
shared_->nfile.emplace(callee->env(), callee->initiator(), t.npath, t.flags);
}
auto ret = t.func();
callee->env().ExecSub(callee, [callee, caller, ret = std::move(ret)]() {
caller->Handle("result", ret, callee);
});
}
} catch (nf7::Exception& e) {
shared_->log.Error("operation failure: "+e.msg());
}
void NativeFile::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 NativeFile::UpdateMenu() noexcept {
if (ImGui::MenuItem("config")) {
config_popup_.Open();
}
}
void NativeFile::UpdateTooltip() noexcept {
ImGui::Text("npath: %s", data().npath.generic_string().c_str());
ImGui::Text("mode : %s", data().mode.c_str());
}
void NativeFile::UpdateWidget() noexcept {
ImGui::TextUnformatted("System/NativeFile");
if (ImGui::Button("config")) {
config_popup_.Open();
}
config_popup_.Update();
}
void NativeFile::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();
}
}
}
} // namespace nf7

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_internal.h>
#include <ImNodes.h>
#include <yas/serialize.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_node.hh"
#include "common/gui.hh"
#include "common/life.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
@@ -27,20 +30,15 @@
namespace nf7 {
namespace {
class Curve final : public nf7::File,
class Curve final : public nf7::FileBase,
public nf7::DirItem,
public nf7::Node,
public nf7::Sequencer {
public:
static inline const nf7::GenericTypeInfo<Curve> kType =
{"Value/Curve", {"nf7::DirItem", "nf7::Node", "nf7::Sequencer"}};
static void UpdateTypeTooltip() noexcept {
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");
}
static inline const nf7::GenericTypeInfo<Curve> kType = {
"Value/Curve", {"nf7::DirItem", "nf7::Node", "nf7::Sequencer"},
"bezier curve editor",
};
class NodeLambda;
class SeqLambda;
@@ -67,12 +65,12 @@ class Curve final : public nf7::File,
};
Curve(nf7::Env& env, Data&& data = {}) noexcept :
nf7::File(kType, env),
nf7::FileBase(kType, env),
nf7::DirItem(nf7::DirItem::kWidget),
nf7::Node(nf7::Node::kCustomNode),
nf7::Sequencer(nf7::Sequencer::kCustomItem |
nf7::Sequencer::kParamPanel),
life_(*this), mem_(std::move(data), *this) {
life_(*this), mem_(*this, std::move(data)) {
AssignId();
Sanitize();
}
@@ -94,13 +92,8 @@ class Curve final : public nf7::File,
std::shared_ptr<nf7::Sequencer::Lambda> CreateLambda(
const std::shared_ptr<nf7::Context>&) noexcept override;
std::span<const std::string> GetInputs() const noexcept override {
static const std::vector<std::string> kInputs = {"x"};
return kInputs;
}
std::span<const std::string> GetOutputs() const noexcept override {
static const std::vector<std::string> kOutputs = {"y"};
return kOutputs;
nf7::Node::Meta GetMeta() const noexcept override {
return {{"x"}, {"y"}};
}
void UpdateItem(nf7::Sequencer::Editor&) noexcept override;
@@ -309,11 +302,10 @@ class Curve::NodeLambda final : public nf7::Node::Lambda,
nf7::Node::Lambda(f, parent), f_(f.life_) {
}
void Handle(std::string_view, const nf7::Value& v,
const std::shared_ptr<nf7::Node::Lambda>& caller) noexcept override
void Handle(const nf7::Node::Lambda::Msg& in) noexcept override
try {
f_.EnforceAlive();
caller->Handle("y", f_->Calc(v.scalar()), shared_from_this());
in.sender->Handle("y", f_->Calc(in.value.scalar()), shared_from_this());
} catch (nf7::Exception&) {
}
@@ -394,8 +386,8 @@ void Curve::UpdateCurveEditorWindow(const ImVec2& size) noexcept {
const auto pad = ImGui::GetStyle().WindowPadding / 2;
ImGui::SetCursorPos(pad);
UpdateCurveEditor(ImGui::GetContentRegionAvail()-pad*2);
ImGui::EndChild();
}
ImGui::EndChild();
}
void Curve::UpdateCurveEditor(const ImVec2& sz) noexcept {
const auto& io = ImGui::GetIO();

590
file/value_imm.cc Normal file
View File

@@ -0,0 +1,590 @@
#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/gui_dnd.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
nf7::File& file;
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 nf7::File&) 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 {};
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("generates a pulse manually");
}
ImGui::EndDisabled();
}
void serialize(auto&) {
}
};
struct Integer {
public:
static constexpr const char* kName = "integer";
static constexpr const char* kDesc = nullptr;
nf7::Value GetValue(const nf7::File&) 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 nf7::File&) 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 nf7::File&) 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 = value_;
ed.mod = true;
}
if (ImGui::BeginDragDropTarget()) {
if (auto p = gui::dnd::Accept<nf7::File::Path>(gui::dnd::kFilePath)) {
value_ = p->Stringify();
ed.mod = true;
}
ImGui::EndDragDropTarget();
}
}
void serialize(auto& ar) {
ar(value_);
}
private:
std::string value_;
};
template <int kMin, int kMax>
struct SliderBase {
public:
nf7::Value GetValue(const nf7::File&) 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());
}
nf7::Value GetValue(const nf7::File&) const noexcept {
return GetValue();
}
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());
}
nf7::Value GetValue(const nf7::File&) const noexcept {
return GetValue();
}
void Editor(EditorStatus& ed) noexcept {
const auto em = ImGui::GetFontSize();
auto dlist = ImGui::GetForegroundDrawList();
if (!ed.autosize) {
ImGui::SetNextItemWidth(6*ImGui::GetFontSize());
}
ImGui::DragFloat2("##value", values_.data(), 1e-3f);
ImGui::SameLine();
ImGui::ButtonEx("+", ImVec2 {0, 0},
ImGuiButtonFlags_MouseButtonLeft |
ImGuiButtonFlags_MouseButtonRight);
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::TextUnformatted("LMB & drag: set a position absolutely");
ImGui::TextUnformatted("RMB & drag: move a position relatively");
ImGui::EndTooltip();
}
if (ImGui::IsItemActive()) {
const auto ctx = ImGui::GetCurrentContext();
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 = 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 (ctx->ActiveIdMouseButton == ImGuiMouseButton_Right) {
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_;
};
struct FileRef {
public:
static constexpr const char* kName = "file ref";
static constexpr const char* kDesc = "emits a file ID from the path";
nf7::Value GetValue(const nf7::File& f) const noexcept {
try {
const auto& target = f.ResolveOrThrow(path_);
return static_cast<nf7::Value::Integer>(target.id());
} catch (nf7::File::NotFoundException&) {
return nf7::Value::Integer {0};
}
}
void Editor(EditorStatus& ed) noexcept {
ImGui::SetNextItemWidth(12*ImGui::GetFontSize());
gui::PathButton("##path", path_, ed.file);
if (ImGui::BeginDragDropTarget()) {
if (auto p = gui::dnd::Accept<nf7::File::Path>(gui::dnd::kFilePath)) {
path_ = std::move(*p);
ed.mod = true;
}
ImGui::EndDragDropTarget();
}
}
void serialize(auto& ar) {
ar(path_);
}
private:
nf7::File::Path path_;
};
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, FileRef>;
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(*this); }, 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 = {
.file = *this,
.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 {
.file = *this,
.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 {
.file = *this,
.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>();
ImGui::Separator();
MenuItem<FileRef>();
}
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

447
file/value_plot.cc Normal file
View File

@@ -0,0 +1,447 @@
#include <cstdint>
#include <memory>
#include <string>
#include <string_view>
#include <typeinfo>
#include <unordered_map>
#include <vector>
#include <imgui.h>
#include <imgui_stdlib.h>
#include <implot.h>
#include <magic_enum.hpp>
#include <yaml-cpp/yaml.h>
#include <yas/serialize.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_window.hh"
#include "common/life.hh"
#include "common/logger_ref.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
#include "common/util_algorithm.hh"
#include "common/value.hh"
#include "common/yas_enum.hh"
namespace nf7 {
namespace {
class Plot final : public nf7::FileBase,
public nf7::GenericConfig,
public nf7::DirItem,
public nf7::Node {
public:
static inline const nf7::GenericTypeInfo<Plot> kType =
{"Value/Plot", {"nf7::DirItem"}, "data plotter"};
class Lambda;
enum SeriesType {
kLine,
kScatter,
kBars,
};
enum SeriesFormat {
kU8 = 0x11,
kS8 = 0x21,
kU16 = 0x12,
kS16 = 0x22,
kU32 = 0x14,
kS32 = 0x24,
kF32 = 0x34,
kF64 = 0x38,
};
struct SeriesData {
SeriesFormat fmt;
nf7::Value::ConstVector xs;
nf7::Value::ConstVector ys;
double param[3];
size_t count = 0;
size_t offset = 0;
size_t stride = 0;
int flags;
};
struct Series {
std::string name;
SeriesType type;
SeriesFormat fmt;
std::shared_ptr<SeriesData> data;
Series(std::string_view n = "", SeriesType t = kLine, SeriesFormat f = kF32) noexcept :
name(n), type(t), fmt(f), data(std::make_shared<SeriesData>()) {
}
Series(const Series&) = default;
Series(Series&&) = default;
Series& operator=(const Series&) = default;
Series& operator=(Series&&) = default;
bool operator==(std::string_view v) const noexcept { return name == v; }
bool operator==(const Series& s) const noexcept { return name == s.name; }
void serialize(auto& ar) {
ar(name, type, fmt);
}
void Update() const noexcept;
};
struct Data {
std::string Stringify() const noexcept;
void Parse(const std::string&);
std::vector<Series> series;
};
Plot(nf7::Env& env, Data&& data = {}) noexcept :
nf7::FileBase(kType, env),
nf7::GenericConfig(mem_),
nf7::DirItem(nf7::DirItem::kMenu),
nf7::Node(nf7::Node::kNone),
life_(*this), log_(*this), win_(*this, "Plot"),
mem_(*this, std::move(data)) {
win_.onUpdate = [this]() { PlotGraph(); };
mem_.onRestore = mem_.onCommit = [this]() { BuildInputList(); };
Sanitize();
}
Plot(nf7::Deserializer& ar) : Plot(ar.env()) {
ar(win_, mem_->series);
Sanitize();
}
void Serialize(nf7::Serializer& ar) const noexcept override {
ar(win_, mem_->series);
}
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<Plot>(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 {inputs_, {}};
}
void UpdateMenu() 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<Plot> life_;
nf7::LoggerRef log_;
nf7::gui::Window win_;
nf7::GenericMemento<Data> mem_;
std::vector<std::string> inputs_;
// config management
void Sanitize() {
nf7::util::Uniq(mem_->series);
mem_.CommitAmend();
}
void BuildInputList() {
inputs_.clear();
inputs_.reserve(mem_->series.size());
for (const auto& s : mem_->series) {
inputs_.push_back(s.name);
}
}
// gui
void PlotGraph() noexcept;
};
class Plot::Lambda final : public nf7::Node::Lambda {
public:
Lambda(Plot& 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& series = f_->mem_->series;
auto itr = std::find(series.begin(), series.end(), in.name);
if (itr == series.end()) {
throw nf7::Exception {"unknown series name"};
}
const auto& s = *itr;
auto& v = in.value;
auto& data = *s.data;
if (v.isVector()) {
const auto& vec = v.vector();
const auto fmtsz = static_cast<size_t>(s.fmt & 0xF);
data = SeriesData {
.fmt = s.fmt,
.xs = vec,
.ys = nullptr,
.param = {0},
.count = vec->size() / fmtsz,
.offset = 0,
.stride = fmtsz,
.flags = 0,
};
switch (s.type) {
case kLine:
case kScatter:
data.param[0] = 1; // xscale
break;
case kBars:
data.param[0] = 0.67; // barsize
break;
}
} else if (v.isTuple()) {
// TODO: parameters
} else {
throw nf7::Exception {"expected vector"};
}
} catch (nf7::ExpiredException&) {
} catch (nf7::Exception&) {
f_->log_.Warn("plotter error");
}
private:
nf7::Life<Plot>::Ref f_;
};
std::shared_ptr<nf7::Node::Lambda> Plot::CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
return std::make_shared<Plot::Lambda>(*this, parent);
}
void Plot::UpdateMenu() noexcept {
win_.MenuItem();
}
void Plot::PlotGraph() noexcept {
if (ImPlot::BeginPlot("##plot", ImGui::GetContentRegionAvail())) {
ImPlot::SetupAxis(ImAxis_X1, "X", ImPlotAxisFlags_AutoFit);
ImPlot::SetupAxis(ImAxis_Y1, "Y", ImPlotAxisFlags_AutoFit);
for (const auto& s : mem_->series) {
s.Update();
}
ImPlot::EndPlot();
}
}
void Plot::Series::Update() const noexcept {
switch (type) {
case kLine: {
const auto Line = [&]<typename T>() {
if (data->xs && data->ys) {
ImPlot::PlotLine(
name.c_str(),
reinterpret_cast<const T*>(data->xs->data()),
reinterpret_cast<const T*>(data->ys->data()),
static_cast<int>(data->count),
data->flags,
static_cast<int>(data->offset),
static_cast<int>(data->stride));
} else if (data->xs) {
ImPlot::PlotLine(
name.c_str(),
reinterpret_cast<const T*>(data->xs->data()),
static_cast<int>(data->count),
data->param[0],
data->param[1],
data->flags,
static_cast<int>(data->offset),
static_cast<int>(data->stride));
}
};
switch (data->fmt) {
case kU8: Line.operator()<uint8_t>(); break;
case kS8: Line.operator()<int8_t>(); break;
case kU16: Line.operator()<uint16_t>(); break;
case kS16: Line.operator()<int16_t>(); break;
case kU32: Line.operator()<uint32_t>(); break;
case kS32: Line.operator()<int32_t>(); break;
case kF32: Line.operator()<float>(); break;
case kF64: Line.operator()<double>(); break;
}
} break;
case kScatter: {
const auto Scatter = [&]<typename T>() {
if (data->xs && data->ys) {
ImPlot::PlotScatter(
name.c_str(),
reinterpret_cast<const T*>(data->xs->data()),
reinterpret_cast<const T*>(data->ys->data()),
static_cast<int>(data->count),
data->flags,
static_cast<int>(data->offset),
static_cast<int>(data->stride));
} else if (data->xs) {
ImPlot::PlotScatter(
name.c_str(),
reinterpret_cast<const T*>(data->xs->data()),
static_cast<int>(data->count),
data->param[0],
data->param[1],
data->flags,
static_cast<int>(data->offset),
static_cast<int>(data->stride));
}
};
switch (data->fmt) {
case kU8: Scatter.operator()<uint8_t>(); break;
case kS8: Scatter.operator()<int8_t>(); break;
case kU16: Scatter.operator()<uint16_t>(); break;
case kS16: Scatter.operator()<int16_t>(); break;
case kU32: Scatter.operator()<uint32_t>(); break;
case kS32: Scatter.operator()<int32_t>(); break;
case kF32: Scatter.operator()<float>(); break;
case kF64: Scatter.operator()<double>(); break;
}
} break;
case kBars: {
const auto Bars = [&]<typename T>() {
if (data->xs && data->ys) {
ImPlot::PlotBars(
name.c_str(),
reinterpret_cast<const T*>(data->xs->data()),
reinterpret_cast<const T*>(data->ys->data()),
static_cast<int>(data->count),
data->param[0],
data->flags,
static_cast<int>(data->offset),
static_cast<int>(data->stride));
} else if (data->xs) {
ImPlot::PlotBars(
name.c_str(),
reinterpret_cast<const T*>(data->xs->data()),
static_cast<int>(data->count),
data->param[0],
data->param[1],
data->flags,
static_cast<int>(data->offset),
static_cast<int>(data->stride));
}
};
switch (data->fmt) {
case kU8: Bars.operator()<uint8_t>(); break;
case kS8: Bars.operator()<int8_t>(); break;
case kU16: Bars.operator()<uint16_t>(); break;
case kS16: Bars.operator()<int16_t>(); break;
case kU32: Bars.operator()<uint32_t>(); break;
case kS32: Bars.operator()<int32_t>(); break;
case kF32: Bars.operator()<float>(); break;
case kF64: Bars.operator()<double>(); break;
}
} break;
}
}
std::string Plot::Data::Stringify() const noexcept {
YAML::Emitter st;
st << YAML::BeginMap;
st << YAML::Key << "series";
st << YAML::Value << YAML::BeginMap;
for (auto& s : series) {
st << YAML::Key << s.name;
st << YAML::Value << YAML::BeginMap;
st << YAML::Key << "type";
st << YAML::Value << std::string {magic_enum::enum_name(s.type)};
st << YAML::Key << "fmt" ;
st << YAML::Value << std::string {magic_enum::enum_name(s.fmt)};
st << YAML::EndMap;
}
st << YAML::EndMap;
st << YAML::EndMap;
return std::string {st.c_str(), st.size()};
}
void Plot::Data::Parse(const std::string& str)
try {
const auto& yaml = YAML::Load(str);
std::vector<Series> new_series;
for (auto& s : yaml["series"]) {
new_series.emplace_back(
s.first.as<std::string>(),
magic_enum::enum_cast<SeriesType>(s.second["type"].as<std::string>()).value(),
magic_enum::enum_cast<SeriesFormat>(s.second["fmt"].as<std::string>()).value());
}
series = std::move(new_series);
} catch (std::bad_optional_access&) {
throw nf7::Exception {"unknown enum"};
} catch (YAML::Exception& e) {
throw nf7::Exception {e.what()};
}
} // namespace
} // namespace nf7
namespace magic_enum::customize {
template <>
constexpr customize_t magic_enum::customize::enum_name<nf7::Plot::SeriesType>(nf7::Plot::SeriesType v) noexcept {
switch (v) {
case nf7::Plot::SeriesType::kLine: return "line";
case nf7::Plot::SeriesType::kScatter: return "scatter";
case nf7::Plot::SeriesType::kBars: return "bars";
}
return invalid_tag;
}
template <>
constexpr customize_t magic_enum::customize::enum_name<nf7::Plot::SeriesFormat>(nf7::Plot::SeriesFormat v) noexcept {
switch (v) {
case nf7::Plot::SeriesFormat::kU8: return "u8";
case nf7::Plot::SeriesFormat::kS8: return "s8";
case nf7::Plot::SeriesFormat::kU16: return "u16";
case nf7::Plot::SeriesFormat::kS16: return "s16";
case nf7::Plot::SeriesFormat::kU32: return "u32";
case nf7::Plot::SeriesFormat::kS32: return "s32";
case nf7::Plot::SeriesFormat::kF32: return "f32";
case nf7::Plot::SeriesFormat::kF64: return "f64";
}
return invalid_tag;
}
} // namespace magic_enum::customize
namespace yas::detail {
template <size_t F>
struct serializer<
yas::detail::type_prop::is_enum,
yas::detail::ser_case::use_internal_serializer,
F, nf7::Plot::SeriesType> :
nf7::EnumSerializer<nf7::Plot::SeriesType> {
};
template <size_t F>
struct serializer<
yas::detail::type_prop::is_enum,
yas::detail::ser_case::use_internal_serializer,
F, nf7::Plot::SeriesFormat> :
nf7::EnumSerializer<nf7::Plot::SeriesFormat> {
};
} // namespace yas::detail

28
init.hh
View File

@@ -7,13 +7,29 @@
inline std::unique_ptr<nf7::File> CreateRoot(nf7::Env& env) noexcept {
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));
dir.Add("_imgui", nf7::File::registry("System/ImGui").Create(env));
dir.Add("_logger", nf7::File::registry("System/Logger").Create(env));
dir.Add("_luajit", nf7::File::registry("LuaJIT/Context").Create(env));
const auto Add = [&](nf7::Dir& dir, const char* name, const char* type) -> nf7::File& {
return dir.Add(name, nf7::File::registry(type).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& 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;
}

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