Compare commits
247 Commits
v0.1.0
...
8b2f40d823
| Author | SHA1 | Date | |
|---|---|---|---|
| 8b2f40d823 | |||
| 2834136eaf | |||
| 528bb06523 | |||
| d604ede99b | |||
| 87b5e461b8 | |||
| 0e50b539e5 | |||
| c8feba19c0 | |||
| 66a2b4f552 | |||
| ee718b09f4 | |||
| 014c09e788 | |||
| 69cd19ebb4 | |||
| c5d46f37d6 | |||
| 5d796635b7 | |||
| fa6d7c36ed | |||
| a0eaee59d7 | |||
| 1969f7cdbe | |||
| a3e57406b9 | |||
| 382ecb374c | |||
| c053768146 | |||
| 6ed9f7e463 | |||
| 6e4f8aa114 | |||
| 1681e3ff32 | |||
| 62fc62da36 | |||
| b2e5ccba8c | |||
| 7e5ebabe94 | |||
| e3ccfcda3e | |||
| 7f442e3acb | |||
| c6c8bf96b6 | |||
| a6fdc76333 | |||
| deee4f1e98 | |||
| 3b3bdf834d | |||
| 86d0e2ed0b | |||
| 48e684b891 | |||
| a9345da3c4 | |||
| fce36b2c8f | |||
| a08a02d71c | |||
| 6bca7d7909 | |||
| 9b2ea8703e | |||
| dcc3668d6c | |||
| 1978d28316 | |||
| 3346e1f9ba | |||
| 93312c6d04 | |||
| d7fe51d946 | |||
| 14db20fe67 | |||
| 482796bf60 | |||
| 4f94ea4e3b | |||
| 0c29b828c4 | |||
| 74207a0b63 | |||
| 31924ce5b2 | |||
| d33330b6c3 | |||
| 7f3e3e2064 | |||
| 8b9b7f669f | |||
| 5bd00c97d2 | |||
| 2cbf0035f4 | |||
| cef93dabbc | |||
| 704d8c93ca | |||
| 80d33bd5d4 | |||
| 5c1f41d874 | |||
| c5c3ec769a | |||
| ceff117781 | |||
| 8a78450bcf | |||
| b24e7d0ca5 | |||
| 3901179b51 | |||
| 4851f1eb28 | |||
| 49f106a951 | |||
| 4917cd367e | |||
| 27a28d6c0d | |||
| 61a97e6a32 | |||
| 02d6814eaf | |||
| 49565e657c | |||
| eeb7dabd1f | |||
| 3c2ed1731a | |||
| 06400d4ea4 | |||
| de0f2a4a8a | |||
| 5b566acd10 | |||
| 881704fc49 | |||
| 63dd28ab76 | |||
| 744e8e2506 | |||
| d284221f2c | |||
| 00d9697b9d | |||
| 77ac2e95c0 | |||
| 3c67497229 | |||
| 5c74c5cc40 | |||
| 7cd818fff8 | |||
| e7d37b0adb | |||
| 7f6fd26c71 | |||
| d72ade7b37 | |||
| fb05b5a7d8 | |||
| 7489bad3f8 | |||
| e87c746e65 | |||
| 646863170e | |||
| 27b594ee54 | |||
| 4de67f65e6 | |||
| b6f0f9fa0a | |||
| 9e51470b11 | |||
| 90de6bf3f4 | |||
| 1a7b4fc632 | |||
| 14066c1256 | |||
| 6887410e19 | |||
| 9038511525 | |||
| c5a357c10c | |||
| 935a6f5660 | |||
| 3720893946 | |||
| 5894a303dd | |||
| afa26d36d4 | |||
| c9d1cd40f3 | |||
| b15eee5d9c | |||
| 10946b9b7c | |||
| 5ccfc9869e | |||
| 6546f6b650 | |||
| ceb360c7c7 | |||
| e283e99276 | |||
| 5d79d7631b | |||
| 1f5f46c925 | |||
| 803d93f3ec | |||
| 49e3d6c9c5 | |||
| 6fb6efe9a6 | |||
| 53d4f9f107 | |||
| e4b6e86ebf | |||
| 78dc01be2f | |||
| c02d9e3b10 | |||
| 7a2ead6e6f | |||
| 4b61edd53e | |||
| 728f85328b | |||
| df56eb3462 | |||
| 173edff4a3 | |||
| 5894acda8c | |||
| 61865f4d26 | |||
| f6be39f719 | |||
| 3a4d801f95 | |||
| 0d60b2401a | |||
| 2ec4422c56 | |||
| 7ee26d431e | |||
| b463e112aa | |||
| 3e2d162d65 | |||
| 267c25f798 | |||
| e8e0322e66 | |||
| bb799adfb4 | |||
| dd14217f5b | |||
| 245884fae7 | |||
| 69690f2e29 | |||
| 2e0f0a2303 | |||
| 532fd141e3 | |||
| 17d57ea3e5 | |||
| beb67589ef | |||
| c5337f69c2 | |||
| fe7531260f | |||
| 8e571ee446 | |||
| 42bc1da204 | |||
| f0c4f893bd | |||
| 8f6ff99136 | |||
| 5e515e23fa | |||
| 3b25790f3c | |||
| 5e8fa70805 | |||
| b553f44f95 | |||
| 08fcda65ca | |||
| 4de72c20af | |||
| 2a2c8f3109 | |||
| 20b5217869 | |||
| 0cb8468a58 | |||
| fa1a29c325 | |||
| 9fc39b986a | |||
| e607a587c1 | |||
| 31bbf118e1 | |||
| 8ef4abd75e | |||
| 694e9e34bb | |||
| c1f7328628 | |||
| dc3d8b15bf | |||
| 05201ef13e | |||
| 1b424e299c | |||
| 3c09ac4491 | |||
| 1ee59aaedf | |||
| 42738f4923 | |||
| af19f7807d | |||
| 9dada90b78 | |||
| 419b9a98e2 | |||
| d5b5e664d7 | |||
| f869f191f2 | |||
| c42b63da0c | |||
| 1e33062e9a | |||
| ecb7fca1e8 | |||
| 50f270b571 | |||
| e927179176 | |||
| ffe3d6265d | |||
| 3d14e709b1 | |||
| a19b7636fa | |||
| c59d3e2f29 | |||
| 7f90b40236 | |||
| 396bebf6b5 | |||
| 3b0d86c0a5 | |||
| fcf3dad206 | |||
| 9a637586e2 | |||
| ca5dfb5933 | |||
| 38fc3b680a | |||
| 96fd71df07 | |||
| 0a55250f52 | |||
| 2c6608ea09 | |||
| 451094c9fc | |||
| 94615b3669 | |||
| 4e83f7b5e9 | |||
| 7ee770e67d | |||
| 4b79c5e4df | |||
| 58d39739e8 | |||
| 77dc8cef32 | |||
| 2edd7d9e88 | |||
| cafba96971 | |||
| e3dbcb016d | |||
| 4a25b88e25 | |||
| 5c84d95139 | |||
| f96188ef14 | |||
| 7d696cfbd9 | |||
| 615d2eacb0 | |||
| c2c4b83918 | |||
| 4f13dd9456 | |||
| 7b2f9c8d55 | |||
| b949383932 | |||
| 5ef347fa2e | |||
| f77a60831c | |||
| 8688ef98b6 | |||
| d0d6a2ebd5 | |||
| ab802d02e3 | |||
| 007882ccfd | |||
| 2082a6e482 | |||
| 8ffad3347f | |||
| 7275e9a710 | |||
| 46e6a78682 | |||
| 09375ced9c | |||
| 79f3cc9639 | |||
| f16937da5a | |||
| 7dbda8d281 | |||
| 8879e9ed41 | |||
| e045e86b11 | |||
| 07b198f71e | |||
| b004723464 | |||
| 6e820daef8 | |||
| dcbd3594cf | |||
| c5590092fa | |||
| 8339cc814a | |||
| bbfee304bd | |||
| 2040898bd7 | |||
| 46ddb16128 | |||
| d6a9c62a63 | |||
| a5f3e459bf | |||
| 336f436942 | |||
| 589cd4b4fc | |||
| 5505488661 | |||
| f76960da1b |
114
CMakeLists.txt
114
CMakeLists.txt
@@ -1,17 +1,22 @@
|
|||||||
cmake_minimum_required(VERSION 3.18)
|
cmake_minimum_required(VERSION 3.18)
|
||||||
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
|
cmake_policy(SET CMP0077 NEW)
|
||||||
|
|
||||||
# ---- configuration ----
|
# ---- configuration ----
|
||||||
project(nf7 C CXX)
|
project(nf7 C CXX)
|
||||||
|
|
||||||
option(NF7_STATIC "link all libs statically" ON)
|
option(NF7_STATIC "link all libs statically" ON)
|
||||||
|
option(NF7_SANITIZE_THREAD "use thread sanitizer" OFF)
|
||||||
|
option(NF7_SANITIZE "use various sanitizer" OFF)
|
||||||
|
option(NF7_PROFILE "profiler" OFF)
|
||||||
|
|
||||||
set(NF7_GENERATED_INCLUDE_DIR "${PROJECT_BINARY_DIR}/include/generated")
|
if (NF7_SANITIZE_THREAD AND NF7_PROFILE)
|
||||||
file(MAKE_DIRECTORY "${NF7_GENERATED_INCLUDE_DIR}")
|
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(NF7_OPTIONS_WARNING
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
||||||
set(NF7_CXX_FLAGS
|
|
||||||
$<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>:
|
$<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>:
|
||||||
-Wall -Werror -pedantic-errors -Wextra -Wconversion -Wsign-conversion>
|
-Wall -Werror -pedantic-errors -Wextra -Wconversion -Wsign-conversion>
|
||||||
$<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>>:
|
$<$<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>
|
/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)
|
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||||
|
|
||||||
add_subdirectory(thirdparty EXCLUDE_FROM_ALL)
|
add_subdirectory(thirdparty EXCLUDE_FROM_ALL)
|
||||||
@@ -27,9 +51,15 @@ add_subdirectory(thirdparty EXCLUDE_FROM_ALL)
|
|||||||
|
|
||||||
# ---- application ----
|
# ---- application ----
|
||||||
add_executable(nf7)
|
add_executable(nf7)
|
||||||
target_compile_options(nf7 PRIVATE ${NF7_CXX_FLAGS})
|
|
||||||
target_include_directories(nf7 PRIVATE . "${PROJECT_BINARY_DIR}/include")
|
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
|
target_compile_definitions(nf7
|
||||||
PRIVATE
|
PRIVATE
|
||||||
IMGUI_DEFINE_MATH_OPERATORS
|
IMGUI_DEFINE_MATH_OPERATORS
|
||||||
@@ -44,59 +74,75 @@ target_sources(nf7
|
|||||||
nf7.cc
|
nf7.cc
|
||||||
nf7.hh
|
nf7.hh
|
||||||
theme.hh
|
theme.hh
|
||||||
|
version.hh
|
||||||
|
|
||||||
common/aggregate_command.hh
|
common/aggregate_command.hh
|
||||||
|
common/aggregate_promise.hh
|
||||||
common/audio_queue.hh
|
common/audio_queue.hh
|
||||||
|
common/config.hh
|
||||||
|
common/context_owner.hh
|
||||||
common/dir.hh
|
common/dir.hh
|
||||||
common/dir_item.hh
|
common/dir_item.hh
|
||||||
|
common/dll.hh
|
||||||
|
common/factory.hh
|
||||||
common/file_base.hh
|
common/file_base.hh
|
||||||
common/file_holder.hh
|
common/font_queue.hh
|
||||||
common/file_holder.cc
|
|
||||||
common/future.hh
|
common/future.hh
|
||||||
|
common/generic_config.hh
|
||||||
common/generic_context.hh
|
common/generic_context.hh
|
||||||
|
common/generic_dir.hh
|
||||||
common/generic_history.hh
|
common/generic_history.hh
|
||||||
common/generic_memento.hh
|
common/generic_memento.hh
|
||||||
common/generic_type_info.hh
|
common/generic_type_info.hh
|
||||||
common/generic_watcher.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_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.hh
|
||||||
common/gui_timeline.cc
|
common/gui_timeline.cc
|
||||||
common/gui_value.hh
|
common/gui_value.hh
|
||||||
common/gui_value.cc
|
common/gui_value.cc
|
||||||
common/gui_window.hh
|
common/gui_window.hh
|
||||||
|
common/gui_window.cc
|
||||||
common/history.hh
|
common/history.hh
|
||||||
common/life.hh
|
common/life.hh
|
||||||
common/logger.hh
|
common/logger.hh
|
||||||
common/logger_ref.hh
|
common/logger_ref.hh
|
||||||
common/luajit.hh
|
common/luajit.hh
|
||||||
common/luajit.cc
|
common/luajit.cc
|
||||||
|
common/luajit_nfile_importer.hh
|
||||||
common/luajit_queue.hh
|
common/luajit_queue.hh
|
||||||
common/luajit_ref.hh
|
common/luajit_ref.hh
|
||||||
common/luajit_thread.hh
|
common/luajit_thread.hh
|
||||||
common/luajit_thread.cc
|
common/luajit_thread.cc
|
||||||
common/luajit_thread_lambda.hh
|
|
||||||
common/memento.hh
|
common/memento.hh
|
||||||
common/memento_recorder.hh
|
common/memento_recorder.hh
|
||||||
common/mutable_memento.hh
|
common/mutex.hh
|
||||||
common/native_file.hh
|
common/nfile.hh
|
||||||
|
common/nfile_watcher.hh
|
||||||
|
common/node.h
|
||||||
common/node.hh
|
common/node.hh
|
||||||
common/node_link_store.hh
|
common/node_link_store.hh
|
||||||
common/node_root_lambda.hh
|
common/node_root_lambda.hh
|
||||||
common/ptr_selector.hh
|
common/ptr_selector.hh
|
||||||
|
common/pure_node_file.hh
|
||||||
common/queue.hh
|
common/queue.hh
|
||||||
|
common/ring_buffer.hh
|
||||||
common/sequencer.hh
|
common/sequencer.hh
|
||||||
common/squashed_history.hh
|
common/squashed_history.hh
|
||||||
|
common/stopwatch.hh
|
||||||
|
common/task.hh
|
||||||
common/thread.hh
|
common/thread.hh
|
||||||
common/timed_queue.hh
|
common/timed_queue.hh
|
||||||
common/util_string.hh
|
common/util_algorithm.hh
|
||||||
common/value.hh
|
common/value.hh
|
||||||
common/yas_audio.hh
|
common/yaml_nf7.hh
|
||||||
|
common/yas_enum.hh
|
||||||
common/yas_imgui.hh
|
common/yas_imgui.hh
|
||||||
common/yas_imnodes.hh
|
common/yas_imnodes.hh
|
||||||
common/yas_nf7.hh
|
common/yas_nf7.hh
|
||||||
@@ -104,38 +150,50 @@ target_sources(nf7
|
|||||||
common/yas_std_filesystem.hh
|
common/yas_std_filesystem.hh
|
||||||
common/yas_std_variant.hh
|
common/yas_std_variant.hh
|
||||||
|
|
||||||
$<$<PLATFORM_ID:Linux>:common/native_file_unix.cc>
|
$<$<PLATFORM_ID:Linux>:common/nfile_unix.cc>
|
||||||
$<$<PLATFORM_ID:Windows>:common/native_file_win.cc>
|
$<$<PLATFORM_ID:Windows>:common/nfile_win.cc>
|
||||||
|
|
||||||
file/audio_context.cc
|
file/audio_context.cc
|
||||||
file/audio_device.cc
|
file/audio_device.cc
|
||||||
|
file/font_context.cc
|
||||||
|
file/font_face.cc
|
||||||
|
file/gl_obj.cc
|
||||||
file/luajit_context.cc
|
file/luajit_context.cc
|
||||||
file/luajit_inline_node.cc
|
|
||||||
file/luajit_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_network.cc
|
||||||
file/node_ref.cc
|
file/node_ref.cc
|
||||||
|
file/node_singleton.cc
|
||||||
|
file/node_ziptie.cc
|
||||||
file/sequencer_adaptor.cc
|
file/sequencer_adaptor.cc
|
||||||
file/sequencer_call.cc
|
file/sequencer_call.cc
|
||||||
file/sequencer_timeline.cc
|
file/sequencer_timeline.cc
|
||||||
file/system_call.cc
|
|
||||||
file/system_dir.cc
|
file/system_dir.cc
|
||||||
file/system_event.cc
|
file/system_event.cc
|
||||||
file/system_imgui.cc
|
file/system_imgui.cc
|
||||||
file/system_logger.cc
|
file/system_logger.cc
|
||||||
file/system_native_file.cc
|
file/system_node.cc
|
||||||
file/value_curve.cc
|
file/value_curve.cc
|
||||||
|
file/value_imm.cc
|
||||||
|
file/value_plot.cc
|
||||||
)
|
)
|
||||||
target_link_libraries(nf7
|
target_link_libraries(nf7
|
||||||
PRIVATE
|
PRIVATE
|
||||||
|
exprtk
|
||||||
|
freetype
|
||||||
glew
|
glew
|
||||||
glfw
|
glfw
|
||||||
imgui
|
imgui
|
||||||
imnodes
|
imnodes
|
||||||
implot
|
implot
|
||||||
linalg.h
|
|
||||||
luajit
|
luajit
|
||||||
|
magic_enum
|
||||||
miniaudio
|
miniaudio
|
||||||
source_location
|
source_location
|
||||||
|
TracyClient
|
||||||
yas
|
yas
|
||||||
|
yaml-cpp
|
||||||
)
|
)
|
||||||
|
|||||||
63
README.md
63
README.md
@@ -1,8 +1,67 @@
|
|||||||
nf7
|
nf7
|
||||||
====
|
====
|
||||||
|
|
||||||
node-based programming language
|
portable visual programming platform
|
||||||
|
|
||||||
|
## REQUIREMENTS
|
||||||
|
|
||||||
|
- **OS**: Windows 10, Linux, and Chrome OS
|
||||||
|
- **CPU**: x86_64
|
||||||
|
- **GPU**: OpenGL 3.3 or higher (except Intel HD Graphics)
|
||||||
|
- **RAM**: 512 MB or more (depends on what you do)
|
||||||
|
- **Storage**: 100 MB or more (depends on what you do)
|
||||||
|
|
||||||
|
## INSTALLING
|
||||||
|
|
||||||
|
Build Nf7 by yourself, or download the binary from releases (unavailable on mirror repo).
|
||||||
|
|
||||||
|
It's expected to copy and put the executable on each of your projects to prevent old works from corruption by Nf7's update.
|
||||||
|
|
||||||
|
## BUILDING
|
||||||
|
|
||||||
|
### Succeeded Environments
|
||||||
|
|
||||||
|
- Windows 10 / CMake 3.20.21032501-MSVC_2 / cl 19.29.30137
|
||||||
|
- Arch Linux / CMake 3.24.2 / g++ 12.2.0
|
||||||
|
- Ubuntu (Chrome OS) / CMake 3.18.4 / g++ 10.2.1
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
```
|
||||||
|
PS> mkdir build
|
||||||
|
PS> cd build
|
||||||
|
PS> cmake .. # add build options before the double dots
|
||||||
|
PS> cmake --build . --config Release
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
|
||||||
|
```
|
||||||
|
$ mkdir build
|
||||||
|
$ cd build
|
||||||
|
$ cmake -DCMAKE_BUILD_TYPE=Release .. # add build options before the double dots
|
||||||
|
$ make
|
||||||
|
```
|
||||||
|
|
||||||
|
### CMake build options
|
||||||
|
|name|default|description|
|
||||||
|
|--|--|--|
|
||||||
|
| `NF7_STATIC` | `ON` | links all libraries statically |
|
||||||
|
| `NF7_SANITIZE_THREAD` | `OFF` | enables thread-sanitizer (g++ only) |
|
||||||
|
| `NF7_SANITIZE` | `OFF` | enables address/undefined/leak sanitizers (g++ only) |
|
||||||
|
| `NF7_PROFILE` | `OFF` | enables Tracy features |
|
||||||
|
|
||||||
|
The following condition must be met:
|
||||||
|
```
|
||||||
|
!(NF7_SANITIZE_THREAD && NF7_SANITIZE) && // address-sanitizer cannot be with thread-sanitizer
|
||||||
|
!(NF7_SANITIZE_THREAD && NF7_PROFILE) // TracyClient causes error because of thread-sanitizer
|
||||||
|
```
|
||||||
|
|
||||||
|
## DEPENDENCIES
|
||||||
|
|
||||||
|
see `thirdparty/CMakeLists.txt`
|
||||||
|
|
||||||
## LICENSE
|
## LICENSE
|
||||||
|
|
||||||
WTFPLv2
|
Do What The Fuck You Want To Public License v2
|
||||||
|
*-- expression has nothing without the real free --*
|
||||||
|
|||||||
78
common/aggregate_promise.hh
Normal file
78
common/aggregate_promise.hh
Normal 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
|
||||||
@@ -22,7 +22,6 @@ class Queue : public nf7::File::Interface {
|
|||||||
Queue& operator=(Queue&&) = delete;
|
Queue& operator=(Queue&&) = delete;
|
||||||
|
|
||||||
// thread-safe
|
// thread-safe
|
||||||
// WARNING: when failed to create ma_context, nullptr is passed
|
|
||||||
virtual void Push(const std::shared_ptr<nf7::Context>&, Task&&) noexcept = 0;
|
virtual void Push(const std::shared_ptr<nf7::Context>&, Task&&) noexcept = 0;
|
||||||
|
|
||||||
virtual std::shared_ptr<Queue> self() noexcept = 0;
|
virtual std::shared_ptr<Queue> self() noexcept = 0;
|
||||||
|
|||||||
22
common/config.hh
Normal file
22
common/config.hh
Normal 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
50
common/context_owner.hh
Normal 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
|
||||||
@@ -9,13 +9,20 @@ namespace nf7 {
|
|||||||
|
|
||||||
class DirItem : public File::Interface {
|
class DirItem : public File::Interface {
|
||||||
public:
|
public:
|
||||||
enum Flag : uint8_t {
|
enum Flag : uint16_t {
|
||||||
kNone = 0,
|
kNone = 0,
|
||||||
kTree = 1 << 0,
|
kTree = 1 << 0,
|
||||||
kMenu = 1 << 1,
|
kMenu = 1 << 1,
|
||||||
kTooltip = 1 << 2,
|
kTooltip = 1 << 2,
|
||||||
kWidget = 1 << 3,
|
kWidget = 1 << 3,
|
||||||
kDragDropTarget = 1 << 4,
|
kDragDropTarget = 1 << 4,
|
||||||
|
|
||||||
|
// Update() will be called earlier than other items.
|
||||||
|
// This is used by some system files and meaningless in most of cases.
|
||||||
|
kEarlyUpdate = 1 << 5,
|
||||||
|
|
||||||
|
// suggests to forbid to move/remove/clone through UI
|
||||||
|
kImportant = 1 << 6,
|
||||||
};
|
};
|
||||||
using Flags = uint8_t;
|
using Flags = uint8_t;
|
||||||
|
|
||||||
|
|||||||
72
common/dll.hh
Normal file
72
common/dll.hh
Normal 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
27
common/factory.hh
Normal 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
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -12,26 +13,27 @@ class FileBase : public nf7::File {
|
|||||||
public:
|
public:
|
||||||
class Feature {
|
class Feature {
|
||||||
public:
|
public:
|
||||||
Feature() = default;
|
Feature() = delete;
|
||||||
|
Feature(nf7::FileBase& f) noexcept {
|
||||||
|
f.feats_.push_back(this);
|
||||||
|
}
|
||||||
virtual ~Feature() = default;
|
virtual ~Feature() = default;
|
||||||
Feature(const Feature&) = delete;
|
Feature(const Feature&) = delete;
|
||||||
Feature(Feature&&) = delete;
|
Feature(Feature&&) = delete;
|
||||||
Feature& operator=(const Feature&) = delete;
|
Feature& operator=(const Feature&) = delete;
|
||||||
Feature& operator=(Feature&&) = delete;
|
Feature& operator=(Feature&&) = delete;
|
||||||
|
|
||||||
// Feature* is just for avoiding multi inheritance issues with Env::Watcher
|
|
||||||
virtual nf7::File* Find(std::string_view) const noexcept { return nullptr; }
|
virtual nf7::File* Find(std::string_view) const noexcept { return nullptr; }
|
||||||
virtual void Handle(const nf7::File::Event&) noexcept { }
|
virtual void Handle(const nf7::File::Event&) noexcept { }
|
||||||
virtual void Update() noexcept { }
|
virtual void Update() noexcept { }
|
||||||
};
|
};
|
||||||
|
|
||||||
FileBase(const nf7::File::TypeInfo& t,
|
using nf7::File::File;
|
||||||
nf7::Env& env,
|
|
||||||
std::vector<Feature*>&& feats = {}) noexcept :
|
|
||||||
nf7::File(t, env), feats_(std::move(feats)) {
|
|
||||||
}
|
|
||||||
|
|
||||||
nf7::File* Find(std::string_view name) const noexcept override {
|
nf7::File* Find(std::string_view name) const noexcept final {
|
||||||
|
if (auto ret = PreFind(name)) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
for (auto feat : feats_) {
|
for (auto feat : feats_) {
|
||||||
if (auto ret = feat->Find(name)) {
|
if (auto ret = feat->Find(name)) {
|
||||||
return ret;
|
return ret;
|
||||||
@@ -39,21 +41,29 @@ class FileBase : public nf7::File {
|
|||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
void Handle(const nf7::File::Event& ev) noexcept override {
|
void Handle(const nf7::File::Event& ev) noexcept final {
|
||||||
|
PreHandle(ev);
|
||||||
for (auto feat : feats_) {
|
for (auto feat : feats_) {
|
||||||
feat->Handle(ev);
|
feat->Handle(ev);
|
||||||
}
|
}
|
||||||
|
PostHandle(ev);
|
||||||
}
|
}
|
||||||
void Update() noexcept override {
|
void Update() noexcept final {
|
||||||
|
PreUpdate();
|
||||||
for (auto feat : feats_) {
|
for (auto feat : feats_) {
|
||||||
feat->Update();
|
feat->Update();
|
||||||
}
|
}
|
||||||
|
PostUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void Install(Feature& f) noexcept {
|
virtual nf7::File* PreFind(std::string_view) const noexcept { return nullptr; }
|
||||||
feats_.push_back(&f);
|
|
||||||
}
|
virtual void PreHandle(const nf7::File::Event&) noexcept { }
|
||||||
|
virtual void PostHandle(const nf7::File::Event&) noexcept { }
|
||||||
|
|
||||||
|
virtual void PreUpdate() noexcept { }
|
||||||
|
virtual void PostUpdate() noexcept { }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<Feature*> feats_;
|
std::vector<Feature*> feats_;
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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
57
common/font_face.hh
Normal 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
41
common/font_queue.hh
Normal 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
|
||||||
165
common/future.hh
165
common/future.hh
@@ -24,16 +24,23 @@ namespace nf7 {
|
|||||||
// 3. Call Promise::Return(T) or Promise::Throw() to finish the promise.
|
// 3. Call Promise::Return(T) or Promise::Throw() to finish the promise.
|
||||||
//
|
//
|
||||||
// Users who receive Future can wait for finishing
|
// 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>
|
template <typename T>
|
||||||
class Future final {
|
class Future final {
|
||||||
public:
|
public:
|
||||||
class Promise;
|
class Promise;
|
||||||
class Coro;
|
class Coro;
|
||||||
|
|
||||||
|
using Product = T;
|
||||||
|
using ThisFuture = nf7::Future<T>;
|
||||||
using Handle = std::coroutine_handle<Promise>;
|
using Handle = std::coroutine_handle<Promise>;
|
||||||
using Imm = std::variant<T, std::exception_ptr>;
|
using Imm = std::variant<T, std::exception_ptr>;
|
||||||
|
|
||||||
@@ -59,7 +66,7 @@ class Future final {
|
|||||||
// Factory side have this to tell finish or abort.
|
// Factory side have this to tell finish or abort.
|
||||||
class Promise final {
|
class Promise final {
|
||||||
public:
|
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>;
|
||||||
// template <typename U> friend class nf7::Future<U>::Coro;
|
// template <typename U> friend class nf7::Future<U>::Coro;
|
||||||
|
|
||||||
@@ -90,11 +97,14 @@ class Future final {
|
|||||||
auto Return(T&& v) noexcept {
|
auto Return(T&& v) noexcept {
|
||||||
std::unique_lock<std::mutex> k(data_->mtx);
|
std::unique_lock<std::mutex> k(data_->mtx);
|
||||||
if (data_->state == kYet) {
|
if (data_->state == kYet) {
|
||||||
data_->state = kDone;
|
|
||||||
data_->value = std::move(v);
|
data_->value = std::move(v);
|
||||||
|
data_->state = kDone;
|
||||||
CallReceivers();
|
CallReceivers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
auto Return(const T& v) noexcept {
|
||||||
|
Return(T {v});
|
||||||
|
}
|
||||||
// thread-safe
|
// thread-safe
|
||||||
void Throw(std::exception_ptr e) noexcept {
|
void Throw(std::exception_ptr e) noexcept {
|
||||||
std::unique_lock<std::mutex> k(data_->mtx);
|
std::unique_lock<std::mutex> k(data_->mtx);
|
||||||
@@ -104,21 +114,25 @@ class Future final {
|
|||||||
CallReceivers();
|
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
|
// thread-safe
|
||||||
// Do Return(f()) if no exception is thrown, otherwise call Throw().
|
// Do Return(f()) if no exception is thrown, otherwise call Throw().
|
||||||
auto Wrap(const std::function<T()>& f) noexcept
|
auto Wrap(const std::function<T()>& f) noexcept
|
||||||
try {
|
try {
|
||||||
Return(f());
|
Return(f());
|
||||||
} catch (Exception&) {
|
} catch (...) {
|
||||||
Throw(std::current_exception());
|
Throw(std::current_exception());
|
||||||
}
|
}
|
||||||
|
|
||||||
// thread-safe
|
// thread-safe
|
||||||
// Creates Future() object.
|
// Creates Future() object.
|
||||||
Future future() const noexcept {
|
ThisFuture future() const noexcept {
|
||||||
assert(data_);
|
assert(data_);
|
||||||
return Future(data_);
|
return ThisFuture(data_);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto get_return_object() noexcept {
|
auto get_return_object() noexcept {
|
||||||
@@ -179,14 +193,14 @@ class Future final {
|
|||||||
Coro& operator=(const Coro&) = delete;
|
Coro& operator=(const Coro&) = delete;
|
||||||
Coro& operator=(Coro&&) = default;
|
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(); });
|
ctx->env().ExecSub(ctx, [h = h_]() { h.resume(); });
|
||||||
data_->ctx = ctx;
|
data_->ctx = ctx;
|
||||||
return Future(data_);
|
return ThisFuture(data_);
|
||||||
}
|
}
|
||||||
void Abort() noexcept {
|
void Abort() noexcept {
|
||||||
h_.promise().Throw(
|
h_.promise().Throw(
|
||||||
std::make_exception_ptr<nf7::Exception>({"coroutine aborted"}));
|
std::make_exception_ptr<CoroutineAbortException>({"coroutine aborted"}));
|
||||||
data_->aborted = true;
|
data_->aborted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,43 +222,126 @@ class Future final {
|
|||||||
}
|
}
|
||||||
Future(Imm&& imm) noexcept : imm_(std::move(imm)) {
|
Future(Imm&& imm) noexcept : imm_(std::move(imm)) {
|
||||||
}
|
}
|
||||||
Future(const Future&) = default;
|
Future(const ThisFuture&) = default;
|
||||||
Future(Future&&) = default;
|
Future(ThisFuture&&) = default;
|
||||||
Future& operator=(const Future&) = default;
|
Future& operator=(const ThisFuture&) = default;
|
||||||
Future& operator=(Future&&) = default;
|
Future& operator=(ThisFuture&&) = default;
|
||||||
|
|
||||||
// Schedules to execute f() immediately on any thread
|
// Schedules to execute f() immediately on any thread
|
||||||
// when the promise is finished or aborted.
|
// 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_) {
|
if (data_) {
|
||||||
std::unique_lock<std::mutex> k(data_->mtx);
|
std::unique_lock<std::mutex> k(data_->mtx);
|
||||||
if (yet()) {
|
if (yet()) {
|
||||||
data_->recv.push_back(
|
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;
|
return *this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
f(*this);
|
fun(*this);
|
||||||
return *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.
|
// same as Then() but called when it's done without error
|
||||||
Future& ThenSub(const std::shared_ptr<nf7::Context>& ctx,
|
ThisFuture& ThenIf(nf7::Env::Executor exec,
|
||||||
std::function<void(Future)>&& f) noexcept {
|
const std::shared_ptr<nf7::Context>& ctx,
|
||||||
if (data_) {
|
std::function<void(const T&)>&& f) noexcept {
|
||||||
std::unique_lock<std::mutex> k(data_->mtx);
|
Then(exec, ctx, [f = std::move(f)](auto& fu) {
|
||||||
if (yet()) {
|
if (fu.done()) f(fu.value());
|
||||||
data_->recv.push_back([d = data_, ctx, f = std::move(f)]() {
|
|
||||||
ctx->env().ExecSub(ctx, std::bind(f, Future(d)));
|
|
||||||
});
|
});
|
||||||
return *this;
|
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));
|
ThisFuture& ThenIf(std::function<void(const T&)>&& f) noexcept {
|
||||||
return *this;
|
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 (imm_) {
|
||||||
if (std::holds_alternative<T>(*imm_)) return std::get<T>(*imm_);
|
if (std::holds_alternative<T>(*imm_)) return std::get<T>(*imm_);
|
||||||
std::rethrow_exception(std::get<std::exception_ptr>(*imm_));
|
std::rethrow_exception(std::get<std::exception_ptr>(*imm_));
|
||||||
@@ -267,11 +364,14 @@ class Future final {
|
|||||||
return !imm_ && data_->state == kYet;
|
return !imm_ && data_->state == kYet;
|
||||||
}
|
}
|
||||||
bool done() const noexcept {
|
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 {
|
bool error() const noexcept {
|
||||||
return (imm_ && std::holds_alternative<std::exception_ptr>(*imm_)) ||
|
return
|
||||||
data_->state == kError;
|
(imm_ && std::holds_alternative<std::exception_ptr>(*imm_)) ||
|
||||||
|
(data_ && data_->state == kError);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool await_ready() const noexcept { return !yet(); }
|
bool await_ready() const noexcept { return !yet(); }
|
||||||
@@ -283,7 +383,6 @@ class Future final {
|
|||||||
|
|
||||||
std::unique_lock<std::mutex> k(data.mtx);
|
std::unique_lock<std::mutex> k(data.mtx);
|
||||||
auto callee_ctx = data.ctx.lock();
|
auto callee_ctx = data.ctx.lock();
|
||||||
assert(callee_ctx);
|
|
||||||
|
|
||||||
auto caller_data = caller.promise().data__();
|
auto caller_data = caller.promise().data__();
|
||||||
auto caller_ctx = caller_data->ctx.lock();
|
auto caller_ctx = caller_data->ctx.lock();
|
||||||
@@ -306,7 +405,7 @@ class Future final {
|
|||||||
caller.resume();
|
caller.resume();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto await_resume() { return value(); }
|
auto& await_resume() { return value(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::optional<Imm> imm_;
|
std::optional<Imm> imm_;
|
||||||
|
|||||||
52
common/generic_config.hh
Normal file
52
common/generic_config.hh
Normal 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
|
||||||
@@ -10,13 +10,18 @@
|
|||||||
|
|
||||||
namespace nf7 {
|
namespace nf7 {
|
||||||
|
|
||||||
class GenericContext : public Context {
|
class GenericContext : public nf7::Context {
|
||||||
public:
|
public:
|
||||||
GenericContext(Env& env, File::Id id, std::string_view desc = "") noexcept :
|
GenericContext(nf7::Env& env,
|
||||||
Context(env, id), desc_(desc) {
|
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(nf7::File& f,
|
||||||
GenericContext(f.env(), f.id(), desc) {
|
std::string_view desc = "",
|
||||||
|
const std::shared_ptr<nf7::Context>& parent = nullptr) noexcept :
|
||||||
|
GenericContext(f.env(), f.id(), desc, parent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void CleanUp() noexcept override {
|
void CleanUp() noexcept override {
|
||||||
|
|||||||
180
common/generic_dir.hh
Normal file
180
common/generic_dir.hh
Normal 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
|
||||||
@@ -7,28 +7,35 @@
|
|||||||
|
|
||||||
#include "nf7.hh"
|
#include "nf7.hh"
|
||||||
|
|
||||||
#include "common/mutable_memento.hh"
|
#include "common/file_base.hh"
|
||||||
|
#include "common/generic_context.hh"
|
||||||
|
#include "common/memento.hh"
|
||||||
|
|
||||||
|
|
||||||
namespace nf7 {
|
namespace nf7 {
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
class GenericMemento : public nf7::MutableMemento {
|
class GenericMemento : public nf7::FileBase::Feature, public nf7::Memento {
|
||||||
public:
|
public:
|
||||||
class CustomTag;
|
class CustomTag;
|
||||||
|
|
||||||
GenericMemento(T&& data, nf7::File* f = nullptr) noexcept :
|
GenericMemento(nf7::FileBase& f, T&& data) noexcept :
|
||||||
|
nf7::FileBase::Feature(f),
|
||||||
file_(f), initial_(T(data)), data_(std::move(data)) {
|
file_(f), initial_(T(data)), data_(std::move(data)) {
|
||||||
}
|
}
|
||||||
GenericMemento(T&& data, nf7::File& f) noexcept :
|
|
||||||
GenericMemento(std::move(data), &f) {
|
|
||||||
}
|
|
||||||
~GenericMemento() noexcept {
|
~GenericMemento() noexcept {
|
||||||
tag_ = nullptr;
|
tag_ = nullptr;
|
||||||
last_ = nullptr;
|
last_ = nullptr;
|
||||||
assert(map_.empty());
|
assert(map_.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
T* operator->() noexcept {
|
||||||
|
return &data_;
|
||||||
|
}
|
||||||
|
const T* operator->() const noexcept {
|
||||||
|
return &data_;
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_ptr<Tag> Save() noexcept override {
|
std::shared_ptr<Tag> Save() noexcept override {
|
||||||
if (tag_) return tag_;
|
if (tag_) return tag_;
|
||||||
auto [itr, emplaced] = map_.emplace(next_++, data_);
|
auto [itr, emplaced] = map_.emplace(next_++, data_);
|
||||||
@@ -43,20 +50,26 @@ class GenericMemento : public nf7::MutableMemento {
|
|||||||
tag_ = tag;
|
tag_ = tag;
|
||||||
last_ = tag;
|
last_ = tag;
|
||||||
onRestore();
|
onRestore();
|
||||||
if (file_) file_->Touch();
|
file_.Touch();
|
||||||
}
|
}
|
||||||
void Commit() noexcept override {
|
void Commit(bool quiet = false) noexcept {
|
||||||
tag_ = nullptr;
|
tag_ = nullptr;
|
||||||
onCommit();
|
onCommit();
|
||||||
if (file_) file_->Touch();
|
if (!quiet) file_.Touch();
|
||||||
}
|
}
|
||||||
void CommitAmend() noexcept override {
|
void CommitQuiet() noexcept {
|
||||||
|
Commit(true);
|
||||||
|
}
|
||||||
|
void CommitAmend(bool quiet = false) noexcept {
|
||||||
if (!tag_) return;
|
if (!tag_) return;
|
||||||
auto itr = map_.find(tag_->id());
|
auto itr = map_.find(tag_->id());
|
||||||
assert(itr != map_.end());
|
assert(itr != map_.end());
|
||||||
itr->second = data_;
|
itr->second = data_;
|
||||||
onCommit();
|
onCommit();
|
||||||
if (file_) file_->Touch();
|
if (!quiet) file_.Touch();
|
||||||
|
}
|
||||||
|
void CommitAmendQuiet() noexcept {
|
||||||
|
CommitAmend(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
T& data() noexcept { return data_; }
|
T& data() noexcept { return data_; }
|
||||||
@@ -74,7 +87,7 @@ class GenericMemento : public nf7::MutableMemento {
|
|||||||
std::function<void()> onCommit = [](){};
|
std::function<void()> onCommit = [](){};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
nf7::File* const file_;
|
nf7::File& file_;
|
||||||
|
|
||||||
const T initial_;
|
const T initial_;
|
||||||
T data_;
|
T data_;
|
||||||
@@ -84,6 +97,19 @@ class GenericMemento : public nf7::MutableMemento {
|
|||||||
|
|
||||||
std::shared_ptr<nf7::Memento::Tag> tag_;
|
std::shared_ptr<nf7::Memento::Tag> tag_;
|
||||||
std::shared_ptr<nf7::Memento::Tag> last_;
|
std::shared_ptr<nf7::Memento::Tag> last_;
|
||||||
|
|
||||||
|
|
||||||
|
void Handle(const nf7::File::Event& e) noexcept override {
|
||||||
|
switch (e.type) {
|
||||||
|
case nf7::File::Event::kAdd:
|
||||||
|
file_.env().ExecMain(
|
||||||
|
std::make_shared<nf7::GenericContext>(file_),
|
||||||
|
[this]() { CommitQuiet(); });
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
|||||||
@@ -14,47 +14,40 @@
|
|||||||
|
|
||||||
namespace nf7 {
|
namespace nf7 {
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
concept GenericTypeInfo_UpdateTooltip_ = requires() { T::UpdateTypeTooltip(); };
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
concept GenericTypeInfo_Description_ = requires() { T::kTypeDescription; };
|
|
||||||
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
class GenericTypeInfo : public nf7::File::TypeInfo {
|
class GenericTypeInfo : public nf7::File::TypeInfo {
|
||||||
public:
|
public:
|
||||||
GenericTypeInfo(const std::string& name, std::unordered_set<std::string>&& v) noexcept :
|
GenericTypeInfo(const std::string& name,
|
||||||
TypeInfo(name, AddFlags(std::move(v))) {
|
std::unordered_set<std::string>&& v,
|
||||||
|
const std::string& desc = "(no description)") noexcept :
|
||||||
|
TypeInfo(name, AddFlags(std::move(v))), desc_(desc) {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<nf7::File> Deserialize(nf7::Deserializer& ar) const override
|
std::unique_ptr<nf7::File> Deserialize(nf7::Deserializer& ar) const override
|
||||||
try {
|
try {
|
||||||
|
if constexpr (std::is_constructible<T, nf7::Deserializer&>::value) {
|
||||||
return std::make_unique<T>(ar);
|
return std::make_unique<T>(ar);
|
||||||
} catch (nf7::Exception&) {
|
} else {
|
||||||
throw nf7::DeserializeException {"deserialization failed"};
|
throw nf7::Exception {name() + " is not a deserializable"};
|
||||||
|
}
|
||||||
} catch (std::exception&) {
|
} catch (std::exception&) {
|
||||||
throw nf7::DeserializeException {"deserialization failed"};
|
throw nf7::DeserializeException {"deserialization failed ("+name()+")"};
|
||||||
}
|
}
|
||||||
std::unique_ptr<File> Create(nf7::Env& env) const override {
|
std::unique_ptr<File> Create(nf7::Env& env) const override {
|
||||||
if constexpr (std::is_constructible<T, nf7::Env&>::value) {
|
if constexpr (std::is_constructible<T, nf7::Env&>::value) {
|
||||||
return std::make_unique<T>(env);
|
return std::make_unique<T>(env);
|
||||||
} else {
|
} else {
|
||||||
throw nf7::Exception {name()+" has no factory without parameters"};
|
throw nf7::Exception {name()+" has no default factory"};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateTooltip() const noexcept override {
|
void UpdateTooltip() const noexcept override {
|
||||||
if constexpr (nf7::GenericTypeInfo_UpdateTooltip_<T>) {
|
ImGui::TextUnformatted(desc_.c_str());
|
||||||
T::UpdateTypeTooltip();
|
|
||||||
} else if constexpr (nf7::GenericTypeInfo_Description_<T>) {
|
|
||||||
ImGui::TextUnformatted(T::kTypeDescription);
|
|
||||||
} else {
|
|
||||||
ImGui::TextUnformatted("(no description)");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
std::string desc_;
|
||||||
|
|
||||||
static std::unordered_set<std::string> AddFlags(
|
static std::unordered_set<std::string> AddFlags(
|
||||||
std::unordered_set<std::string>&& flags) noexcept {
|
std::unordered_set<std::string>&& flags) noexcept {
|
||||||
if (std::is_constructible<T, nf7::Env&>::value) {
|
if (std::is_constructible<T, nf7::Env&>::value) {
|
||||||
|
|||||||
327
common/gl_enum.hh
Normal file
327
common/gl_enum.hh
Normal 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
44
common/gl_fence.hh
Normal 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
457
common/gl_obj.cc
Normal 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
236
common/gl_obj.hh
Normal 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
114
common/gl_shader_preproc.hh
Normal 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
307
common/gui.cc
Normal 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
|
||||||
@@ -1,15 +1,43 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <filesystem>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include <imgui.h>
|
|
||||||
|
|
||||||
#include "nf7.hh"
|
#include "nf7.hh"
|
||||||
|
|
||||||
|
#include "common/config.hh"
|
||||||
|
|
||||||
|
|
||||||
namespace nf7::gui {
|
namespace nf7::gui {
|
||||||
|
|
||||||
|
// widgets
|
||||||
|
void FileMenuItems(nf7::File& f) noexcept;
|
||||||
|
void FileTooltip(nf7::File& f) noexcept;
|
||||||
|
|
||||||
|
bool PathButton (const char* id, nf7::File::Path&, nf7::File&) noexcept;
|
||||||
|
void ContextStack(const nf7::Context&) noexcept;
|
||||||
|
|
||||||
|
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 {
|
inline std::string GetContextDisplayName(const nf7::Context& ctx) noexcept {
|
||||||
auto f = ctx.env().GetFile(ctx.initiator());
|
auto f = ctx.env().GetFile(ctx.initiator());
|
||||||
|
|
||||||
@@ -21,7 +49,6 @@ inline std::string GetContextDisplayName(const nf7::Context& ctx) noexcept {
|
|||||||
|
|
||||||
return initiator + " " + buf;
|
return initiator + " " + buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline std::string GetParentContextDisplayName(const nf7::Context& ctx) noexcept {
|
inline std::string GetParentContextDisplayName(const nf7::Context& ctx) noexcept {
|
||||||
if (auto parent = ctx.parent()) {
|
if (auto parent = ctx.parent()) {
|
||||||
return nf7::gui::GetContextDisplayName(*parent);
|
return nf7::gui::GetContextDisplayName(*parent);
|
||||||
@@ -32,15 +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
|
} // namespace nf7::gui
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
47
common/gui_window.cc
Normal 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
|
||||||
@@ -1,72 +1,54 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <utility>
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include <imgui.h>
|
|
||||||
#include <yas/serialize.hpp>
|
#include <yas/serialize.hpp>
|
||||||
#include <yas/types/utility/usertype.hpp>
|
#include <yas/types/utility/usertype.hpp>
|
||||||
|
|
||||||
#include "nf7.hh"
|
#include "nf7.hh"
|
||||||
|
|
||||||
|
#include "common/file_base.hh"
|
||||||
|
|
||||||
|
|
||||||
namespace nf7::gui {
|
namespace nf7::gui {
|
||||||
|
|
||||||
class Window {
|
class Window : public nf7::FileBase::Feature {
|
||||||
public:
|
public:
|
||||||
static std::string ConcatId(nf7::File& f, const std::string& name) noexcept {
|
static std::string ConcatId(nf7::File& f, const std::string& name) noexcept {
|
||||||
return f.abspath().Stringify() + " | " + std::string {name};
|
return f.abspath().Stringify() + " | " + std::string {name};
|
||||||
}
|
}
|
||||||
|
|
||||||
Window() = delete;
|
Window() = delete;
|
||||||
Window(File& owner, std::string_view title, const gui::Window* src = nullptr) noexcept :
|
Window(nf7::FileBase& owner, std::string_view title) noexcept :
|
||||||
owner_(&owner), title_(title),
|
nf7::FileBase::Feature(owner), owner_(&owner), title_(title), shown_(false) {
|
||||||
shown_(src? src->shown_: false) {
|
|
||||||
}
|
}
|
||||||
Window(const Window&) = delete;
|
Window(const Window&) = delete;
|
||||||
Window(Window&&) = delete;
|
Window(Window&&) = delete;
|
||||||
Window& operator=(const Window&) = delete;
|
Window& operator=(const Window&) = delete;
|
||||||
Window& operator=(Window&&) = delete;
|
Window& operator=(Window&&) = delete;
|
||||||
|
|
||||||
bool Begin() noexcept {
|
void serialize(auto& ar) {
|
||||||
if (std::exchange(set_focus_, false)) {
|
ar(shown_);
|
||||||
ImGui::SetNextWindowFocus();
|
}
|
||||||
|
|
||||||
|
void Show() noexcept {
|
||||||
shown_ = true;
|
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 {
|
void SetFocus() noexcept {
|
||||||
shown_ = true;
|
shown_ = true;
|
||||||
set_focus_ = true;
|
set_focus_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Ar>
|
bool MenuItem() noexcept;
|
||||||
Ar& serialize(Ar& ar) {
|
|
||||||
ar(shown_);
|
|
||||||
return ar;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string id() const noexcept {
|
|
||||||
return ConcatId(*owner_, title_);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool shownInCurrentFrame() const noexcept {
|
|
||||||
return shown_ || set_focus_;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
std::string id() const noexcept { return ConcatId(*owner_, title_); }
|
||||||
bool shown() const noexcept { return shown_; }
|
bool shown() const noexcept { return shown_; }
|
||||||
bool& shown() noexcept { return shown_; }
|
|
||||||
|
std::function<void()> onConfig = [](){};
|
||||||
|
std::function<void()> onUpdate;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
File* const owner_;
|
File* const owner_;
|
||||||
@@ -77,6 +59,10 @@ class Window {
|
|||||||
|
|
||||||
// persistent params
|
// persistent params
|
||||||
bool shown_;
|
bool shown_;
|
||||||
|
|
||||||
|
|
||||||
|
void Handle(const nf7::File::Event&) noexcept override;
|
||||||
|
void Update() noexcept override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace nf7::gui
|
} // namespace nf7::gui
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
@@ -8,38 +9,13 @@
|
|||||||
|
|
||||||
namespace nf7 {
|
namespace nf7 {
|
||||||
|
|
||||||
class LifeExpiredException final : public nf7::Exception {
|
|
||||||
public:
|
|
||||||
using nf7::Exception::Exception;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
class Life final {
|
class Life final {
|
||||||
public:
|
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 {
|
struct Data final {
|
||||||
T* ptr;
|
std::atomic<T*> ptr;
|
||||||
};
|
};
|
||||||
std::shared_ptr<Data> data_;
|
class Ref final {
|
||||||
};
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
class Life<T>::Ref final {
|
|
||||||
public:
|
public:
|
||||||
Ref() = default;
|
Ref() = default;
|
||||||
Ref(const Life& life) noexcept {
|
Ref(const Life& life) noexcept {
|
||||||
@@ -57,7 +33,7 @@ class Life<T>::Ref final {
|
|||||||
|
|
||||||
void EnforceAlive() const {
|
void EnforceAlive() const {
|
||||||
if (!data_->ptr) {
|
if (!data_->ptr) {
|
||||||
throw LifeExpiredException {"target expired"};
|
throw nf7::ExpiredException {"target expired"};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,5 +52,25 @@ class Life<T>::Ref final {
|
|||||||
std::shared_ptr<Data> data_;
|
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
|
} // namespace nf7
|
||||||
|
|||||||
@@ -4,9 +4,10 @@
|
|||||||
#include <exception>
|
#include <exception>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <source_location>
|
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
|
#include <source_location.hh>
|
||||||
|
|
||||||
#include "nf7.hh"
|
#include "nf7.hh"
|
||||||
|
|
||||||
#include "common/file_base.hh"
|
#include "common/file_base.hh"
|
||||||
@@ -17,8 +18,8 @@ namespace nf7 {
|
|||||||
|
|
||||||
class LoggerRef final : public nf7::FileBase::Feature {
|
class LoggerRef final : public nf7::FileBase::Feature {
|
||||||
public:
|
public:
|
||||||
LoggerRef(nf7::File& f, nf7::File::Path&& p = {"_logger"}) noexcept :
|
LoggerRef(nf7::FileBase& f, nf7::File::Path&& p = {"_logger"}) noexcept :
|
||||||
file_(&f), path_(std::move(p)) {
|
nf7::FileBase::Feature(f), file_(&f), path_(std::move(p)) {
|
||||||
}
|
}
|
||||||
LoggerRef(const LoggerRef&) = default;
|
LoggerRef(const LoggerRef&) = default;
|
||||||
LoggerRef(LoggerRef&&) = default;
|
LoggerRef(LoggerRef&&) = default;
|
||||||
|
|||||||
418
common/luajit.cc
418
common/luajit.cc
@@ -7,22 +7,18 @@
|
|||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
#include <lua.hpp>
|
#include <lua.hpp>
|
||||||
|
|
||||||
#include "common/logger.hh"
|
#include "common/logger.hh"
|
||||||
#include "common/logger_ref.hh"
|
#include "common/logger_ref.hh"
|
||||||
#include "common/luajit_thread.hh"
|
#include "common/luajit_thread.hh"
|
||||||
|
#include "common/luajit_std.hh"
|
||||||
|
|
||||||
|
|
||||||
namespace nf7::luajit {
|
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
|
// buffer <-> lua value conversion
|
||||||
template <typename T>
|
template <typename T>
|
||||||
static size_t PushArrayFromBytes(
|
static size_t PushArrayFromBytes(
|
||||||
@@ -33,79 +29,6 @@ template <typename T>
|
|||||||
static size_t ToBytes(lua_State* L, uint8_t* ptr, uint8_t* end);
|
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 {
|
void PushValue(lua_State* L, const nf7::Value& v) noexcept {
|
||||||
new (lua_newuserdata(L, sizeof(v))) nf7::Value(v);
|
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 {
|
struct Visitor final {
|
||||||
lua_State* L;
|
lua_State* L;
|
||||||
const nf7::Value& v;
|
auto operator()(const Value::Pulse&) noexcept { lua_pushnil(L); }
|
||||||
auto operator()(Value::Pulse) noexcept { lua_pushnil(L); }
|
auto operator()(const Value::Boolean& v) noexcept { lua_pushboolean(L, v); }
|
||||||
auto operator()(Value::Boolean) noexcept { lua_pushboolean(L, v.boolean()); }
|
auto operator()(const Value::Integer& v) noexcept { lua_pushinteger(L, v); }
|
||||||
auto operator()(Value::Integer) noexcept { lua_pushinteger(L, v.integer()); }
|
auto operator()(const Value::Scalar& v) noexcept { lua_pushnumber(L, v); }
|
||||||
auto operator()(Value::Scalar) noexcept { lua_pushnumber(L, v.scalar()); }
|
auto operator()(const Value::String& v) noexcept { lua_pushstring(L, v.c_str()); }
|
||||||
auto operator()(Value::String) noexcept { lua_pushstring(L, v.string().c_str()); }
|
auto operator()(const Value::ConstVector& v) noexcept { PushVector(L, v); }
|
||||||
auto operator()(Value::Vector) noexcept { PushVector(L, v.vector()); }
|
auto operator()(const Value::DataPtr&) noexcept { lua_pushnil(L); }
|
||||||
auto operator()(Value::DataPtr) noexcept { lua_pushnil(L); }
|
|
||||||
|
|
||||||
auto operator()(Value::Tuple) noexcept {
|
auto operator()(const Value::ConstTuple& v) noexcept {
|
||||||
const auto& tup = *v.tuple();
|
const auto& tup = *v;
|
||||||
lua_createtable(L, 0, 0);
|
lua_createtable(L, 0, 0);
|
||||||
size_t arridx = 0;
|
size_t arridx = 0;
|
||||||
for (auto& p : tup) {
|
for (auto& p : tup) {
|
||||||
PushValue(L, p.second);
|
PushValue(L, p.second);
|
||||||
if (p.first.empty()) {
|
if (p.first.empty()) {
|
||||||
lua_rawseti(L, -2, static_cast<int>(arridx++));
|
lua_rawseti(L, -2, static_cast<int>(++arridx));
|
||||||
} else {
|
} else {
|
||||||
lua_setfield(L, -2, p.first.c_str());
|
lua_setfield(L, -2, p.first.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
v.Visit(Visitor{.L = L, .v = v});
|
std::visit(Visitor {.L = L}, v.value());
|
||||||
return 1;
|
return 1;
|
||||||
});
|
});
|
||||||
lua_setfield(L, -2, "value");
|
lua_setfield(L, -2, "value");
|
||||||
@@ -160,14 +82,65 @@ void PushValue(lua_State* L, const nf7::Value& v) noexcept {
|
|||||||
}
|
}
|
||||||
lua_setmetatable(L, -2);
|
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 {
|
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);
|
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_createtable(L, 0, 0);
|
||||||
lua_pushcfunction(L, [](auto L) {
|
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);
|
const auto offset = luaL_checkinteger(L, 2);
|
||||||
if (offset < 0) {
|
if (offset < 0) {
|
||||||
return luaL_error(L, "negative offset");
|
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_setfield(L, -2, "get");
|
||||||
|
|
||||||
lua_pushcfunction(L, [](auto L) {
|
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());
|
lua_pushlstring(L, reinterpret_cast<const char*>(v->data()), v->size());
|
||||||
return 1;
|
return 1;
|
||||||
});
|
});
|
||||||
lua_setfield(L, -2, "str");
|
lua_setfield(L, -2, "str");
|
||||||
|
|
||||||
lua_pushcfunction(L, [](auto L) {
|
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()));
|
lua_pushinteger(L, static_cast<lua_Integer>(v->size()));
|
||||||
return 1;
|
return 1;
|
||||||
});
|
});
|
||||||
@@ -262,20 +235,23 @@ void PushVector(lua_State* L, const nf7::Value::ConstVector& v) noexcept {
|
|||||||
lua_setfield(L, -2, "__index");
|
lua_setfield(L, -2, "__index");
|
||||||
|
|
||||||
lua_pushcfunction(L, [](auto L) {
|
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;
|
return 0;
|
||||||
});
|
});
|
||||||
lua_setfield(L, -2, "__gc");
|
lua_setfield(L, -2, "__gc");
|
||||||
}
|
}
|
||||||
lua_setmetatable(L, -2);
|
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_createtable(L, 0, 0);
|
||||||
lua_pushcfunction(L, [](auto L) {
|
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);
|
const lua_Integer offset = luaL_checkinteger(L, 2);
|
||||||
if (offset < 0) return luaL_error(L, "negative offset");
|
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_setfield(L, -2, "set");
|
||||||
|
|
||||||
lua_pushcfunction(L, [](auto L) {
|
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);
|
const lua_Integer size = luaL_checkinteger(L, 2);
|
||||||
if (size < 0) return luaL_error(L, "negative size");
|
if (size < 0) return luaL_error(L, "negative size");
|
||||||
v.resize(static_cast<size_t>(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_setfield(L, -2, "resize");
|
||||||
|
|
||||||
lua_pushcfunction(L, [](auto L) {
|
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 auto dst_off = luaL_checkinteger(L, 2);
|
||||||
|
|
||||||
const std::vector<uint8_t>* src;
|
const T* src;
|
||||||
if (const auto& v = ToVector(L, 3)) {
|
if (const auto& v = ToVector(L, 3)) {
|
||||||
src = &**v;
|
src = &**v;
|
||||||
} else if (const auto& mv = ToMutableVector(L, 3)) {
|
} 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_setfield(L, -2, "__index");
|
||||||
|
|
||||||
lua_pushcfunction(L, [](auto L) {
|
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;
|
return 0;
|
||||||
});
|
});
|
||||||
lua_setfield(L, -2, "__gc");
|
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 {
|
void PushGlobalTable(lua_State* L) noexcept {
|
||||||
if (lua_isnoneornil(L, idx)) {
|
if (luaL_newmetatable(L, "nf7::luajit::GlobalTable")) {
|
||||||
return nf7::Value {nf7::Value::Pulse {}};
|
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)) {
|
void PushImmEnv(lua_State* L) noexcept {
|
||||||
return nf7::Value {bool {!!lua_toboolean(L, idx)}};
|
if (luaL_newmetatable(L, "nf7::luajit::ImmEnv")) {
|
||||||
}
|
|
||||||
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);
|
|
||||||
lua_createtable(L, 0, 0);
|
lua_createtable(L, 0, 0);
|
||||||
{
|
{
|
||||||
lua_pushcfunction(L, [](auto L) {
|
PushGlobalTable(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");
|
|
||||||
}
|
|
||||||
lua_setfield(L, -2, "__index");
|
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);
|
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");
|
|
||||||
}
|
}
|
||||||
lua_setfield(L, -2, "__index");
|
void PushImmTable(lua_State* L) noexcept {
|
||||||
lua_setmetatable(L, -2);
|
if (luaL_newmetatable(L, "nf7::luajit::ImmTable")) {
|
||||||
|
lua_pushcfunction(L, [](auto L) { return luaL_error(L, "table is immutable"); });
|
||||||
|
lua_setfield(L, -2, "__newindex");
|
||||||
}
|
}
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
136
common/luajit.hh
136
common/luajit.hh
@@ -9,22 +9,91 @@
|
|||||||
|
|
||||||
#include <lua.hpp>
|
#include <lua.hpp>
|
||||||
|
|
||||||
|
#include "common/node_root_lambda.hh"
|
||||||
#include "common/value.hh"
|
#include "common/value.hh"
|
||||||
|
|
||||||
|
|
||||||
namespace nf7::luajit {
|
namespace nf7::luajit {
|
||||||
|
|
||||||
void PushGlobalTable(lua_State*) noexcept;
|
// ---- utility
|
||||||
void PushImmEnv(lua_State*) noexcept;
|
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 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> ToValue(lua_State*, int) noexcept;
|
||||||
std::optional<nf7::Value::ConstVector> ToVector(lua_State*, int) noexcept;
|
inline nf7::Value CheckValue(lua_State* L, int idx) {
|
||||||
std::optional<std::vector<uint8_t>> ToMutableVector(lua_State*, int) noexcept;
|
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>
|
template <typename T>
|
||||||
void Push(lua_State* L, T v) noexcept {
|
void Push(lua_State* L, T v) noexcept {
|
||||||
if constexpr (std::is_integral<T>::value) {
|
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 {
|
inline void Push(lua_State* L, std::vector<uint8_t>&& v) noexcept {
|
||||||
luajit::PushMutableVector(L, std::move(v));
|
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 {
|
inline int PushAll(lua_State*) noexcept {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -67,50 +140,9 @@ int PushAll(lua_State* L, T v, Args&&... args) noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
template <typename T>
|
// ---- global table
|
||||||
inline void PushWeakPtr(lua_State* L, const std::weak_ptr<T>& wptr) noexcept {
|
void PushGlobalTable(lua_State*) noexcept;
|
||||||
new (lua_newuserdata(L, sizeof(wptr))) std::weak_ptr<T>(wptr);
|
void PushImmEnv(lua_State*) noexcept;
|
||||||
}
|
void PushImmTable(lua_State*) noexcept;
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace nf7
|
} // namespace nf7
|
||||||
|
|||||||
107
common/luajit_nfile_importer.hh
Normal file
107
common/luajit_nfile_importer.hh
Normal 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
|
||||||
@@ -44,4 +44,8 @@ class Ref final : public nf7::Value::Data {
|
|||||||
int idx_;
|
int idx_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline void Push(lua_State* L, const std::shared_ptr<Ref>& ref) noexcept {
|
||||||
|
ref->PushSelf(L);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace nf7::luajit
|
} // namespace nf7::luajit
|
||||||
|
|||||||
105
common/luajit_std.hh
Normal file
105
common/luajit_std.hh
Normal 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
|
||||||
@@ -1,15 +1,19 @@
|
|||||||
#include "common/luajit_thread.hh"
|
#include "common/luajit_thread.hh"
|
||||||
#include "common/luajit_thread_lambda.hh"
|
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
#include <tracy/Tracy.hpp>
|
||||||
|
|
||||||
|
#include "common/node.hh"
|
||||||
|
#include "common/node_root_lambda.hh"
|
||||||
|
|
||||||
|
|
||||||
namespace nf7::luajit {
|
namespace nf7::luajit {
|
||||||
|
|
||||||
constexpr size_t kInstructionLimit = 10000000;
|
constexpr size_t kInstructionLimit = 100000;
|
||||||
constexpr size_t kBufferSizeMax = 64 * 1024 * 1024;
|
|
||||||
|
|
||||||
|
|
||||||
// Pushes a metatable for Thread object, available on global table as 'nf7'.
|
// Pushes a metatable for Thread object, available on global table as 'nf7'.
|
||||||
@@ -20,9 +24,7 @@ lua_State* Thread::Init(lua_State* L) noexcept {
|
|||||||
assert(state_ == kInitial);
|
assert(state_ == kInitial);
|
||||||
|
|
||||||
th_ = lua_newthread(L);
|
th_ = lua_newthread(L);
|
||||||
PushImmEnv(L);
|
th_ref_.emplace(shared_from_this(), ljq_, L);
|
||||||
lua_setfenv(L, -2);
|
|
||||||
th_ref_.emplace(ctx_, ljq_, L);
|
|
||||||
|
|
||||||
state_ = kPaused;
|
state_ = kPaused;
|
||||||
return th_;
|
return th_;
|
||||||
@@ -33,44 +35,59 @@ void Thread::Resume(lua_State* L, int narg) noexcept {
|
|||||||
if (state_ == kAborted) return;
|
if (state_ == kAborted) return;
|
||||||
assert(L == th_);
|
assert(L == th_);
|
||||||
assert(state_ == kPaused);
|
assert(state_ == kPaused);
|
||||||
(void) L;
|
|
||||||
|
|
||||||
static const auto kHook = [](auto L, auto) {
|
// set global table
|
||||||
luaL_error(L, "reached instruction limit (<=1e7)");
|
PushGlobalTable(L);
|
||||||
};
|
NewUserData<std::weak_ptr<Thread>>(L, weak_from_this());
|
||||||
lua_sethook(th_, kHook, LUA_MASKCOUNT, kInstructionLimit);
|
PushMeta(L);
|
||||||
|
lua_setmetatable(L, -2);
|
||||||
PushGlobalTable(th_);
|
lua_setfield(L, -2, "nf7");
|
||||||
PushWeakPtr(th_, weak_from_this());
|
lua_pop(L, 1);
|
||||||
PushMeta(th_);
|
|
||||||
lua_setmetatable(th_, -2);
|
|
||||||
lua_setfield(th_, -2, "nf7");
|
|
||||||
lua_pop(th_, 1);
|
|
||||||
|
|
||||||
state_ = kRunning;
|
state_ = kRunning;
|
||||||
k.unlock();
|
|
||||||
active_ = true;
|
active_ = true;
|
||||||
const auto ret = lua_resume(th_, narg);
|
yield_ctx_.reset();
|
||||||
active_ = false;
|
|
||||||
|
k.unlock();
|
||||||
|
int ret;
|
||||||
|
{
|
||||||
|
ZoneScopedN("lua_resume");
|
||||||
|
ret = lua_resume(L, narg);
|
||||||
|
}
|
||||||
k.lock();
|
k.lock();
|
||||||
|
|
||||||
|
active_ = false;
|
||||||
if (state_ == kAborted) return;
|
if (state_ == kAborted) return;
|
||||||
switch (ret) {
|
switch (ret) {
|
||||||
case 0:
|
case 0:
|
||||||
|
th_ref_ = std::nullopt;
|
||||||
state_ = kFinished;
|
state_ = kFinished;
|
||||||
break;
|
break;
|
||||||
case LUA_YIELD:
|
case LUA_YIELD:
|
||||||
state_ = kPaused;
|
state_ = kPaused;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
th_ref_ = std::nullopt;
|
||||||
state_ = kAborted;
|
state_ = kAborted;
|
||||||
}
|
}
|
||||||
if (!std::exchange(skip_handle_, false)) {
|
if (!std::exchange(skip_handler_, false)) {
|
||||||
handler_(*this, th_);
|
k.unlock();
|
||||||
|
handler_(*this, L);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void Thread::Abort() noexcept {
|
void Thread::Abort() noexcept {
|
||||||
std::unique_lock<std::mutex> k(mtx_);
|
std::unique_lock<std::mutex> k(mtx_);
|
||||||
state_ = kAborted;
|
state_ = kAborted;
|
||||||
|
th_ref_ = std::nullopt;
|
||||||
|
|
||||||
|
auto wctx = std::move(yield_ctx_);
|
||||||
|
yield_ctx_.reset();
|
||||||
|
k.unlock();
|
||||||
|
if (auto ctx = wctx.lock()) {
|
||||||
|
if (ctx.get() != this) {
|
||||||
|
ctx->Abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -119,26 +136,52 @@ Thread::Handler Thread::CreateNodeLambdaHandler(
|
|||||||
|
|
||||||
static void PushMeta(lua_State* L) noexcept {
|
static void PushMeta(lua_State* L) noexcept {
|
||||||
if (luaL_newmetatable(L, Thread::kTypeName)) {
|
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_setfield(L, -2, "__gc");
|
||||||
|
|
||||||
lua_createtable(L, 0, 0);
|
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)
|
// nf7:resolve(path)
|
||||||
lua_pushcfunction(L, [](auto L) {
|
lua_pushcfunction(L, [](auto L) {
|
||||||
auto th = Thread::GetPtr(L, 1);
|
auto th = Thread::GetPtr(L, 1);
|
||||||
auto base = th->ctx()->initiator();
|
auto base = th->initiator();
|
||||||
|
|
||||||
std::string path = luaL_checkstring(L, 2);
|
std::string path = luaL_checkstring(L, 2);
|
||||||
th->env().ExecSub(th->ctx(), [th, L, base, path = std::move(path)]() {
|
th->env().ExecSub(th, [th, L, base, path = std::move(path)]() {
|
||||||
try {
|
try {
|
||||||
th->ExecResume(L, th->env().GetFileOrThrow(base).ResolveOrThrow(path).id());
|
th->ExecResume(L, th->env().GetFileOrThrow(base).ResolveOrThrow(path).id());
|
||||||
} catch (nf7::File::NotFoundException&) {
|
} catch (nf7::File::NotFoundException&) {
|
||||||
th->ExecResume(L, 0);
|
th->ExecResume(L, 0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
th->ExpectYield(L);
|
return th->Yield(L);
|
||||||
return lua_yield(L, 0);
|
|
||||||
});
|
});
|
||||||
lua_setfield(L, -2, "resolve");
|
lua_setfield(L, -2, "resolve");
|
||||||
|
|
||||||
@@ -147,7 +190,7 @@ static void PushMeta(lua_State* L) noexcept {
|
|||||||
auto th = Thread::GetPtr(L, 1);
|
auto th = Thread::GetPtr(L, 1);
|
||||||
lua_pushvalue(L, 2);
|
lua_pushvalue(L, 2);
|
||||||
|
|
||||||
auto ref = std::make_shared<nf7::luajit::Ref>(th->ctx(), th->ljq(), L);
|
auto ref = std::make_shared<nf7::luajit::Ref>(th, th->ljq(), L);
|
||||||
PushValue(L, nf7::Value {std::move(ref)});
|
PushValue(L, nf7::Value {std::move(ref)});
|
||||||
return 1;
|
return 1;
|
||||||
});
|
});
|
||||||
@@ -159,11 +202,13 @@ static void PushMeta(lua_State* L) noexcept {
|
|||||||
|
|
||||||
const auto id = luaL_checkinteger(L, 2);
|
const auto id = luaL_checkinteger(L, 2);
|
||||||
std::string iface = luaL_checkstring(L, 3);
|
std::string iface = luaL_checkstring(L, 3);
|
||||||
th->env().ExecSub(th->ctx(), [th, L, id, iface = std::move(iface)]() {
|
th->env().ExecSub(th, [th, L, id, iface = std::move(iface)]() {
|
||||||
try {
|
try {
|
||||||
auto& f = th->env().GetFileOrThrow(static_cast<nf7::File::Id>(id));
|
auto& f = th->env().GetFileOrThrow(static_cast<nf7::File::Id>(id));
|
||||||
if (iface == "node") {
|
if (iface == "node") {
|
||||||
Thread::Lambda::CreateAndPush(L, th, f);
|
th->ExecResume(
|
||||||
|
L, nf7::NodeRootLambda::Create(
|
||||||
|
th, f.template interfaceOrThrow<nf7::Node>()));
|
||||||
} else {
|
} else {
|
||||||
throw nf7::Exception {"unknown interface: "+iface};
|
throw nf7::Exception {"unknown interface: "+iface};
|
||||||
}
|
}
|
||||||
@@ -171,21 +216,20 @@ static void PushMeta(lua_State* L) noexcept {
|
|||||||
th->ExecResume(L, nullptr, e.msg());
|
th->ExecResume(L, nullptr, e.msg());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
th->ExpectYield(L);
|
return th->Yield(L);
|
||||||
return lua_yield(L, 0);
|
|
||||||
});
|
});
|
||||||
lua_setfield(L, -2, "query");
|
lua_setfield(L, -2, "query");
|
||||||
|
|
||||||
|
// nf7:sleep(sec)
|
||||||
lua_pushcfunction(L, [](auto L) {
|
lua_pushcfunction(L, [](auto L) {
|
||||||
auto th = Thread::GetPtr(L, 1);
|
auto th = Thread::GetPtr(L, 1);
|
||||||
const auto sec = luaL_checknumber(L, 2);
|
const auto sec = luaL_checknumber(L, 2);
|
||||||
|
|
||||||
const auto time = nf7::Env::Clock::now() +
|
const auto time = nf7::Env::Clock::now() +
|
||||||
std::chrono::milliseconds(static_cast<uint64_t>(sec*1000));
|
std::chrono::milliseconds(static_cast<uint64_t>(sec*1000));
|
||||||
th->ljq()->Push(th->ctx(), [th, L](auto) { th->ExecResume(L); }, time);
|
th->ljq()->Push(th, [th, L](auto) { th->ExecResume(L); }, time);
|
||||||
|
|
||||||
th->ExpectYield(L);
|
return th->Yield(L);
|
||||||
return lua_yield(L, 0);
|
|
||||||
});
|
});
|
||||||
lua_setfield(L, -2, "sleep");
|
lua_setfield(L, -2, "sleep");
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
#include "nf7.hh"
|
#include "nf7.hh"
|
||||||
|
|
||||||
|
#include "common/context_owner.hh"
|
||||||
#include "common/future.hh"
|
#include "common/future.hh"
|
||||||
#include "common/logger_ref.hh"
|
#include "common/logger_ref.hh"
|
||||||
#include "common/luajit.hh"
|
#include "common/luajit.hh"
|
||||||
@@ -23,45 +24,50 @@
|
|||||||
|
|
||||||
namespace nf7::luajit {
|
namespace nf7::luajit {
|
||||||
|
|
||||||
class Thread final : public std::enable_shared_from_this<Thread> {
|
class Thread final : public nf7::Context,
|
||||||
|
public std::enable_shared_from_this<Thread> {
|
||||||
public:
|
public:
|
||||||
static constexpr const char* kTypeName = "nf7::luajit::Thread";
|
static constexpr const char* kTypeName = "nf7::luajit::Thread";
|
||||||
|
|
||||||
enum State { kInitial, kRunning, kPaused, kFinished, kAborted, };
|
enum State { kInitial, kRunning, kPaused, kFinished, kAborted, };
|
||||||
using Handler = std::function<void(Thread&, lua_State*)>;
|
using Handler = std::function<void(Thread&, lua_State*)>;
|
||||||
|
|
||||||
// Registry keeps an objects used in the Thread and deletes immediately when the Thread ends.
|
class Importer;
|
||||||
class RegistryItem;
|
|
||||||
class Lambda;
|
|
||||||
template <typename T> class Lock;
|
|
||||||
|
|
||||||
class Exception final : public nf7::Exception {
|
class Exception final : public nf7::Exception {
|
||||||
public:
|
public:
|
||||||
using nf7::Exception::Exception;
|
using nf7::Exception::Exception;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Creates a handler to finalize a promise.
|
// Creates a handler that finalizes a promise.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
static inline Handler CreatePromiseHandler(
|
static inline Handler CreatePromiseHandler(
|
||||||
nf7::Future<T>::Promise& pro, std::function<T(lua_State*)>&&) noexcept;
|
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(
|
static Handler CreateNodeLambdaHandler(
|
||||||
const std::shared_ptr<nf7::Node::Lambda>& caller,
|
const std::shared_ptr<nf7::Node::Lambda>& caller,
|
||||||
const std::shared_ptr<nf7::Node::Lambda>& callee) noexcept;
|
const std::shared_ptr<nf7::Node::Lambda>& callee) noexcept;
|
||||||
|
|
||||||
// must be called on luajit thread
|
// must be called on luajit thread
|
||||||
static std::shared_ptr<Thread> GetPtr(lua_State* L, int idx) {
|
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);
|
th->EnsureActive(L);
|
||||||
return th;
|
return th;
|
||||||
|
} else {
|
||||||
|
luaL_error(L, "thread expired");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Thread() = delete;
|
Thread() = delete;
|
||||||
Thread(const std::shared_ptr<nf7::Context>& ctx,
|
Thread(const std::shared_ptr<nf7::Context>& parent,
|
||||||
const std::shared_ptr<nf7::luajit::Queue>& ljq,
|
const std::shared_ptr<nf7::luajit::Queue>& ljq,
|
||||||
Handler&& handler) noexcept :
|
Handler&& handler) noexcept :
|
||||||
ctx_(ctx), ljq_(ljq), handler_(std::move(handler)) {
|
nf7::Context(parent->env(), parent->initiator(), parent),
|
||||||
|
ljq_(ljq),
|
||||||
|
handler_(std::move(handler)) {
|
||||||
}
|
}
|
||||||
Thread(const Thread&) = delete;
|
Thread(const Thread&) = delete;
|
||||||
Thread(Thread&&) = delete;
|
Thread(Thread&&) = delete;
|
||||||
@@ -72,6 +78,15 @@ class Thread final : public std::enable_shared_from_this<Thread> {
|
|||||||
assert(state_ == kInitial);
|
assert(state_ == kInitial);
|
||||||
logger_ = logger;
|
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
|
// must be called on luajit thread
|
||||||
lua_State* Init(lua_State* L) noexcept;
|
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;
|
void Resume(lua_State* L, int narg) noexcept;
|
||||||
|
|
||||||
// must be called on luajit thread
|
// must be called on luajit thread
|
||||||
// handler_ won't be called on next yielding
|
// handler_ won't be called on this yielding
|
||||||
void ExpectYield(lua_State*) noexcept {
|
int Yield(lua_State* L, const std::shared_ptr<nf7::Context>& ctx = nullptr) {
|
||||||
skip_handle_ = true;
|
yield_ctx_ = ctx;
|
||||||
|
skip_handler_ = true;
|
||||||
|
return lua_yield(L, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// must be called on luajit thread
|
// 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
|
// thread-safe
|
||||||
void Abort() noexcept;
|
void Abort() noexcept override;
|
||||||
|
|
||||||
// queue a task that exec Resume()
|
// queue a task that exec Resume()
|
||||||
// thread-safe
|
// thread-safe
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
void ExecResume(lua_State* L, Args&&... args) noexcept {
|
void ExecResume(lua_State* L, Args&&... args) noexcept {
|
||||||
auto self = shared_from_this();
|
auto self = shared_from_this();
|
||||||
ljq_->Push(ctx_, [this, L, self, args...](auto) mutable {
|
ljq_->Push(self, [this, L, args...](auto) mutable {
|
||||||
Resume(L, luajit::PushAll(L, std::forward<Args>(args)...));
|
Resume(L, luajit::PushAll(L, std::forward<Args>(args)...));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
nf7::Env& env() noexcept { return ctx_->env(); }
|
|
||||||
const std::shared_ptr<nf7::Context>& ctx() const noexcept { return ctx_; }
|
std::string GetDescription() const noexcept override {
|
||||||
|
return "LuaJIT thread";
|
||||||
|
}
|
||||||
|
|
||||||
const std::shared_ptr<nf7::luajit::Queue>& ljq() const noexcept { return ljq_; }
|
const std::shared_ptr<nf7::luajit::Queue>& ljq() const noexcept { return ljq_; }
|
||||||
const std::shared_ptr<nf7::LoggerRef>& logger() const noexcept { return logger_; }
|
const std::shared_ptr<nf7::LoggerRef>& logger() const noexcept { return logger_; }
|
||||||
|
const std::shared_ptr<Importer>& importer() const noexcept { return importer_; }
|
||||||
State state() const noexcept { return state_; }
|
State state() const noexcept { return state_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// initialized by constructor
|
// initialized by constructor
|
||||||
std::mutex mtx_;
|
std::mutex mtx_;
|
||||||
|
|
||||||
std::shared_ptr<nf7::Context> ctx_;
|
|
||||||
std::shared_ptr<nf7::luajit::Queue> ljq_;
|
std::shared_ptr<nf7::luajit::Queue> ljq_;
|
||||||
|
|
||||||
Handler handler_;
|
Handler handler_;
|
||||||
@@ -141,40 +150,44 @@ class Thread final : public std::enable_shared_from_this<Thread> {
|
|||||||
|
|
||||||
// installed features
|
// installed features
|
||||||
std::shared_ptr<nf7::LoggerRef> logger_;
|
std::shared_ptr<nf7::LoggerRef> logger_;
|
||||||
|
std::shared_ptr<Importer> importer_;
|
||||||
|
|
||||||
|
|
||||||
// mutable params
|
// mutable params
|
||||||
std::vector<std::shared_ptr<RegistryItem>> registry_;
|
|
||||||
|
|
||||||
bool active_ = false; // true while executing lua_resume
|
bool active_ = false; // true while executing lua_resume
|
||||||
bool skip_handle_ = false; // handler_ won't be called on next yield
|
bool skip_handler_ = false;
|
||||||
|
std::weak_ptr<nf7::Context> yield_ctx_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
class Thread::RegistryItem {
|
class Thread::Importer {
|
||||||
public:
|
public:
|
||||||
RegistryItem() = default;
|
Importer() = default;
|
||||||
virtual ~RegistryItem() = default;
|
virtual ~Importer() = default;
|
||||||
RegistryItem(const RegistryItem&) = delete;
|
Importer(const Importer&) = delete;
|
||||||
RegistryItem(RegistryItem&&) = delete;
|
Importer(Importer&&) = delete;
|
||||||
RegistryItem& operator=(const RegistryItem&) = delete;
|
Importer& operator=(const Importer&) = delete;
|
||||||
RegistryItem& operator=(RegistryItem&&) = 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>
|
template <typename T>
|
||||||
Thread::Handler Thread::CreatePromiseHandler(
|
Thread::Handler Thread::CreatePromiseHandler(
|
||||||
nf7::Future<T>::Promise& pro, std::function<T(lua_State*)>&& f) noexcept {
|
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()) {
|
switch (self.state()) {
|
||||||
case kPaused:
|
case kPaused:
|
||||||
pro.Throw(std::make_exception_ptr<nf7::Exception>({"unexpected yield"}));
|
pro.template Throw<nf7::Exception>("unexpected yield");
|
||||||
break;
|
break;
|
||||||
case kFinished:
|
case kFinished:
|
||||||
pro.Wrap([&]() { return f(L); });
|
pro.Wrap([&]() { return f(L); });
|
||||||
break;
|
break;
|
||||||
case kAborted:
|
case kAborted:
|
||||||
pro.Throw(std::make_exception_ptr<nf7::Exception>({lua_tostring(L, -1)}));
|
pro.template Throw<nf7::Exception>(lua_tostring(L, -1));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
assert(false);
|
assert(false);
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -6,12 +6,15 @@
|
|||||||
|
|
||||||
#include "nf7.hh"
|
#include "nf7.hh"
|
||||||
|
|
||||||
|
#include "common/history.hh"
|
||||||
|
|
||||||
|
|
||||||
namespace nf7 {
|
namespace nf7 {
|
||||||
|
|
||||||
class Memento : public File::Interface {
|
class Memento : public File::Interface {
|
||||||
public:
|
public:
|
||||||
class Tag;
|
class Tag;
|
||||||
|
class RestoreCommand;
|
||||||
class CorruptException;
|
class CorruptException;
|
||||||
|
|
||||||
Memento() = default;
|
Memento() = default;
|
||||||
@@ -43,6 +46,33 @@ class Memento::Tag {
|
|||||||
Id id_;
|
Id id_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class Memento::RestoreCommand final : public nf7::History::Command {
|
||||||
|
public:
|
||||||
|
RestoreCommand() = delete;
|
||||||
|
RestoreCommand(Memento& mem,
|
||||||
|
const std::shared_ptr<Tag>& prev,
|
||||||
|
const std::shared_ptr<Tag>& next) noexcept :
|
||||||
|
mem_(mem), prev_(prev), next_(next) {
|
||||||
|
}
|
||||||
|
RestoreCommand(const RestoreCommand&) = delete;
|
||||||
|
RestoreCommand(RestoreCommand&&) = delete;
|
||||||
|
RestoreCommand& operator=(const RestoreCommand&) = delete;
|
||||||
|
RestoreCommand& operator=(RestoreCommand&&) = delete;
|
||||||
|
|
||||||
|
void Apply() override { Exec(); }
|
||||||
|
void Revert() override { Exec(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Memento& mem_;
|
||||||
|
std::shared_ptr<Tag> prev_;
|
||||||
|
std::shared_ptr<Tag> next_;
|
||||||
|
|
||||||
|
void Exec() noexcept {
|
||||||
|
mem_.Restore(next_);
|
||||||
|
std::swap(prev_, next_);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class Memento::CorruptException : public Exception {
|
class Memento::CorruptException : public Exception {
|
||||||
public:
|
public:
|
||||||
using Exception::Exception;
|
using Exception::Exception;
|
||||||
|
|||||||
@@ -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
169
common/mutex.hh
Normal 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
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
namespace nf7 {
|
namespace nf7 {
|
||||||
|
|
||||||
class NativeFile final : public nf7::Context {
|
class NFile final {
|
||||||
public:
|
public:
|
||||||
class Exception final : public nf7::Exception {
|
class Exception final : public nf7::Exception {
|
||||||
using nf7::Exception::Exception;
|
using nf7::Exception::Exception;
|
||||||
@@ -23,17 +23,16 @@ class NativeFile final : public nf7::Context {
|
|||||||
};
|
};
|
||||||
using Flags = uint8_t;
|
using Flags = uint8_t;
|
||||||
|
|
||||||
NativeFile() = delete;
|
NFile() = delete;
|
||||||
NativeFile(nf7::Env& env, nf7::File::Id id,
|
NFile(const std::filesystem::path& path, Flags flags) :
|
||||||
const std::filesystem::path& path, Flags flags) :
|
path_(path), flags_(flags) {
|
||||||
Context(env, id), path_(path), flags_(flags) {
|
|
||||||
Init();
|
Init();
|
||||||
}
|
}
|
||||||
~NativeFile() noexcept;
|
~NFile() noexcept;
|
||||||
NativeFile(const NativeFile&) = delete;
|
NFile(const NFile&) = delete;
|
||||||
NativeFile(NativeFile&&) = delete;
|
NFile(NFile&&) = delete;
|
||||||
NativeFile& operator=(const NativeFile&) = delete;
|
NFile& operator=(const NFile&) = delete;
|
||||||
NativeFile& operator=(NativeFile&&) = delete;
|
NFile& operator=(NFile&&) = delete;
|
||||||
|
|
||||||
size_t Read(size_t offset, uint8_t* buf, size_t size);
|
size_t Read(size_t offset, uint8_t* buf, size_t size);
|
||||||
size_t Write(size_t offset, const uint8_t* buf, size_t size);
|
size_t Write(size_t offset, const uint8_t* buf, size_t size);
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "common/native_file.hh"
|
#include "common/nfile.hh"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
@@ -11,7 +11,7 @@ extern "C" {
|
|||||||
|
|
||||||
namespace nf7 {
|
namespace nf7 {
|
||||||
|
|
||||||
void NativeFile::Init() {
|
void NFile::Init() {
|
||||||
int flags = 0;
|
int flags = 0;
|
||||||
if ((flags_ & kRead) && (flags_ & kWrite)) {
|
if ((flags_ & kRead) && (flags_ & kWrite)) {
|
||||||
flags |= O_RDWR | O_CREAT;
|
flags |= O_RDWR | O_CREAT;
|
||||||
@@ -23,45 +23,45 @@ void NativeFile::Init() {
|
|||||||
|
|
||||||
int fd = open(path_.string().c_str(), flags, 0600);
|
int fd = open(path_.string().c_str(), flags, 0600);
|
||||||
if (fd < 0) {
|
if (fd < 0) {
|
||||||
throw NativeFile::Exception {"open failure"};
|
throw NFile::Exception {"open failure"};
|
||||||
}
|
}
|
||||||
handle_ = static_cast<uint64_t>(fd);
|
handle_ = static_cast<uint64_t>(fd);
|
||||||
}
|
}
|
||||||
NativeFile::~NativeFile() noexcept {
|
NFile::~NFile() noexcept {
|
||||||
const auto fd = static_cast<int>(handle_);
|
const auto fd = static_cast<int>(handle_);
|
||||||
if (close(fd) == -1) {
|
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 fd = static_cast<int>(handle_);
|
||||||
const auto off = static_cast<off_t>(offset);
|
const auto off = static_cast<off_t>(offset);
|
||||||
if (lseek(fd, off, SEEK_SET) == off-1) {
|
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);
|
const auto ret = read(fd, buf, size);
|
||||||
if (ret == -1) {
|
if (ret == -1) {
|
||||||
throw NativeFile::Exception {"read failure"};
|
throw NFile::Exception {"read failure"};
|
||||||
}
|
}
|
||||||
return static_cast<size_t>(ret);
|
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 fd = static_cast<int>(handle_);
|
||||||
const auto off = static_cast<off_t>(offset);
|
const auto off = static_cast<off_t>(offset);
|
||||||
if (lseek(fd, off, SEEK_SET) == off-1) {
|
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);
|
const auto ret = write(fd, buf, size);
|
||||||
if (ret == -1) {
|
if (ret == -1) {
|
||||||
throw nf7::NativeFile::Exception {"write failure"};
|
throw nf7::NFile::Exception {"write failure"};
|
||||||
}
|
}
|
||||||
return static_cast<size_t>(ret);
|
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_);
|
const auto fd = static_cast<int>(handle_);
|
||||||
if (ftruncate(fd, static_cast<off_t>(size)) == 0) {
|
if (ftruncate(fd, static_cast<off_t>(size)) == 0) {
|
||||||
throw nf7::NativeFile::Exception {"ftruncate failure"};
|
throw nf7::NFile::Exception {"ftruncate failure"};
|
||||||
}
|
}
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
59
common/nfile_watcher.hh
Normal file
59
common/nfile_watcher.hh
Normal 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
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "common/native_file.hh"
|
#include "common/nfile.hh"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
@@ -7,7 +7,7 @@ extern "C" {
|
|||||||
|
|
||||||
namespace nf7 {
|
namespace nf7 {
|
||||||
|
|
||||||
void NativeFile::Init() {
|
void NFile::Init() {
|
||||||
DWORD acc = 0;
|
DWORD acc = 0;
|
||||||
DWORD flags = 0;
|
DWORD flags = 0;
|
||||||
if (flags_ & kRead) {
|
if (flags_ & kRead) {
|
||||||
@@ -24,55 +24,55 @@ void NativeFile::Init() {
|
|||||||
path_.string().c_str(),
|
path_.string().c_str(),
|
||||||
acc, 0, nullptr, flags, FILE_ATTRIBUTE_NORMAL, nullptr);
|
acc, 0, nullptr, flags, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||||
if (h == INVALID_HANDLE_VALUE) {
|
if (h == INVALID_HANDLE_VALUE) {
|
||||||
throw NativeFile::Exception {"open failure"};
|
throw NFile::Exception {"open failure"};
|
||||||
}
|
}
|
||||||
handle_ = reinterpret_cast<uintptr_t>(h);
|
handle_ = reinterpret_cast<uintptr_t>(h);
|
||||||
}
|
}
|
||||||
NativeFile::~NativeFile() noexcept {
|
NFile::~NFile() noexcept {
|
||||||
auto h = reinterpret_cast<HANDLE>(handle_);
|
auto h = reinterpret_cast<HANDLE>(handle_);
|
||||||
if (!CloseHandle(h)) {
|
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_);
|
const auto h = reinterpret_cast<HANDLE>(handle_);
|
||||||
|
|
||||||
LONG off_low = offset & 0xFFFFFFFF;
|
LONG off_low = offset & 0xFFFFFFFF;
|
||||||
LONG off_high = offset >> 32;
|
LONG off_high = offset >> 32;
|
||||||
if (INVALID_SET_FILE_POINTER == SetFilePointer(h, off_low, &off_high, FILE_BEGIN)) {
|
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;
|
DWORD ret;
|
||||||
if (!ReadFile(h, buf, static_cast<DWORD>(size), &ret, nullptr)) {
|
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);
|
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_);
|
const auto h = reinterpret_cast<HANDLE>(handle_);
|
||||||
|
|
||||||
LONG off_low = offset & 0xFFFFFFFF;
|
LONG off_low = offset & 0xFFFFFFFF;
|
||||||
LONG off_high = offset >> 32;
|
LONG off_high = offset >> 32;
|
||||||
if (INVALID_SET_FILE_POINTER == SetFilePointer(h, off_low, &off_high, FILE_BEGIN)) {
|
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;
|
DWORD ret;
|
||||||
if (!WriteFile(h, buf, static_cast<DWORD>(size), &ret, nullptr)) {
|
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);
|
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_);
|
const auto h = reinterpret_cast<HANDLE>(handle_);
|
||||||
|
|
||||||
LONG off_low = size & 0xFFFFFFFF;
|
LONG off_low = size & 0xFFFFFFFF;
|
||||||
LONG off_high = size >> 32;
|
LONG off_high = size >> 32;
|
||||||
if (INVALID_SET_FILE_POINTER == SetFilePointer(h, off_low, &off_high, FILE_BEGIN)) {
|
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)) {
|
if (!SetEndOfFile(h)) {
|
||||||
throw NativeFile::Exception {"SetEndOfFile failure"};
|
throw NFile::Exception {"SetEndOfFile failure"};
|
||||||
}
|
}
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
99
common/node.h
Normal file
99
common/node.h
Normal 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
|
||||||
@@ -25,10 +25,38 @@ class Node : public File::Interface {
|
|||||||
kNone = 0,
|
kNone = 0,
|
||||||
kCustomNode = 1 << 0,
|
kCustomNode = 1 << 0,
|
||||||
kMenu = 1 << 1,
|
kMenu = 1 << 1,
|
||||||
kMenu_DirItem = 1 << 2, // use DirItem::UpdateMenu() method instead of Node's
|
|
||||||
};
|
};
|
||||||
using Flags = uint8_t;
|
using Flags = uint8_t;
|
||||||
|
|
||||||
|
struct Meta final {
|
||||||
|
public:
|
||||||
|
Meta() = default;
|
||||||
|
Meta(std::vector<std::string>&& i, std::vector<std::string>&& o) noexcept :
|
||||||
|
inputs(std::move(i)), outputs(std::move(o)) {
|
||||||
|
}
|
||||||
|
Meta(const std::vector<std::string>& i, const std::vector<std::string>& o) noexcept :
|
||||||
|
inputs(i), outputs(o) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Meta(const Meta&) = default;
|
||||||
|
Meta(Meta&&) = default;
|
||||||
|
Meta& operator=(const Meta&) = default;
|
||||||
|
Meta& operator=(Meta&&) = default;
|
||||||
|
|
||||||
|
std::vector<std::string> inputs, outputs;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void ValidateSockets(std::span<const std::string> v) {
|
||||||
|
for (auto itr = v.begin(); itr < v.end(); ++itr) {
|
||||||
|
if (v.end() != std::find(itr+1, v.end(), *itr)) {
|
||||||
|
throw nf7::Exception {"name duplication: "+*itr};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto& s : v) {
|
||||||
|
nf7::File::Path::ValidateTerm(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Node(Flags f) noexcept : flags_(f) { }
|
Node(Flags f) noexcept : flags_(f) { }
|
||||||
Node(const Node&) = default;
|
Node(const Node&) = default;
|
||||||
Node(Node&&) = default;
|
Node(Node&&) = default;
|
||||||
@@ -40,13 +68,12 @@ class Node : public File::Interface {
|
|||||||
virtual void UpdateNode(Editor&) noexcept { }
|
virtual void UpdateNode(Editor&) noexcept { }
|
||||||
virtual void UpdateMenu(Editor&) noexcept { }
|
virtual void UpdateMenu(Editor&) noexcept { }
|
||||||
|
|
||||||
// The returned span is alive until next operation to the file.
|
// don't call too often because causes heap allocation
|
||||||
virtual std::span<const std::string> GetInputs() const noexcept = 0;
|
virtual Meta GetMeta() const noexcept = 0;
|
||||||
virtual std::span<const std::string> GetOutputs() const noexcept = 0;
|
|
||||||
|
|
||||||
Flags flags() const noexcept { return flags_; }
|
Flags flags() const noexcept { return flags_; }
|
||||||
|
|
||||||
protected:
|
private:
|
||||||
Flags flags_;
|
Flags flags_;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -73,6 +100,26 @@ class Node::Editor {
|
|||||||
|
|
||||||
class Node::Lambda : public nf7::Context {
|
class Node::Lambda : public nf7::Context {
|
||||||
public:
|
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(nf7::File& f, const std::shared_ptr<nf7::Context>& parent = nullptr) noexcept :
|
||||||
Lambda(f.env(), f.id(), parent) {
|
Lambda(f.env(), f.id(), parent) {
|
||||||
}
|
}
|
||||||
@@ -81,8 +128,11 @@ class Node::Lambda : public nf7::Context {
|
|||||||
parent_(std::dynamic_pointer_cast<Node::Lambda>(parent)) {
|
parent_(std::dynamic_pointer_cast<Node::Lambda>(parent)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void Handle(
|
virtual void Handle(const Msg&) noexcept {
|
||||||
std::string_view, const nf7::Value&, const std::shared_ptr<Lambda>&) 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(); }
|
std::shared_ptr<Node::Lambda> parent() const noexcept { return parent_.lock(); }
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <span>
|
#include <span>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <unordered_set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <yas/serialize.hpp>
|
#include <yas/serialize.hpp>
|
||||||
@@ -65,6 +66,8 @@ class NodeLinkStore {
|
|||||||
|
|
||||||
inline std::unique_ptr<nf7::History::Command> CreateCommandToRemoveExpired(
|
inline std::unique_ptr<nf7::History::Command> CreateCommandToRemoveExpired(
|
||||||
uint64_t id, std::span<const std::string> in, std::span<const std::string> out) noexcept;
|
uint64_t id, std::span<const std::string> in, std::span<const std::string> out) noexcept;
|
||||||
|
inline std::unique_ptr<nf7::History::Command> CreateCommandToRemoveExpired(
|
||||||
|
const std::unordered_set<uint64_t>& ids) noexcept;
|
||||||
|
|
||||||
std::span<const Link> items() const noexcept { return links_; }
|
std::span<const Link> items() const noexcept { return links_; }
|
||||||
|
|
||||||
@@ -111,7 +114,19 @@ std::unique_ptr<nf7::History::Command> NodeLinkStore::CreateCommandToRemoveExpir
|
|||||||
const bool rm =
|
const bool rm =
|
||||||
(lk.src_id == id && std::find(out.begin(), out.end(), lk.src_name) == out.end()) ||
|
(lk.src_id == id && std::find(out.begin(), out.end(), lk.src_name) == out.end()) ||
|
||||||
(lk.dst_id == id && std::find(in .begin(), in .end(), lk.dst_name) == in .end());
|
(lk.dst_id == id && std::find(in .begin(), in .end(), lk.dst_name) == in .end());
|
||||||
if (rm) cmds.push_back(SwapCommand::CreateToRemove(*this, Link(lk)));
|
if (rm) cmds.push_back(SwapCommand::CreateToRemove(*this, Link {lk}));
|
||||||
|
}
|
||||||
|
if (cmds.empty()) return nullptr;
|
||||||
|
return std::make_unique<nf7::AggregateCommand>(std::move(cmds));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<nf7::History::Command> NodeLinkStore::CreateCommandToRemoveExpired(
|
||||||
|
const std::unordered_set<uint64_t>& ids) noexcept {
|
||||||
|
std::vector<std::unique_ptr<nf7::History::Command>> cmds;
|
||||||
|
for (const auto& lk : links_) {
|
||||||
|
if (!ids.contains(lk.src_id) || !ids.contains(lk.dst_id)) {
|
||||||
|
cmds.push_back(SwapCommand::CreateToRemove(*this, Link {lk}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (cmds.empty()) return nullptr;
|
if (cmds.empty()) return nullptr;
|
||||||
return std::make_unique<nf7::AggregateCommand>(std::move(cmds));
|
return std::make_unique<nf7::AggregateCommand>(std::move(cmds));
|
||||||
|
|||||||
@@ -1,116 +1,94 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <algorithm>
|
#include <cassert>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <unordered_map>
|
#include <unordered_set>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "nf7.hh"
|
#include "nf7.hh"
|
||||||
|
|
||||||
#include "common/future.hh"
|
#include "common/future.hh"
|
||||||
|
#include "common/node.hh"
|
||||||
#include "common/value.hh"
|
#include "common/value.hh"
|
||||||
|
|
||||||
|
|
||||||
namespace nf7 {
|
namespace nf7 {
|
||||||
|
|
||||||
class NodeRootLambda final : public nf7::Node::Lambda,
|
class NodeRootLambda : public nf7::Node::Lambda,
|
||||||
public std::enable_shared_from_this<NodeRootLambda> {
|
public std::enable_shared_from_this<NodeRootLambda> {
|
||||||
public:
|
public:
|
||||||
struct Builder;
|
using Pair = std::pair<std::string, nf7::Value>;
|
||||||
|
|
||||||
NodeRootLambda(const NodeRootLambda&) = delete;
|
static std::shared_ptr<NodeRootLambda> Create(
|
||||||
NodeRootLambda(NodeRootLambda&&) = delete;
|
const std::shared_ptr<nf7::Context>& ctx, nf7::Node& n) noexcept {
|
||||||
NodeRootLambda& operator=(const NodeRootLambda&) = delete;
|
auto ret = std::make_shared<NodeRootLambda>(ctx->env(), ctx->initiator(), ctx);
|
||||||
NodeRootLambda& operator=(NodeRootLambda&&) = delete;
|
ret->target_ = n.CreateLambda(ret);
|
||||||
~NodeRootLambda() noexcept {
|
return ret;
|
||||||
target_ = nullptr;
|
|
||||||
for (auto& pro : pro_) {
|
|
||||||
pro.second.Throw(std::make_exception_ptr(
|
|
||||||
nf7::Exception {"output was never satisified"}));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
using nf7::Node::Lambda::Lambda;
|
||||||
void Handle(std::string_view name, const nf7::Value& v,
|
~NodeRootLambda() noexcept {
|
||||||
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override {
|
Abort();
|
||||||
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_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CheckOutput(std::string_view name) const {
|
void Handle(const nf7::Node::Lambda::Msg& in) noexcept override {
|
||||||
auto out = node_->GetOutputs();
|
std::unique_lock<std::mutex> lk {mtx_};
|
||||||
if (out.end() == std::find(out.begin(), out.end(), name)) {
|
|
||||||
throw nf7::Exception {"required output is missing: "+std::string {name}};
|
if (names_.contains(in.name)) {
|
||||||
|
names_.clear();
|
||||||
|
if (auto pro = std::exchange(pro_, std::nullopt)) {
|
||||||
|
lk.unlock();
|
||||||
|
pro->Return({in.name, in.value});
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
void CheckInput(std::string_view name) const {
|
q_.push_back({in.name, in.value});
|
||||||
auto in = node_->GetInputs();
|
|
||||||
if (in.end() == std::find(in.begin(), in.end(), name)) {
|
|
||||||
throw nf7::Exception {"required input is missing: "+std::string {name}};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nf7::Future<nf7::Value> Receive(const std::string& name) {
|
// thread-safe
|
||||||
assert(!built_);
|
void ExecSend(std::string_view k, const nf7::Value& v) noexcept {
|
||||||
CheckOutput(name);
|
env().ExecSub(shared_from_this(), [this, k = std::string {k}, v = v]() {
|
||||||
auto [itr, added] =
|
target_->Handle(k, v, shared_from_this());
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<NodeRootLambda> Build() noexcept {
|
// thread-safe
|
||||||
assert(!built_);
|
nf7::Future<Pair> Select(std::unordered_set<std::string>&& names) noexcept {
|
||||||
built_ = true;
|
std::unique_lock<std::mutex> k(mtx_);
|
||||||
return prod_;
|
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) {
|
void Abort() noexcept override {
|
||||||
assert(built_);
|
target_->Abort();
|
||||||
CheckInput(name);
|
pro_ = std::nullopt;
|
||||||
target_->Handle(name, v, prod_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool built_ = false;
|
std::mutex mtx_;
|
||||||
|
|
||||||
std::shared_ptr<NodeRootLambda> prod_;
|
|
||||||
|
|
||||||
std::shared_ptr<nf7::Node::Lambda> target_;
|
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
|
} // namespace nf7
|
||||||
|
|||||||
59
common/pure_node_file.hh
Normal file
59
common/pure_node_file.hh
Normal 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
102
common/ring_buffer.hh
Normal 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
|
||||||
@@ -27,6 +27,11 @@ class SquashedHistory : public nf7::GenericHistory {
|
|||||||
if (staged_.size() == 0) {
|
if (staged_.size() == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (++tick_ <= 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
tick_ = 0;
|
||||||
|
|
||||||
nf7::GenericHistory::Add(
|
nf7::GenericHistory::Add(
|
||||||
std::make_unique<nf7::AggregateCommand>(std::move(staged_)));
|
std::make_unique<nf7::AggregateCommand>(std::move(staged_)));
|
||||||
return true;
|
return true;
|
||||||
@@ -48,6 +53,8 @@ class SquashedHistory : public nf7::GenericHistory {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<std::unique_ptr<Command>> staged_;
|
std::vector<std::unique_ptr<Command>> staged_;
|
||||||
|
|
||||||
|
uint8_t tick_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace nf7
|
} // namespace nf7
|
||||||
|
|||||||
52
common/stopwatch.hh
Normal file
52
common/stopwatch.hh
Normal 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
95
common/task.hh
Normal 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
|
||||||
@@ -1,31 +1,37 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#include <tracy/Tracy.hpp>
|
||||||
|
|
||||||
#include "nf7.hh"
|
#include "nf7.hh"
|
||||||
|
|
||||||
|
#include "common/stopwatch.hh"
|
||||||
#include "common/timed_queue.hh"
|
#include "common/timed_queue.hh"
|
||||||
|
|
||||||
|
|
||||||
namespace nf7 {
|
namespace nf7 {
|
||||||
|
|
||||||
// a thread emulation using nf7::Env::ExecAsync
|
// a thread emulation by tasks
|
||||||
template <typename Runner, typename Task>
|
template <typename Runner, typename Task>
|
||||||
class Thread final : public nf7::Context,
|
class Thread final : public nf7::Context,
|
||||||
public std::enable_shared_from_this<Thread<Runner, Task>> {
|
public std::enable_shared_from_this<Thread<Runner, Task>> {
|
||||||
public:
|
public:
|
||||||
|
static constexpr auto kTaskDur = std::chrono::milliseconds {1};
|
||||||
|
|
||||||
Thread() = delete;
|
Thread() = delete;
|
||||||
Thread(nf7::File& f, Runner&& runner) noexcept :
|
Thread(nf7::File& f, Runner&& runner, nf7::Env::Executor exec = nf7::Env::kAsync) noexcept :
|
||||||
Thread(f.env(), f.id(), std::move(runner)) {
|
Thread(f.env(), f.id(), std::move(runner), exec) {
|
||||||
}
|
}
|
||||||
Thread(nf7::Env& env, nf7::File::Id id, Runner&& runner) noexcept :
|
Thread(nf7::Env& env, nf7::File::Id id, Runner&& runner, nf7::Env::Executor exec = nf7::Env::kAsync) noexcept :
|
||||||
nf7::Context(env, id), env_(&env), runner_(std::move(runner)) {
|
nf7::Context(env, id), runner_(std::move(runner)), exec_(exec) {
|
||||||
}
|
}
|
||||||
virtual ~Thread() noexcept = default;
|
|
||||||
Thread(const Thread&) = delete;
|
Thread(const Thread&) = delete;
|
||||||
Thread(Thread&&) = delete;
|
Thread(Thread&&) = delete;
|
||||||
Thread& operator=(const 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 {
|
void Push(const std::shared_ptr<nf7::Context>& ctx, Task&& t, nf7::Env::Time time = {}) noexcept {
|
||||||
q_.Push(time, {ctx, std::move(t)});
|
q_.Push(time, {ctx, std::move(t)});
|
||||||
HandleNext(true /* = first */);
|
ExecNext(true /* = entry */);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetExecutor(nf7::Env::Executor exec) noexcept {
|
||||||
|
exec_ = exec;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t tasksDone() const noexcept { return tasks_done_; }
|
size_t tasksDone() const noexcept { return tasks_done_; }
|
||||||
@@ -41,36 +51,49 @@ class Thread final : public nf7::Context,
|
|||||||
private:
|
private:
|
||||||
using Pair = std::pair<std::shared_ptr<nf7::Context>, Task>;
|
using Pair = std::pair<std::shared_ptr<nf7::Context>, Task>;
|
||||||
|
|
||||||
Env* const env_;
|
|
||||||
Runner runner_;
|
Runner runner_;
|
||||||
|
std::atomic<nf7::Env::Executor> exec_;
|
||||||
|
|
||||||
nf7::TimedQueue<Pair> q_;
|
nf7::TimedQueue<Pair> q_;
|
||||||
|
|
||||||
std::mutex mtx_;
|
std::mutex mtx_;
|
||||||
bool working_ = false;
|
bool working_ = false;
|
||||||
|
nf7::Env::Time scheduled_;
|
||||||
|
|
||||||
std::atomic<size_t> tasks_done_ = 0;
|
std::atomic<size_t> tasks_done_ = 0;
|
||||||
|
|
||||||
|
|
||||||
using std::enable_shared_from_this<Thread<Runner, Task>>::shared_from_this;
|
using std::enable_shared_from_this<Thread<Runner, Task>>::shared_from_this;
|
||||||
void HandleNext(bool first = false) noexcept {
|
void ExecNext(bool entry = false) noexcept {
|
||||||
std::unique_lock<std::mutex> k(mtx_);
|
{
|
||||||
if (std::exchange(working_, true) && first) return;
|
std::unique_lock<std::mutex> k {mtx_};
|
||||||
|
if (std::exchange(working_, true)) return;
|
||||||
|
}
|
||||||
|
|
||||||
auto self = shared_from_this();
|
auto self = shared_from_this();
|
||||||
if (auto p = q_.Pop()) {
|
if (!entry) {
|
||||||
k.unlock();
|
ZoneScopedN("thread task execution");
|
||||||
|
for (nf7::Stopwatch sw; sw.dur() < kTaskDur; ++tasks_done_) {
|
||||||
env_->ExecAsync(p->first, [this, self, t = std::move(p->second)]() mutable {
|
auto t = q_.Pop();
|
||||||
runner_(std::move(t));
|
if (t) {
|
||||||
++tasks_done_;
|
runner_(std::move(t->second));
|
||||||
HandleNext();
|
|
||||||
});
|
|
||||||
} else if (auto time = q_.next()) {
|
|
||||||
working_ = false;
|
|
||||||
env_->ExecAsync(
|
|
||||||
shared_from_this(), [this, self]() mutable { HandleNext(); }, *time);
|
|
||||||
} else {
|
} 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;
|
working_ = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ class TimedQueue {
|
|||||||
return ret.task;
|
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::optional<nf7::Env::Time> next() const noexcept {
|
||||||
std::unique_lock<std::mutex> k(mtx_);
|
std::unique_lock<std::mutex> k(mtx_);
|
||||||
return next_();
|
return next_();
|
||||||
@@ -71,41 +76,4 @@ class TimedQueue {
|
|||||||
std::priority_queue<Item, std::vector<Item>, Comp> q_;
|
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
|
} // namespace nf7
|
||||||
|
|||||||
23
common/util_algorithm.hh
Normal file
23
common/util_algorithm.hh
Normal 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
|
||||||
@@ -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
|
|
||||||
203
common/value.hh
203
common/value.hh
@@ -62,31 +62,26 @@ class Value {
|
|||||||
Value& operator=(Scalar v) noexcept { value_ = v; return *this; }
|
Value& operator=(Scalar v) noexcept { value_ = v; return *this; }
|
||||||
Value(Boolean v) noexcept : value_(v) { }
|
Value(Boolean v) noexcept : value_(v) { }
|
||||||
Value& operator=(Boolean v) noexcept { value_ = v; return *this; }
|
Value& operator=(Boolean v) noexcept { value_ = v; return *this; }
|
||||||
|
|
||||||
Value(std::string_view v) noexcept : value_(std::string {v}) { }
|
Value(std::string_view v) noexcept : value_(std::string {v}) { }
|
||||||
Value& operator=(std::string_view v) noexcept { value_ = std::string(v); return *this; }
|
Value& operator=(std::string_view v) noexcept { value_ = std::string(v); return *this; }
|
||||||
Value(String&& v) noexcept : value_(std::move(v)) { }
|
Value(String&& v) noexcept : value_(std::move(v)) { }
|
||||||
Value& operator=(String&& v) noexcept { value_ = std::move(v); return *this; }
|
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(const Vector& v) noexcept : value_(v? v: std::make_shared<std::vector<uint8_t>>()) { }
|
||||||
Value(Vector&& v) noexcept { value_ = std::move(v); }
|
Value& operator=(const Vector& v) noexcept { value_ = v? v: std::make_shared<std::vector<uint8_t>>(); return *this; }
|
||||||
Value& operator=(Vector&& v) noexcept { value_ = std::move(v); 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(const ConstVector& v) noexcept { value_ = std::const_pointer_cast<std::vector<uint8_t>>(v); }
|
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& 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(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& 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(const Tuple& v) noexcept : value_(v? v: std::make_shared<std::vector<TuplePair>>()) { }
|
||||||
Value(Tuple&& v) noexcept : value_(std::move(v)) { }
|
Value& operator=(const Tuple& v) noexcept { value_ = v? v: std::make_shared<std::vector<TuplePair>>(); return *this; }
|
||||||
Value& operator=(Tuple&& v) noexcept { value_ = std::move(v); return *this; }
|
Value(const ConstTuple& v) noexcept : value_(v? v: std::make_shared<std::vector<TuplePair>>()) { }
|
||||||
Value(std::vector<TuplePair>&& p) noexcept { value_ = std::make_shared<std::vector<TuplePair>>(std::move(p)); }
|
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& 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 {
|
Value(std::vector<nf7::Value>&& v) noexcept { *this = std::move(v); }
|
||||||
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& operator=(std::vector<nf7::Value>&& v) noexcept {
|
Value& operator=(std::vector<nf7::Value>&& v) noexcept {
|
||||||
std::vector<TuplePair> pairs;
|
std::vector<TuplePair> pairs;
|
||||||
pairs.reserve(v.size());
|
pairs.reserve(v.size());
|
||||||
@@ -95,46 +90,69 @@ class Value {
|
|||||||
value_ = std::make_shared<std::vector<TuplePair>>(std::move(pairs));
|
value_ = std::make_shared<std::vector<TuplePair>>(std::move(pairs));
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
Value(const DataPtr& v) noexcept : value_(v) { }
|
Value(const DataPtr& v) noexcept : value_(v) { }
|
||||||
Value& operator=(const DataPtr& v) noexcept { value_ = v; return *this; }
|
Value& operator=(const DataPtr& v) noexcept { value_ = v; return *this; }
|
||||||
Value(DataPtr&& v) noexcept : value_(std::move(v)) { }
|
Value(DataPtr&& v) noexcept : value_(std::move(v)) { }
|
||||||
Value& operator=(DataPtr&& v) noexcept { value_ = std::move(v); return *this; }
|
Value& operator=(DataPtr&& v) noexcept { value_ = std::move(v); return *this; }
|
||||||
|
|
||||||
auto Visit(auto visitor) const noexcept {
|
|
||||||
return std::visit(visitor, value_);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isPulse() const noexcept { return std::holds_alternative<Pulse>(value_); }
|
bool isPulse() const noexcept { return std::holds_alternative<Pulse>(value_); }
|
||||||
bool isBoolean() const noexcept { return std::holds_alternative<Boolean>(value_); }
|
bool isBoolean() const noexcept { return std::holds_alternative<Boolean>(value_); }
|
||||||
bool isInteger() const noexcept { return std::holds_alternative<Integer>(value_); }
|
bool isInteger() const noexcept { return std::holds_alternative<Integer>(value_); }
|
||||||
bool isScalar() const noexcept { return std::holds_alternative<Scalar>(value_); }
|
bool isScalar() const noexcept { return std::holds_alternative<Scalar>(value_); }
|
||||||
bool isString() const noexcept { return std::holds_alternative<String>(value_); }
|
bool isString() const noexcept { return std::holds_alternative<String>(value_); }
|
||||||
bool isVector() const noexcept { return std::holds_alternative<Vector>(value_); }
|
bool isVector() const noexcept { return std::holds_alternative<ConstVector>(value_); }
|
||||||
bool isTuple() const noexcept { return std::holds_alternative<Tuple>(value_); }
|
bool isTuple() const noexcept { return std::holds_alternative<ConstTuple>(value_); }
|
||||||
bool isData() const noexcept { return std::holds_alternative<DataPtr>(value_); }
|
bool isData() const noexcept { return std::holds_alternative<DataPtr>(value_); }
|
||||||
|
|
||||||
|
// direct accessors
|
||||||
Integer integer() const { return get<Integer>(); }
|
Integer integer() const { return get<Integer>(); }
|
||||||
Boolean boolean() const { return get<Boolean>(); }
|
Boolean boolean() const { return get<Boolean>(); }
|
||||||
Scalar scalar() const { return get<Scalar>(); }
|
Scalar scalar() const { return get<Scalar>(); }
|
||||||
const String& string() const { return get<String>(); }
|
const String& string() const { return get<String>(); }
|
||||||
const ConstVector vector() const { return get<Vector>(); }
|
const ConstVector& vector() const { return get<ConstVector>(); }
|
||||||
const ConstTuple tuple() const { return get<Tuple>(); }
|
const ConstTuple& tuple() const { return get<ConstTuple>(); }
|
||||||
const DataPtr& data() const { return get<DataPtr>(); }
|
const DataPtr& data() const { return get<DataPtr>(); }
|
||||||
|
const auto& value() const noexcept { return value_; }
|
||||||
|
|
||||||
template <typename I>
|
// direct reference accessor
|
||||||
I integer() const {
|
Integer& integer() { return get<Integer>(); }
|
||||||
const auto ret = integer();
|
Boolean& boolean() { return get<Boolean>(); }
|
||||||
if constexpr (std::is_unsigned<I>::value) {
|
Scalar& scalar() { return get<Scalar>(); }
|
||||||
if (ret < 0) {
|
String& string() { return get<String>(); }
|
||||||
throw IncompatibleException("integer underflow");
|
|
||||||
|
// conversion accessor
|
||||||
|
template <typename N>
|
||||||
|
N integer() const {
|
||||||
|
return SafeCast<N>(integer());
|
||||||
}
|
}
|
||||||
} else {
|
template <typename N>
|
||||||
if (ret != static_cast<Integer>(static_cast<I>(ret))) {
|
N scalar() const {
|
||||||
throw IncompatibleException("integer out of range");
|
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 {
|
const Value& tuple(size_t idx) const {
|
||||||
auto& tup = *tuple();
|
auto& tup = *tuple();
|
||||||
return idx < tup.size()? tup[idx].second:
|
return idx < tup.size()? tup[idx].second:
|
||||||
@@ -147,19 +165,24 @@ class Value {
|
|||||||
return itr < tup.end()? itr->second:
|
return itr < tup.end()? itr->second:
|
||||||
throw IncompatibleException("unknown tuple field: "+std::string {name});
|
throw IncompatibleException("unknown tuple field: "+std::string {name});
|
||||||
}
|
}
|
||||||
template <typename T>
|
Value tupleOr(auto idx, const Value& v) const noexcept {
|
||||||
std::shared_ptr<T> data() const {
|
try {
|
||||||
if (auto ptr = std::dynamic_pointer_cast<T>(data())) return ptr;
|
return tuple(idx);
|
||||||
throw IncompatibleException("data pointer downcast failure");
|
} catch (nf7::Exception&) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Integer& integer() { return get<Integer>(); }
|
// extended accessor
|
||||||
Boolean& boolean() { return get<Boolean>(); }
|
nf7::File& file(const nf7::File& base) const {
|
||||||
Scalar& scalar() { return get<Scalar>(); }
|
if (isInteger()) {
|
||||||
String& string() { return get<String>(); }
|
return base.env().GetFileOrThrow(integerOrScalar<nf7::File::Id>());
|
||||||
|
} else if (isString()) {
|
||||||
Vector vectorUniq() { return getUniq<Vector>(); }
|
return base.ResolveOrThrow(string());
|
||||||
Tuple tupleUniq() { return getUniq<Tuple>(); }
|
} else {
|
||||||
|
throw IncompatibleException {"expected file id or file path"};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const char* typeName() const noexcept {
|
const char* typeName() const noexcept {
|
||||||
struct Visitor final {
|
struct Visitor final {
|
||||||
@@ -169,11 +192,11 @@ class Value {
|
|||||||
auto operator()(Integer) noexcept { return "integer"; }
|
auto operator()(Integer) noexcept { return "integer"; }
|
||||||
auto operator()(Scalar) noexcept { return "scalar"; }
|
auto operator()(Scalar) noexcept { return "scalar"; }
|
||||||
auto operator()(String) noexcept { return "string"; }
|
auto operator()(String) noexcept { return "string"; }
|
||||||
auto operator()(Vector) noexcept { return "vector"; }
|
auto operator()(ConstVector) noexcept { return "vector"; }
|
||||||
auto operator()(Tuple) noexcept { return "tuple"; }
|
auto operator()(ConstTuple) noexcept { return "tuple"; }
|
||||||
auto operator()(DataPtr) noexcept { return "data"; }
|
auto operator()(DataPtr) noexcept { return "data"; }
|
||||||
};
|
};
|
||||||
return Visit(Visitor{});
|
return std::visit(Visitor {}, value_);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Ar>
|
template <typename Ar>
|
||||||
@@ -183,34 +206,44 @@ class Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
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>
|
template <typename T>
|
||||||
const T& get() const
|
const T& get() const {
|
||||||
try {
|
return const_cast<Value&>(*this).get<T>();
|
||||||
return std::get<T>(value_);
|
|
||||||
} catch (std::bad_variant_access&) {
|
|
||||||
throw IncompatibleException(
|
|
||||||
std::string{"expected "}+typeid(T).name()+" but it's "+typeName());
|
|
||||||
}
|
}
|
||||||
template <typename T>
|
template <typename T>
|
||||||
T& get()
|
T& get()
|
||||||
try {
|
try {
|
||||||
return std::get<T>(value_);
|
return std::get<T>(value_);
|
||||||
} catch (std::bad_variant_access&) {
|
} catch (std::bad_variant_access&) {
|
||||||
throw IncompatibleException(
|
std::stringstream st;
|
||||||
std::string{"expected "}+typeid(T).name()+" but it's "+typeName());
|
st << "expected " << typeid(T).name() << " but it's " << typeName();
|
||||||
|
throw IncompatibleException(st.str());
|
||||||
}
|
}
|
||||||
template <typename T>
|
|
||||||
T getUniq() {
|
template <typename R, typename N>
|
||||||
auto v = std::move(get<T>());
|
static R SafeCast(N in) {
|
||||||
if (v.use_count() == 1) {
|
const auto ret = static_cast<R>(in);
|
||||||
return v;
|
const auto retn = static_cast<N>(ret);
|
||||||
} else {
|
if constexpr (std::is_unsigned<R>::value) {
|
||||||
return std::make_shared<typename T::element_type>(*v);
|
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 {
|
class Value::Data {
|
||||||
@@ -261,6 +294,22 @@ struct serializer<
|
|||||||
throw nf7::DeserializeException("cannot deserialize Value::Vector");
|
throw nf7::DeserializeException("cannot deserialize Value::Vector");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
template <size_t F>
|
||||||
|
struct serializer<
|
||||||
|
type_prop::not_a_fundamental,
|
||||||
|
ser_case::use_internal_serializer,
|
||||||
|
F,
|
||||||
|
nf7::Value::ConstVector> {
|
||||||
|
public:
|
||||||
|
template <typename Archive>
|
||||||
|
static Archive& save(Archive&, const nf7::Value::ConstVector&) {
|
||||||
|
throw nf7::Exception("cannot serialize Value::Vector");
|
||||||
|
}
|
||||||
|
template <typename Archive>
|
||||||
|
static Archive& load(Archive&, nf7::Value::ConstVector&) {
|
||||||
|
throw nf7::DeserializeException("cannot deserialize Value::Vector");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
template <size_t F>
|
template <size_t F>
|
||||||
struct serializer<
|
struct serializer<
|
||||||
@@ -280,6 +329,26 @@ struct serializer<
|
|||||||
return ar;
|
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>
|
template <size_t F>
|
||||||
struct serializer<
|
struct serializer<
|
||||||
|
|||||||
26
common/yaml_nf7.hh
Normal file
26
common/yaml_nf7.hh
Normal 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
|
||||||
@@ -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
42
common/yas_enum.hh
Normal 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
|
||||||
@@ -30,11 +30,10 @@ struct serializer<
|
|||||||
static Archive& load(Archive& ar, std::unique_ptr<nf7::File>& f) {
|
static Archive& load(Archive& ar, std::unique_ptr<nf7::File>& f) {
|
||||||
std::string name;
|
std::string name;
|
||||||
ar(name);
|
ar(name);
|
||||||
auto& type = nf7::File::registry(name);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
typename Archive::ChunkGuard guard {ar};
|
typename Archive::ChunkGuard guard {ar};
|
||||||
f = type.Deserialize(ar);
|
f = nf7::File::registry(name).Deserialize(ar);
|
||||||
|
|
||||||
guard.ValidateEnd();
|
guard.ValidateEnd();
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
f = nullptr;
|
f = nullptr;
|
||||||
@@ -44,29 +43,6 @@ struct serializer<
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <size_t F>
|
|
||||||
struct serializer<
|
|
||||||
type_prop::not_a_fundamental,
|
|
||||||
ser_case::use_internal_serializer,
|
|
||||||
F,
|
|
||||||
std::shared_ptr<nf7::File>> {
|
|
||||||
public:
|
|
||||||
template <typename Archive>
|
|
||||||
static Archive& save(Archive& ar, const std::shared_ptr<nf7::File>& f) {
|
|
||||||
std::unique_ptr<nf7::File> uf(f.get());
|
|
||||||
ar(uf);
|
|
||||||
uf.release();
|
|
||||||
return ar;
|
|
||||||
}
|
|
||||||
template <typename Archive>
|
|
||||||
static Archive& load(Archive& ar, std::shared_ptr<nf7::File>& f) {
|
|
||||||
std::unique_ptr<nf7::File> uf;
|
|
||||||
ar(uf);
|
|
||||||
f = std::move(uf);
|
|
||||||
return ar;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <size_t F>
|
template <size_t F>
|
||||||
struct serializer<
|
struct serializer<
|
||||||
type_prop::not_a_fundamental,
|
type_prop::not_a_fundamental,
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
|
#include <algorithm>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <cinttypes>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <typeinfo>
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
||||||
#include <miniaudio.h>
|
#include <miniaudio.h>
|
||||||
|
|
||||||
|
#include <tracy/Tracy.hpp>
|
||||||
|
|
||||||
#include "nf7.hh"
|
#include "nf7.hh"
|
||||||
|
|
||||||
#include "common/audio_queue.hh"
|
#include "common/audio_queue.hh"
|
||||||
#include "common/dir_item.hh"
|
#include "common/dir_item.hh"
|
||||||
#include "common/generic_context.hh"
|
|
||||||
#include "common/generic_type_info.hh"
|
#include "common/generic_type_info.hh"
|
||||||
#include "common/ptr_selector.hh"
|
#include "common/ptr_selector.hh"
|
||||||
#include "common/thread.hh"
|
#include "common/thread.hh"
|
||||||
@@ -20,19 +25,15 @@ namespace {
|
|||||||
class AudioContext final : public nf7::File, public nf7::DirItem {
|
class AudioContext final : public nf7::File, public nf7::DirItem {
|
||||||
public:
|
public:
|
||||||
static inline const nf7::GenericTypeInfo<AudioContext> kType = {
|
static inline const nf7::GenericTypeInfo<AudioContext> kType = {
|
||||||
"Audio/Context", {"nf7::DirItem",}};
|
"Audio/Context", {"nf7::DirItem",}, "drives miniaudio context"};
|
||||||
static void UpdateTypeTooltip() noexcept {
|
|
||||||
ImGui::TextUnformatted("Drives miniaudio context.");
|
|
||||||
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::audio::Queue");
|
|
||||||
ImGui::Bullet(); ImGui::TextUnformatted(
|
|
||||||
"there's no merit to use multiple contexts");
|
|
||||||
ImGui::Bullet(); ImGui::TextUnformatted(
|
|
||||||
"the context remains alive after file deletion until unused");
|
|
||||||
}
|
|
||||||
|
|
||||||
class Queue;
|
class Queue;
|
||||||
|
|
||||||
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()) {
|
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);
|
return std::make_unique<AudioContext>(env);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Update() noexcept override;
|
|
||||||
void UpdateMenu() noexcept override;
|
void UpdateMenu() noexcept override;
|
||||||
void UpdateTooltip() 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 {
|
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
|
||||||
return nf7::InterfaceSelector<
|
return nf7::InterfaceSelector<
|
||||||
nf7::DirItem, nf7::audio::Queue>(t).Select(this, q_.get());
|
nf7::DirItem, nf7::audio::Queue>(t).Select(this, q_.get());
|
||||||
@@ -53,176 +55,134 @@ class AudioContext final : public nf7::File, public nf7::DirItem {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<Queue> q_;
|
std::shared_ptr<Queue> q_;
|
||||||
|
|
||||||
const char* popup_ = nullptr;
|
|
||||||
|
|
||||||
|
|
||||||
// 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,
|
class AudioContext::Queue final : public nf7::audio::Queue,
|
||||||
public std::enable_shared_from_this<AudioContext::Queue> {
|
public std::enable_shared_from_this<AudioContext::Queue> {
|
||||||
public:
|
public:
|
||||||
struct Runner final {
|
struct SharedData {
|
||||||
Runner(Queue& owner) noexcept : owner_(&owner) {
|
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) {
|
void operator()(Task&& t) {
|
||||||
t(owner_->ctx_.get());
|
if (!data_->broken) {
|
||||||
|
ZoneScopedN("audio task");
|
||||||
|
t(&data_->ctx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
Queue* const owner_;
|
std::shared_ptr<SharedData> data_;
|
||||||
};
|
};
|
||||||
using Thread = nf7::Thread<Runner, Task>;
|
using Thread = nf7::Thread<Runner, Task>;
|
||||||
|
|
||||||
enum State {
|
|
||||||
kInitializing,
|
|
||||||
kReady,
|
|
||||||
kBroken,
|
|
||||||
};
|
|
||||||
|
|
||||||
Queue() = delete;
|
Queue() = delete;
|
||||||
Queue(AudioContext& f) noexcept :
|
Queue(AudioContext& f) noexcept :
|
||||||
env_(&f.env()), th_(std::make_shared<Thread>(f, Runner {*this})) {
|
env_(&f.env()),
|
||||||
}
|
data_(std::make_shared<SharedData>()),
|
||||||
~Queue() noexcept {
|
th_(std::make_shared<Thread>(f, Runner {data_})) {
|
||||||
th_->Push(
|
th_->Push(th_, [data = data_](auto) {
|
||||||
std::make_shared<nf7::GenericContext>(*env_, 0, "deleting ma_context"),
|
if (MA_SUCCESS != ma_context_init(nullptr, 0, nullptr, &data->ctx)) {
|
||||||
[ctx = std::move(ctx_)](auto) { if (ctx) ma_context_uninit(ctx.get()); }
|
data->broken = true;
|
||||||
);
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
~Queue() noexcept {
|
||||||
|
th_->Push(th_, [](auto ma) { ma_context_uninit(ma); });
|
||||||
|
}
|
||||||
|
|
||||||
void Push(const std::shared_ptr<nf7::Context>& ctx, Task&& task) noexcept override {
|
void Push(const std::shared_ptr<nf7::Context>& ctx, Task&& task) noexcept override {
|
||||||
th_->Push(ctx, std::move(task));
|
th_->Push(ctx, std::move(task));
|
||||||
}
|
}
|
||||||
std::shared_ptr<audio::Queue> self() noexcept override { return shared_from_this(); }
|
std::shared_ptr<audio::Queue> self() noexcept override { return shared_from_this(); }
|
||||||
|
|
||||||
State state() const noexcept { return state_; }
|
bool broken() const noexcept { return data_->broken; }
|
||||||
size_t tasksDone() const noexcept { return th_->tasksDone(); }
|
size_t tasksDone() const noexcept { return th_->tasksDone(); }
|
||||||
|
|
||||||
|
// thread-safe
|
||||||
|
ma_context* ctx() const noexcept {
|
||||||
|
return broken()? nullptr: &data_->ctx;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Env* const env_;
|
Env* const env_;
|
||||||
|
|
||||||
|
std::shared_ptr<SharedData> data_;
|
||||||
std::shared_ptr<Thread> th_;
|
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 {
|
void AudioContext::UpdateMenu() noexcept {
|
||||||
if (ImGui::MenuItem("display available devices")) {
|
ma_device_info* pbs;
|
||||||
popup_ = "DeviceList";
|
ma_uint32 pbn;
|
||||||
|
ma_device_info* cps;
|
||||||
|
ma_uint32 cpn;
|
||||||
|
if (ImGui::BeginMenu("playback devices")) {
|
||||||
|
auto ma = q_->ctx();
|
||||||
|
if (MA_SUCCESS == ma_context_get_devices(ma, &pbs, &pbn, &cps, &cpn)) {
|
||||||
|
UpdateDeviceListMenu(pbs, pbn);
|
||||||
|
} else {
|
||||||
|
ImGui::MenuItem("fetch failure... ;(", nullptr, false, false);
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
if (ImGui::BeginMenu("capture devices")) {
|
||||||
|
auto ma = q_->ctx();
|
||||||
|
if (MA_SUCCESS == ma_context_get_devices(ma, &pbs, &pbn, &cps, &cpn)) {
|
||||||
|
UpdateDeviceListMenu(cps, cpn);
|
||||||
|
} else {
|
||||||
|
ImGui::MenuItem("fetch failure... ;(", nullptr, false, false);
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
void AudioContext::UpdateDeviceListMenu(ma_device_info* ptr, ma_uint32 n) noexcept {
|
||||||
void AudioContext::UpdateTooltip() noexcept {
|
for (ma_uint32 i = 0; i < n; ++i) {
|
||||||
const auto state = q_->state();
|
const auto name = std::to_string(i) + ": " + ptr[i].name;
|
||||||
const char* state_str =
|
if (ImGui::MenuItem(name.c_str())) {
|
||||||
state == Queue::kInitializing? "initializing":
|
ImGui::SetClipboardText(ptr[i].name);
|
||||||
state == Queue::kReady ? "ready":
|
|
||||||
state == Queue::kBroken ? "broken": "unknown";
|
|
||||||
ImGui::Text("state: %s", state_str);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioContext::UpdateDeviceList(const ma_device_info* p, size_t n) noexcept {
|
|
||||||
for (size_t i = 0; i < n; ++i) {
|
|
||||||
const auto& info = p[i];
|
|
||||||
const auto name = std::to_string(i) + ": " + info.name;
|
|
||||||
ImGui::Selectable(name.c_str(), false, ImGuiSelectableFlags_DontClosePopups);
|
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ImGui::IsItemHovered()) {
|
||||||
ImGui::BeginTooltip();
|
ImGui::BeginTooltip();
|
||||||
ImGui::Text("index : %zu", i);
|
|
||||||
ImGui::Text("name : %s", info.name);
|
ImGui::Text("index : %" PRIu32, i);
|
||||||
ImGui::Text("default : %s", info.isDefault? "true": "false");
|
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();
|
ImGui::EndTooltip();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AudioContext::UpdateTooltip() noexcept {
|
||||||
|
ImGui::Text("state: %s", q_->broken()? "broken": "running");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
} // namespace nf7
|
} // namespace nf7
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
116
file/font_context.cc
Normal file
116
file/font_context.cc
Normal 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
247
file/font_face.cc
Normal 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
1452
file/gl_obj.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,106 +1,196 @@
|
|||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <lua.hpp>
|
#include <lua.hpp>
|
||||||
|
|
||||||
|
#include <tracy/Tracy.hpp>
|
||||||
|
|
||||||
#include "nf7.hh"
|
#include "nf7.hh"
|
||||||
|
|
||||||
#include "common/dir_item.hh"
|
#include "common/dir_item.hh"
|
||||||
|
#include "common/file_base.hh"
|
||||||
#include "common/generic_context.hh"
|
#include "common/generic_context.hh"
|
||||||
#include "common/generic_type_info.hh"
|
#include "common/generic_type_info.hh"
|
||||||
|
#include "common/logger_ref.hh"
|
||||||
|
#include "common/luajit.hh"
|
||||||
#include "common/luajit_queue.hh"
|
#include "common/luajit_queue.hh"
|
||||||
#include "common/ptr_selector.hh"
|
#include "common/ptr_selector.hh"
|
||||||
#include "common/queue.hh"
|
#include "common/queue.hh"
|
||||||
#include "common/thread.hh"
|
#include "common/thread.hh"
|
||||||
|
|
||||||
|
|
||||||
|
using namespace std::literals;
|
||||||
|
|
||||||
namespace nf7 {
|
namespace nf7 {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
class LuaContext final : public nf7::File, public nf7::DirItem {
|
class LuaContext final : public nf7::FileBase, public nf7::DirItem {
|
||||||
public:
|
public:
|
||||||
static inline const GenericTypeInfo<LuaContext> kType = {
|
static inline const nf7::GenericTypeInfo<nf7::LuaContext> kType = {
|
||||||
"LuaJIT/Context", {"nf7::DirItem",}};
|
"LuaJIT/Context", {"nf7::DirItem",},
|
||||||
static void UpdateTypeTooltip() noexcept {
|
"drives LuaJIT thread and task queue"};
|
||||||
ImGui::TextUnformatted("Drives LuaJIT thread and task queue.");
|
|
||||||
ImGui::Bullet(); ImGui::TextUnformatted(
|
|
||||||
"implements nf7::luajit::Queue");
|
|
||||||
ImGui::Bullet(); ImGui::TextUnformatted(
|
|
||||||
"create multiple contexts to execute LuaJIT paralelly");
|
|
||||||
ImGui::Bullet(); ImGui::TextUnformatted(
|
|
||||||
"the thread remains alive after file deletion until unused");
|
|
||||||
}
|
|
||||||
|
|
||||||
class Queue;
|
class Queue;
|
||||||
|
|
||||||
LuaContext(nf7::Env& env) :
|
LuaContext(nf7::Env& env, bool async = false) noexcept :
|
||||||
File(kType, env), DirItem(DirItem::kTooltip) {
|
nf7::FileBase(kType, env),
|
||||||
q_ = std::make_shared<Queue>(*this);
|
nf7::DirItem(nf7::DirItem::kMenu |
|
||||||
|
nf7::DirItem::kTooltip),
|
||||||
|
log_(*this),
|
||||||
|
q_(std::make_shared<Queue>(*this, async)),
|
||||||
|
async_(async) {
|
||||||
}
|
}
|
||||||
|
|
||||||
LuaContext(nf7::Deserializer& ar) : LuaContext(ar.env()) {
|
LuaContext(nf7::Deserializer& ar) : LuaContext(ar.env()) {
|
||||||
|
ar(async_);
|
||||||
}
|
}
|
||||||
void Serialize(Serializer&) const noexcept override {
|
void Serialize(nf7::Serializer& ar) const noexcept override {
|
||||||
|
ar(async_);
|
||||||
}
|
}
|
||||||
std::unique_ptr<File> Clone(Env& env) const noexcept override {
|
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
|
||||||
return std::make_unique<LuaContext>(env);
|
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;
|
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<
|
return nf7::InterfaceSelector<
|
||||||
nf7::DirItem, nf7::luajit::Queue>(t).Select(this, q_.get());
|
nf7::DirItem, nf7::luajit::Queue>(t).Select(this, q_.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
nf7::LoggerRef log_;
|
||||||
|
|
||||||
std::shared_ptr<Queue> q_;
|
std::shared_ptr<Queue> q_;
|
||||||
|
|
||||||
|
bool async_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class LuaContext::Queue final : public nf7::luajit::Queue,
|
class LuaContext::Queue final : public nf7::luajit::Queue,
|
||||||
public std::enable_shared_from_this<LuaContext::Queue> {
|
public std::enable_shared_from_this<LuaContext::Queue> {
|
||||||
public:
|
public:
|
||||||
|
struct SharedData final {
|
||||||
|
lua_State* L;
|
||||||
|
|
||||||
|
std::atomic_flag lock;
|
||||||
|
std::optional<nf7::Env::Time> begin;
|
||||||
|
};
|
||||||
struct Runner final {
|
struct Runner final {
|
||||||
Runner(Queue& owner) noexcept : owner_(&owner) {
|
Runner(const std::shared_ptr<SharedData>& data) noexcept : data_(data) {
|
||||||
}
|
}
|
||||||
void operator()(Task&& t) {
|
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:
|
private:
|
||||||
Queue* const owner_;
|
std::shared_ptr<SharedData> data_;
|
||||||
|
bool require_gc_ = false;
|
||||||
};
|
};
|
||||||
using Thread = nf7::Thread<Runner, Task>;
|
using Thread = nf7::Thread<Runner, Task>;
|
||||||
|
|
||||||
Queue() = delete;
|
Queue() = delete;
|
||||||
Queue(LuaContext& f) :
|
Queue(LuaContext& f, bool async) {
|
||||||
L(luaL_newstate()), env_(&f.env()),
|
auto L = luaL_newstate();
|
||||||
th_(std::make_shared<Thread>(f, Runner {*this})) {
|
if (!L) {
|
||||||
if (!L) throw nf7::Exception("failed to create new Lua state");
|
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 {
|
~Queue() noexcept {
|
||||||
th_->Push(
|
th_->Push(
|
||||||
std::make_shared<nf7::GenericContext>(*env_, 0, "deleting lua_State"),
|
std::make_shared<nf7::GenericContext>(th_->env(), 0, "deleting lua_State"),
|
||||||
[L = L](auto) { lua_close(L); }
|
[data = data_](auto) {
|
||||||
);
|
lua_close(data->L);
|
||||||
|
data->L = nullptr;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
Queue(const Queue&) = delete;
|
Queue(const Queue&) = delete;
|
||||||
Queue(Queue&&) = delete;
|
Queue(Queue&&) = delete;
|
||||||
Queue& operator=(const Queue&) = delete;
|
Queue& operator=(const Queue&) = delete;
|
||||||
Queue& operator=(Queue&&) = delete;
|
Queue& operator=(Queue&&) = delete;
|
||||||
|
|
||||||
|
void SetAsync(bool async) noexcept {
|
||||||
|
th_->SetExecutor(async? nf7::Env::kAsync: nf7::Env::kSub);
|
||||||
|
}
|
||||||
|
|
||||||
void Push(const std::shared_ptr<nf7::Context>& ctx, Task&& task, nf7::Env::Time t) noexcept override {
|
void Push(const std::shared_ptr<nf7::Context>& ctx, Task&& task, nf7::Env::Time t) noexcept override {
|
||||||
th_->Push(ctx, std::move(task), t);
|
th_->Push(ctx, std::move(task), t);
|
||||||
}
|
}
|
||||||
std::shared_ptr<luajit::Queue> self() noexcept override { return shared_from_this(); }
|
std::shared_ptr<luajit::Queue> self() noexcept override { return shared_from_this(); }
|
||||||
|
|
||||||
size_t tasksDone() const noexcept { return th_->tasksDone(); }
|
size_t tasksDone() const noexcept {
|
||||||
|
return th_->tasksDone();
|
||||||
|
}
|
||||||
|
std::optional<nf7::Env::Time> currentTaskBegin() const noexcept {
|
||||||
|
auto& k = data_->lock;
|
||||||
|
while (k.test_and_set());
|
||||||
|
const auto ret = data_->begin;
|
||||||
|
k.clear();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
lua_State* L;
|
|
||||||
Env* const env_;
|
|
||||||
std::shared_ptr<Thread> th_;
|
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 {
|
void LuaContext::UpdateTooltip() noexcept {
|
||||||
ImGui::Text("tasks done: %zu", static_cast<size_t>(q_->tasksDone()));
|
ImGui::Text("tasks done: %zu", static_cast<size_t>(q_->tasksDone()));
|
||||||
if (q_) {
|
if (q_) {
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|
||||||
@@ -1,38 +1,39 @@
|
|||||||
#include <algorithm>
|
#include <chrono>
|
||||||
#include <exception>
|
#include <filesystem>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <typeinfo>
|
#include <utility>
|
||||||
#include <variant>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <imgui_stdlib.h>
|
#include <imgui_stdlib.h>
|
||||||
|
|
||||||
|
#include <ImNodes.h>
|
||||||
|
|
||||||
|
#include <tracy/Tracy.hpp>
|
||||||
|
|
||||||
|
#include <yaml-cpp/yaml.h>
|
||||||
|
|
||||||
#include <yas/serialize.hpp>
|
#include <yas/serialize.hpp>
|
||||||
#include <yas/types/std/string.hpp>
|
#include <yas/types/std/string.hpp>
|
||||||
#include <yas/types/std/vector.hpp>
|
|
||||||
|
|
||||||
#include "nf7.hh"
|
#include "nf7.hh"
|
||||||
|
|
||||||
#include "common/dir_item.hh"
|
#include "common/dir_item.hh"
|
||||||
#include "common/file_base.hh"
|
#include "common/file_base.hh"
|
||||||
#include "common/file_holder.hh"
|
#include "common/generic_config.hh"
|
||||||
#include "common/future.hh"
|
|
||||||
#include "common/generic_context.hh"
|
|
||||||
#include "common/generic_type_info.hh"
|
|
||||||
#include "common/generic_memento.hh"
|
#include "common/generic_memento.hh"
|
||||||
#include "common/gui_file.hh"
|
#include "common/generic_type_info.hh"
|
||||||
#include "common/gui_popup.hh"
|
#include "common/gui.hh"
|
||||||
#include "common/life.hh"
|
#include "common/life.hh"
|
||||||
#include "common/logger_ref.hh"
|
#include "common/logger_ref.hh"
|
||||||
|
#include "common/luajit_nfile_importer.hh"
|
||||||
#include "common/luajit_queue.hh"
|
#include "common/luajit_queue.hh"
|
||||||
#include "common/luajit_ref.hh"
|
#include "common/luajit_ref.hh"
|
||||||
#include "common/luajit_thread.hh"
|
#include "common/luajit_thread.hh"
|
||||||
#include "common/memento.hh"
|
#include "common/memento.hh"
|
||||||
#include "common/node.hh"
|
#include "common/node.hh"
|
||||||
#include "common/node_root_lambda.hh"
|
|
||||||
#include "common/ptr_selector.hh"
|
#include "common/ptr_selector.hh"
|
||||||
#include "common/util_string.hh"
|
|
||||||
|
|
||||||
|
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
@@ -41,85 +42,68 @@ using namespace std::literals;
|
|||||||
namespace nf7 {
|
namespace nf7 {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
class Node final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
|
class Node final : public nf7::FileBase,
|
||||||
|
public nf7::GenericConfig, public nf7::DirItem, public nf7::Node {
|
||||||
public:
|
public:
|
||||||
static inline const GenericTypeInfo<Node> kType =
|
static inline const nf7::GenericTypeInfo<Node> kType = {
|
||||||
{"LuaJIT/Node", {"nf7::DirItem"}};
|
"LuaJIT/Node", {"nf7::DirItem", "nf7::Node"},
|
||||||
static void UpdateTypeTooltip() noexcept {
|
"defines new pure Node without creating nfile"
|
||||||
ImGui::TextUnformatted("Defines new Node using Lua object factory.");
|
};
|
||||||
ImGui::Bullet();
|
|
||||||
ImGui::TextUnformatted("refers nf7::luajit::Queue through linked LuaJIT/Obj");
|
|
||||||
}
|
|
||||||
|
|
||||||
class Lambda;
|
class Lambda;
|
||||||
|
|
||||||
struct Data {
|
struct Data {
|
||||||
nf7::FileHolder::Tag obj;
|
Data() noexcept { }
|
||||||
std::string desc;
|
std::string Stringify() const noexcept;
|
||||||
std::vector<std::string> inputs;
|
void Parse(const std::string&);
|
||||||
std::vector<std::string> outputs;
|
|
||||||
|
std::string script;
|
||||||
|
std::vector<std::string> inputs = {"in"};
|
||||||
|
std::vector<std::string> outputs = {"out"};
|
||||||
};
|
};
|
||||||
|
|
||||||
Node(Env& env, Data&& data = {}) noexcept :
|
Node(nf7::Env& env, Data&& data = {}) noexcept :
|
||||||
nf7::FileBase(kType, env, {&obj_, &obj_editor_, &socket_popup_}),
|
nf7::FileBase(kType, env),
|
||||||
nf7::DirItem(nf7::DirItem::kTooltip | nf7::DirItem::kWidget),
|
nf7::GenericConfig(mem_),
|
||||||
nf7::Node(nf7::Node::kNone),
|
nf7::DirItem(nf7::DirItem::kTooltip),
|
||||||
|
nf7::Node(nf7::Node::kCustomNode),
|
||||||
life_(*this),
|
life_(*this),
|
||||||
log_(std::make_shared<nf7::LoggerRef>(*this)),
|
log_(std::make_shared<nf7::LoggerRef>(*this)),
|
||||||
obj_(*this, "obj_factory", mem_),
|
mem_(*this, std::move(data)),
|
||||||
obj_editor_(obj_, [](auto& t) { return t.flags().contains("nf7::Node"); }),
|
importer_(std::make_shared<nf7::luajit::NFileImporter>(env.npath())) {
|
||||||
mem_(std::move(data), *this) {
|
mem_.onCommit = mem_.onRestore = [this]() {
|
||||||
nf7::FileBase::Install(*log_);
|
cache_ = std::nullopt;
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Node(nf7::Deserializer& ar) : Node(ar.env()) {
|
Node(nf7::Deserializer& ar) : Node(ar.env()) {
|
||||||
ar(obj_, data().desc, data().inputs, data().outputs);
|
ar(mem_->script, mem_->inputs, mem_->outputs);
|
||||||
|
nf7::Node::ValidateSockets(mem_->inputs);
|
||||||
nf7::util::Uniq(data().inputs);
|
nf7::Node::ValidateSockets(mem_->outputs);
|
||||||
nf7::util::Uniq(data().outputs);
|
|
||||||
}
|
}
|
||||||
void Serialize(nf7::Serializer& ar) const noexcept override {
|
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 {
|
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(
|
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
|
||||||
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
|
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
|
||||||
std::span<const std::string> GetInputs() const noexcept override {
|
|
||||||
return data().inputs;
|
nf7::Node::Meta GetMeta() const noexcept override {
|
||||||
}
|
return nf7::Node::Meta {mem_->inputs, mem_->outputs};
|
||||||
std::span<const std::string> GetOutputs() const noexcept override {
|
|
||||||
return data().outputs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nf7::Future<std::shared_ptr<nf7::luajit::Ref>> Build() noexcept;
|
||||||
|
|
||||||
|
void PostUpdate() noexcept override;
|
||||||
void UpdateTooltip() 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<
|
return nf7::InterfaceSelector<
|
||||||
nf7::DirItem, nf7::Memento, nf7::Node>(t).Select(this, &mem_);
|
nf7::Config, nf7::DirItem, nf7::Memento, nf7::Node>(t).Select(this, &mem_);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -127,63 +111,40 @@ class Node final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
|
|||||||
|
|
||||||
std::shared_ptr<nf7::LoggerRef> log_;
|
std::shared_ptr<nf7::LoggerRef> log_;
|
||||||
|
|
||||||
nf7::FileHolder obj_;
|
|
||||||
nf7::gui::FileHolderEditor obj_editor_;
|
|
||||||
|
|
||||||
nf7::gui::IOSocketListPopup socket_popup_;
|
|
||||||
|
|
||||||
nf7::GenericMemento<Data> mem_;
|
nf7::GenericMemento<Data> mem_;
|
||||||
const Data& data() const noexcept { return mem_.data(); }
|
|
||||||
Data& data() noexcept { return mem_.data(); }
|
|
||||||
|
|
||||||
// factory context
|
std::optional<nf7::Future<std::shared_ptr<nf7::luajit::Ref>>> cache_;
|
||||||
std::shared_ptr<nf7::NodeRootLambda> factory_;
|
|
||||||
std::optional<nf7::Future<nf7::Value>> fu_;
|
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,
|
class Node::Lambda final : public nf7::Node::Lambda,
|
||||||
public std::enable_shared_from_this<Node::Lambda> {
|
public std::enable_shared_from_this<Node::Lambda> {
|
||||||
public:
|
public:
|
||||||
Lambda(Node& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept :
|
Lambda(Node& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept :
|
||||||
nf7::Node::Lambda(f, parent), f_(f.life_), log_(f.log_) {
|
nf7::Node::Lambda(f, parent),
|
||||||
|
f_(f.life_), log_(f.log_),
|
||||||
|
table_ctx_(std::make_shared<nf7::GenericContext>(f, "LuaJIT Node context table")) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Handle(std::string_view k, const nf7::Value& v,
|
void Handle(const nf7::Node::Lambda::Msg& in) noexcept override
|
||||||
const std::shared_ptr<nf7::Node::Lambda>& caller) noexcept override
|
|
||||||
try {
|
try {
|
||||||
f_.EnforceAlive();
|
f_.EnforceAlive();
|
||||||
|
|
||||||
auto self = shared_from_this();
|
auto self = shared_from_this();
|
||||||
|
f_->Build().
|
||||||
if (!f_->fu_) {
|
ThenIf(self, [this, in](auto& func) mutable {
|
||||||
auto& n = f_->obj_.GetFileOrThrow().interfaceOrThrow<nf7::Node>();
|
if (f_) StartThread(in, func);
|
||||||
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());
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} catch (nf7::LifeExpiredException&) {
|
} catch (nf7::ExpiredException&) {
|
||||||
} catch (nf7::Exception& e) {
|
|
||||||
log_->Error(e.msg());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Abort() noexcept override {
|
void Abort() noexcept override {
|
||||||
for (auto& wth : th_) {
|
th_owner_.AbortAll();
|
||||||
if (auto th = wth.lock()) {
|
|
||||||
th->Abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -191,114 +152,161 @@ class Node::Lambda final : public nf7::Node::Lambda,
|
|||||||
|
|
||||||
std::shared_ptr<nf7::LoggerRef> log_;
|
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,
|
void StartThread(const nf7::Node::Lambda::Msg& in,
|
||||||
std::string&& k, nf7::Value&& v,
|
const std::shared_ptr<nf7::luajit::Ref>& func) noexcept {
|
||||||
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());
|
|
||||||
|
|
||||||
auto ljq = func->ljq();
|
auto ljq = func->ljq();
|
||||||
auto th = std::make_shared<nf7::luajit::Thread>(
|
auto self = shared_from_this();
|
||||||
self, ljq,
|
|
||||||
nf7::luajit::Thread::CreateNodeLambdaHandler(caller, 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->Install(log_);
|
||||||
th_.emplace_back(th);
|
|
||||||
|
|
||||||
auto ctx = std::make_shared<nf7::GenericContext>(env(), initiator());
|
ljq->Push(self, [this, ljq, th, func, in](auto L) mutable {
|
||||||
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);
|
std::unique_lock<std::mutex> k {mtx_};
|
||||||
func->PushSelf(thL);
|
if (!table_ || table_->ljq() != ljq) {
|
||||||
|
lua_createtable(L, 0, 0);
|
||||||
// push args
|
table_.emplace(table_ctx_, ljq, L);
|
||||||
lua_pushstring(thL, k.c_str());
|
|
||||||
nf7::luajit::PushValue(thL, v);
|
|
||||||
|
|
||||||
// push context table
|
|
||||||
if (ctxtable_ && ctxtable_->ljq() != ljq) {
|
|
||||||
ctxtable_ = std::nullopt;
|
|
||||||
}
|
}
|
||||||
if (!ctxtable_) {
|
|
||||||
lua_createtable(thL, 0, 0);
|
|
||||||
lua_pushvalue(thL, -1);
|
|
||||||
ctxtable_.emplace(ctx, ljq, thL);
|
|
||||||
} else {
|
|
||||||
ctxtable_->PushSelf(thL);
|
|
||||||
}
|
}
|
||||||
|
L = th->Init(L);
|
||||||
// execute
|
func->PushSelf(L);
|
||||||
th->Resume(thL, 3);
|
nf7::luajit::PushAll(L, in.name, in.value);
|
||||||
|
table_->PushSelf(L);
|
||||||
|
th->Resume(L, 3);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
std::shared_ptr<nf7::Node::Lambda> Node::CreateLambda(
|
std::shared_ptr<nf7::Node::Lambda> Node::CreateLambda(
|
||||||
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
|
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
|
||||||
return std::make_shared<Lambda>(*this, parent);
|
return la_owner_.Create<Lambda>(*this, parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
nf7::Future<std::shared_ptr<nf7::luajit::Ref>> Node::Build() noexcept
|
||||||
|
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 {
|
void Node::UpdateTooltip() noexcept {
|
||||||
ImGui::Text("factory:");
|
const char* state = "unused";
|
||||||
ImGui::Indent();
|
if (cache_) {
|
||||||
obj_editor_.Tooltip();
|
state =
|
||||||
ImGui::Unindent();
|
cache_->done()? "ready":
|
||||||
ImGui::Spacing();
|
cache_->yet()? "building": "broken";
|
||||||
|
}
|
||||||
|
ImGui::Text("state: %s", state);
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::Text("input:");
|
void Node::UpdateNode(nf7::Node::Editor&) noexcept {
|
||||||
ImGui::Indent();
|
|
||||||
for (const auto& name : data().inputs) {
|
|
||||||
ImGui::Bullet(); ImGui::TextUnformatted(name.c_str());
|
|
||||||
}
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
void Node::UpdateWidget() noexcept {
|
|
||||||
const auto em = ImGui::GetFontSize();
|
const auto em = ImGui::GetFontSize();
|
||||||
|
|
||||||
ImGui::TextUnformatted("LuaJIT/Node: config");
|
ImGui::TextUnformatted("LuaJIT/Node");
|
||||||
obj_editor_.ButtonWithLabel("obj factory");
|
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()) {
|
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
||||||
mem_.Commit();
|
mem_.Commit();
|
||||||
}
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
if (ImGui::Button("I/O list")) {
|
nf7::gui::NodeOutputSockets(mem_->outputs);
|
||||||
socket_popup_.Open(data().inputs, data().outputs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Spacing();
|
|
||||||
obj_editor_.ItemWidget("obj factory");
|
|
||||||
|
|
||||||
socket_popup_.Update();
|
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);
|
||||||
|
|
||||||
|
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
112
file/node_comment.cc
Normal 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
428
file/node_dll.cc
Normal 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
459
file/node_exprtk.cc
Normal 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
|
||||||
152
file/node_imm.cc
152
file/node_imm.cc
@@ -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
122
file/node_mutex.cc
Normal 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
197
file/node_ref.cc
197
file/node_ref.cc
@@ -21,9 +21,8 @@
|
|||||||
#include "common/generic_memento.hh"
|
#include "common/generic_memento.hh"
|
||||||
#include "common/generic_type_info.hh"
|
#include "common/generic_type_info.hh"
|
||||||
#include "common/generic_watcher.hh"
|
#include "common/generic_watcher.hh"
|
||||||
|
#include "common/gui.hh"
|
||||||
#include "common/gui_dnd.hh"
|
#include "common/gui_dnd.hh"
|
||||||
#include "common/gui_node.hh"
|
|
||||||
#include "common/gui_popup.hh"
|
|
||||||
#include "common/life.hh"
|
#include "common/life.hh"
|
||||||
#include "common/logger_ref.hh"
|
#include "common/logger_ref.hh"
|
||||||
#include "common/memento.hh"
|
#include "common/memento.hh"
|
||||||
@@ -37,16 +36,7 @@ namespace {
|
|||||||
class Ref final : public nf7::FileBase, public nf7::Node {
|
class Ref final : public nf7::FileBase, public nf7::Node {
|
||||||
public:
|
public:
|
||||||
static inline const nf7::GenericTypeInfo<Ref> kType = {
|
static inline const nf7::GenericTypeInfo<Ref> kType = {
|
||||||
"Node/Ref", {"nf7::Node"}};
|
"Node/Ref", {"nf7::Node"}, "refers other Node"};
|
||||||
static void UpdateTypeTooltip() noexcept {
|
|
||||||
ImGui::TextUnformatted("Refers other Node.");
|
|
||||||
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Node");
|
|
||||||
ImGui::Bullet(); ImGui::TextUnformatted(
|
|
||||||
"the referencee's changes won't be applied to active lambdas "
|
|
||||||
"until their recreation");
|
|
||||||
ImGui::Bullet(); ImGui::TextUnformatted(
|
|
||||||
"press 'sync' button on Node UI to resolve socket issues");
|
|
||||||
}
|
|
||||||
|
|
||||||
class Lambda;
|
class Lambda;
|
||||||
|
|
||||||
@@ -58,47 +48,30 @@ class Ref final : public nf7::FileBase, public nf7::Node {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Ref(nf7::Env& env, Data&& data = {}) noexcept :
|
Ref(nf7::Env& env, Data&& data = {}) noexcept :
|
||||||
nf7::FileBase(kType, env, {&config_popup_}),
|
nf7::FileBase(kType, env),
|
||||||
nf7::Node(nf7::Node::kCustomNode | nf7::Node::kMenu),
|
nf7::Node(nf7::Node::kCustomNode | nf7::Node::kMenu),
|
||||||
life_(*this),
|
life_(*this),
|
||||||
log_(std::make_shared<nf7::LoggerRef>(*this)),
|
log_(std::make_shared<nf7::LoggerRef>(*this)),
|
||||||
mem_(std::move(data), *this),
|
mem_(*this, std::move(data)) {
|
||||||
config_popup_(*this) {
|
mem_.onRestore = mem_.onCommit = [this]() {
|
||||||
nf7::FileBase::Install(*log_);
|
SetUpWatcher();
|
||||||
|
};
|
||||||
mem_.onRestore = mem_.onCommit = [this]() { SetUpWatcher(); };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ref(nf7::Deserializer& ar) : Ref(ar.env()) {
|
Ref(nf7::Deserializer& ar) : Ref(ar.env()) {
|
||||||
ar(data().target, data().inputs, data().outputs);
|
ar(mem_->target, mem_->inputs, mem_->outputs);
|
||||||
}
|
}
|
||||||
void Serialize(nf7::Serializer& ar) const noexcept override {
|
void Serialize(nf7::Serializer& ar) const noexcept override {
|
||||||
ar(data().target, data().inputs, data().outputs);
|
ar(mem_->target, mem_->inputs, mem_->outputs);
|
||||||
}
|
}
|
||||||
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
|
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
|
||||||
return std::make_unique<Ref>(env, Data {data()});
|
return std::make_unique<Ref>(env, Data {mem_.data()});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
|
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
|
||||||
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
|
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
|
||||||
std::span<const std::string> GetInputs() const noexcept override {
|
nf7::Node::Meta GetMeta() const noexcept override {
|
||||||
return data().inputs;
|
return {mem_->inputs, mem_->outputs};
|
||||||
}
|
|
||||||
std::span<const std::string> GetOutputs() const noexcept override {
|
|
||||||
return data().outputs;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Handle(const nf7::File::Event& ev) noexcept {
|
|
||||||
nf7::FileBase::Handle(ev);
|
|
||||||
|
|
||||||
switch (ev.type) {
|
|
||||||
case nf7::File::Event::kAdd:
|
|
||||||
env().ExecMain(std::make_shared<nf7::GenericContext>(*this),
|
|
||||||
std::bind(&Ref::SetUpWatcher, this));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateNode(nf7::Node::Editor&) noexcept override;
|
void UpdateNode(nf7::Node::Editor&) noexcept override;
|
||||||
@@ -116,48 +89,30 @@ class Ref final : public nf7::FileBase, public nf7::Node {
|
|||||||
std::optional<nf7::GenericWatcher> watcher_;
|
std::optional<nf7::GenericWatcher> watcher_;
|
||||||
|
|
||||||
nf7::GenericMemento<Data> mem_;
|
nf7::GenericMemento<Data> mem_;
|
||||||
const Data& data() const noexcept { return mem_.data(); }
|
|
||||||
Data& data() noexcept { return mem_.data(); }
|
|
||||||
|
|
||||||
|
|
||||||
// GUI popup
|
|
||||||
class ConfigPopup final : public nf7::FileBase::Feature, private nf7::gui::Popup {
|
|
||||||
public:
|
|
||||||
ConfigPopup(Ref& f) noexcept : nf7::gui::Popup("ConfigPopup"), f_(&f) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void Open() noexcept {
|
|
||||||
path_ = f_->data().target.Stringify();
|
|
||||||
nf7::gui::Popup::Open();
|
|
||||||
}
|
|
||||||
void Update() noexcept override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Ref* const f_;
|
|
||||||
std::string path_;
|
|
||||||
} config_popup_;
|
|
||||||
|
|
||||||
// accessors
|
// accessors
|
||||||
nf7::File& target() const {
|
nf7::File& target() const {
|
||||||
auto& f = ResolveOrThrow(data().target);
|
auto& f = ResolveOrThrow(mem_->target);
|
||||||
if (&f == this) throw nf7::Exception("self reference");
|
if (&f == this) throw nf7::Exception("self reference");
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
// socket synchronization
|
// socket synchronization
|
||||||
bool SyncQuiet() noexcept {
|
bool SyncQuiet() noexcept {
|
||||||
auto& dsti = data().inputs;
|
auto& dsti = mem_->inputs;
|
||||||
auto& dsto = data().outputs;
|
auto& dsto = mem_->outputs;
|
||||||
|
|
||||||
bool mod = false;
|
bool mod = false;
|
||||||
try {
|
try {
|
||||||
auto& n = target().interfaceOrThrow<nf7::Node>();
|
auto& n = target().interfaceOrThrow<nf7::Node>();
|
||||||
|
const auto meta = n.GetMeta();
|
||||||
|
|
||||||
const auto srci = n.GetInputs();
|
const auto& srci = meta.inputs;
|
||||||
mod |= std::equal(dsti.begin(), dsti.end(), srci.begin(), srci.end());
|
mod |= std::equal(dsti.begin(), dsti.end(), srci.begin(), srci.end());
|
||||||
dsti = std::vector<std::string>{srci.begin(), srci.end()};
|
dsti = std::vector<std::string>{srci.begin(), srci.end()};
|
||||||
|
|
||||||
const auto srco = n.GetOutputs();
|
const auto& srco = meta.outputs;
|
||||||
mod |= std::equal(dsto.begin(), dsto.end(), srco.begin(), srco.end());
|
mod |= std::equal(dsto.begin(), dsto.end(), srco.begin(), srco.end());
|
||||||
dsto = std::vector<std::string>{srco.begin(), srco.end()};
|
dsto = std::vector<std::string>{srco.begin(), srco.end()};
|
||||||
} catch (nf7::Exception& e) {
|
} catch (nf7::Exception& e) {
|
||||||
@@ -181,7 +136,7 @@ class Ref final : public nf7::FileBase, public nf7::Node {
|
|||||||
|
|
||||||
// referencee operation
|
// referencee operation
|
||||||
void ExecChangeTarget(Path&& p) noexcept {
|
void ExecChangeTarget(Path&& p) noexcept {
|
||||||
auto& target = mem_.data().target;
|
auto& target = mem_->target;
|
||||||
if (p == target) return;
|
if (p == target) return;
|
||||||
|
|
||||||
env().ExecMain(
|
env().ExecMain(
|
||||||
@@ -217,36 +172,48 @@ class Ref::Lambda final : public Node::Lambda,
|
|||||||
Node::Lambda(f, parent), f_(f.life_), log_(f.log_) {
|
Node::Lambda(f, parent), f_(f.life_), log_(f.log_) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Handle(std::string_view name, const Value& v,
|
void Handle(const nf7::Node::Lambda::Msg& in) noexcept override
|
||||||
const std::shared_ptr<Node::Lambda>& caller) noexcept override
|
|
||||||
try {
|
try {
|
||||||
if (!f_) return;
|
if (!f_) return;
|
||||||
|
|
||||||
auto parent = this->parent();
|
auto parent = this->parent();
|
||||||
if (!parent) return;
|
if (!parent) return;
|
||||||
|
|
||||||
if (caller == base_) {
|
// check if target file is changed
|
||||||
parent->Handle(name, v, shared_from_this());
|
auto& target = f_->target();
|
||||||
}
|
if (target.id() != target_id_) {
|
||||||
if (caller == parent) {
|
|
||||||
if (!base_) {
|
|
||||||
if (depth() > kMaxDepth) {
|
if (depth() > kMaxDepth) {
|
||||||
log_->Error("stack overflow");
|
throw nf7::Exception {"stack overflow"};
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
base_ = f_->target().
|
target_id_ = target.id();
|
||||||
|
target_ = target.
|
||||||
interfaceOrThrow<nf7::Node>().
|
interfaceOrThrow<nf7::Node>().
|
||||||
CreateLambda(shared_from_this());
|
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 {
|
void Abort() noexcept override {
|
||||||
if (base_) {
|
if (target_) {
|
||||||
base_->Abort();
|
target_->Abort();
|
||||||
|
target_ = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,7 +222,8 @@ class Ref::Lambda final : public Node::Lambda,
|
|||||||
|
|
||||||
std::shared_ptr<nf7::LoggerRef> log_;
|
std::shared_ptr<nf7::LoggerRef> log_;
|
||||||
|
|
||||||
std::shared_ptr<Node::Lambda> base_;
|
nf7::File::Id target_id_ = 0;
|
||||||
|
std::shared_ptr<nf7::Node::Lambda> target_;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::shared_ptr<Node::Lambda> Ref::CreateLambda(
|
std::shared_ptr<Node::Lambda> Ref::CreateLambda(
|
||||||
@@ -278,26 +246,23 @@ void Ref::UpdateNode(Node::Editor&) noexcept {
|
|||||||
ExecSync();
|
ExecSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto pathstr = mem_.data().target.Stringify();
|
|
||||||
|
|
||||||
auto w = 6*em;
|
auto w = 6*em;
|
||||||
{
|
{
|
||||||
auto pw = ImGui::CalcTextSize(pathstr.c_str()).x+style.FramePadding.x*2;
|
|
||||||
w = std::max(w, std::min(pw, 8*em));
|
|
||||||
|
|
||||||
auto iw = 3*em;
|
auto iw = 3*em;
|
||||||
for (const auto& v : data().inputs) {
|
for (const auto& v : mem_->inputs) {
|
||||||
iw = std::max(iw, ImGui::CalcTextSize(v.c_str()).x);
|
iw = std::max(iw, ImGui::CalcTextSize(v.c_str()).x);
|
||||||
}
|
}
|
||||||
auto ow = 3*em;
|
auto ow = 3*em;
|
||||||
for (const auto& v : data().outputs) {
|
for (const auto& v : mem_->outputs) {
|
||||||
ow = std::max(ow, ImGui::CalcTextSize(v.c_str()).x);
|
ow = std::max(ow, ImGui::CalcTextSize(v.c_str()).x);
|
||||||
}
|
}
|
||||||
w = std::max(w, 1*em+style.ItemSpacing.x+iw +1*em+ ow+style.ItemSpacing.x+1*em);
|
w = std::max(w, 1*em+style.ItemSpacing.x+iw +1*em+ ow+style.ItemSpacing.x+1*em);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui::Button(pathstr.c_str(), {w, 0})) {
|
auto newpath = mem_->target;
|
||||||
config_popup_.Open();
|
ImGui::SetNextItemWidth(w);
|
||||||
|
if (nf7::gui::PathButton("##target", newpath, *this)) {
|
||||||
|
ExecChangeTarget(std::move(newpath));
|
||||||
}
|
}
|
||||||
if (ImGui::BeginDragDropTarget()) {
|
if (ImGui::BeginDragDropTarget()) {
|
||||||
if (auto p = gui::dnd::Accept<Path>(gui::dnd::kFilePath)) {
|
if (auto p = gui::dnd::Accept<Path>(gui::dnd::kFilePath)) {
|
||||||
@@ -308,7 +273,7 @@ void Ref::UpdateNode(Node::Editor&) noexcept {
|
|||||||
|
|
||||||
const auto right = ImGui::GetCursorPosX() + w;
|
const auto right = ImGui::GetCursorPosX() + w;
|
||||||
ImGui::BeginGroup();
|
ImGui::BeginGroup();
|
||||||
for (const auto& name : data().inputs) {
|
for (const auto& name : mem_->inputs) {
|
||||||
if (ImNodes::BeginInputSlot(name.c_str(), 1)) {
|
if (ImNodes::BeginInputSlot(name.c_str(), 1)) {
|
||||||
gui::NodeSocket();
|
gui::NodeSocket();
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
@@ -319,7 +284,7 @@ void Ref::UpdateNode(Node::Editor&) noexcept {
|
|||||||
ImGui::EndGroup();
|
ImGui::EndGroup();
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::BeginGroup();
|
ImGui::BeginGroup();
|
||||||
for (const auto& name : data().outputs) {
|
for (const auto& name : mem_->outputs) {
|
||||||
const auto tw = ImGui::CalcTextSize(name.c_str()).x;
|
const auto tw = ImGui::CalcTextSize(name.c_str()).x;
|
||||||
ImGui::SetCursorPosX(right-(tw+style.ItemSpacing.x+em));
|
ImGui::SetCursorPosX(right-(tw+style.ItemSpacing.x+em));
|
||||||
|
|
||||||
@@ -331,31 +296,18 @@ void Ref::UpdateNode(Node::Editor&) noexcept {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui::EndGroup();
|
ImGui::EndGroup();
|
||||||
|
|
||||||
config_popup_.Update();
|
|
||||||
}
|
}
|
||||||
void Ref::UpdateMenu(nf7::Node::Editor& ed) noexcept {
|
void Ref::UpdateMenu(nf7::Node::Editor& ed) noexcept {
|
||||||
if (ImGui::MenuItem("sync")) {
|
if (ImGui::MenuItem("sync")) {
|
||||||
ExecSync();
|
ExecSync();
|
||||||
}
|
}
|
||||||
if (ImGui::MenuItem("replace target")) {
|
|
||||||
config_popup_.Open();
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
auto& f = target();
|
auto& f = target();
|
||||||
auto& n = f.interfaceOrThrow<nf7::Node>();
|
auto& n = f.interfaceOrThrow<nf7::Node>();
|
||||||
auto d = f.interface<nf7::DirItem>();
|
|
||||||
|
|
||||||
const bool dmenu = n.flags() & nf7::Node::kMenu_DirItem;
|
if (ImGui::BeginMenu("target")) {
|
||||||
const bool menu = n.flags() & nf7::Node::kMenu;
|
nf7::gui::FileMenuItems(f);
|
||||||
|
if (n.flags() & nf7::Node::kMenu) {
|
||||||
if ((dmenu || menu) && ImGui::BeginMenu("target")) {
|
|
||||||
if (dmenu) {
|
|
||||||
assert(d);
|
|
||||||
ImGui::Separator();
|
|
||||||
d->UpdateMenu();
|
|
||||||
}
|
|
||||||
if (menu) {
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
n.UpdateMenu(ed);
|
n.UpdateMenu(ed);
|
||||||
}
|
}
|
||||||
@@ -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
|
} // namespace nf7
|
||||||
|
|||||||
235
file/node_singleton.cc
Normal file
235
file/node_singleton.cc
Normal 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
601
file/node_ziptie.cc
Normal 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
|
||||||
@@ -15,12 +15,10 @@
|
|||||||
#include <yas/types/std/vector.hpp>
|
#include <yas/types/std/vector.hpp>
|
||||||
|
|
||||||
#include "common/file_base.hh"
|
#include "common/file_base.hh"
|
||||||
#include "common/file_holder.hh"
|
|
||||||
#include "common/generic_context.hh"
|
#include "common/generic_context.hh"
|
||||||
#include "common/generic_memento.hh"
|
#include "common/generic_memento.hh"
|
||||||
#include "common/generic_type_info.hh"
|
#include "common/generic_type_info.hh"
|
||||||
#include "common/gui_file.hh"
|
#include "common/gui.hh"
|
||||||
#include "common/gui_popup.hh"
|
|
||||||
#include "common/gui_value.hh"
|
#include "common/gui_value.hh"
|
||||||
#include "common/life.hh"
|
#include "common/life.hh"
|
||||||
#include "common/ptr_selector.hh"
|
#include "common/ptr_selector.hh"
|
||||||
@@ -31,17 +29,13 @@
|
|||||||
namespace nf7 {
|
namespace nf7 {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
class Adaptor final : public nf7::FileBase, public nf7::Sequencer {
|
class Adaptor final : public nf7::FileBase,
|
||||||
|
public nf7::Sequencer {
|
||||||
public:
|
public:
|
||||||
static inline const nf7::GenericTypeInfo<Adaptor> kType =
|
static inline const nf7::GenericTypeInfo<Adaptor> kType = {
|
||||||
{"Sequencer/Adaptor", {"nf7::Sequencer"}};
|
"Sequencer/Adaptor", {"nf7::Sequencer"},
|
||||||
static void UpdateTypeTooltip() noexcept {
|
"wraps and adapts other Sequencer",
|
||||||
ImGui::TextUnformatted("Wraps and Adapts other Sequencer.");
|
};
|
||||||
ImGui::Bullet(); ImGui::TextUnformatted(
|
|
||||||
"implements nf7::Sequencer");
|
|
||||||
ImGui::Bullet(); ImGui::TextUnformatted(
|
|
||||||
"changes will be applied to active lambdas immediately");
|
|
||||||
}
|
|
||||||
|
|
||||||
class Session;
|
class Session;
|
||||||
class Lambda;
|
class Lambda;
|
||||||
@@ -56,35 +50,32 @@ class Adaptor final : public nf7::FileBase, public nf7::Sequencer {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
struct Data {
|
struct Data {
|
||||||
nf7::FileHolder::Tag target;
|
nf7::File::Path path;
|
||||||
|
|
||||||
std::vector<std::pair<std::string, nf7::gui::Value>> input_imm;
|
std::vector<std::pair<std::string, nf7::gui::Value>> input_imm;
|
||||||
std::vector<std::pair<std::string, Var>> input_map;
|
std::vector<std::pair<std::string, Var>> input_map;
|
||||||
std::vector<std::pair<std::string, std::string>> output_map;
|
std::vector<std::pair<std::string, std::string>> output_map;
|
||||||
|
|
||||||
|
void serialize(auto& ar) {
|
||||||
|
ar(path, input_imm, input_map, output_map);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Adaptor(nf7::Env& env, Data&& data = {}) noexcept :
|
Adaptor(nf7::Env& env, Data&& d = {}) noexcept :
|
||||||
nf7::FileBase(kType, env, {&target_, &target_editor_}),
|
nf7::FileBase(kType, env),
|
||||||
Sequencer(Sequencer::kCustomItem |
|
nf7::Sequencer(Sequencer::kCustomItem |
|
||||||
Sequencer::kTooltip |
|
Sequencer::kTooltip |
|
||||||
Sequencer::kParamPanel),
|
Sequencer::kParamPanel),
|
||||||
life_(*this),
|
life_(*this), mem_(*this, std::move(d)) {
|
||||||
target_(*this, "target", mem_),
|
|
||||||
target_editor_(target_,
|
|
||||||
[](auto& t) { return t.flags().contains("nf7::Sequencer"); }),
|
|
||||||
mem_(std::move(data), *this) {
|
|
||||||
mem_.data().target.SetTarget(target_);
|
|
||||||
mem_.CommitAmend();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Adaptor(nf7::Deserializer& ar) : Adaptor(ar.env()) {
|
Adaptor(nf7::Deserializer& ar) : Adaptor(ar.env()) {
|
||||||
ar(target_, data().input_imm, data().input_map, data().output_map);
|
ar(mem_.data());
|
||||||
}
|
}
|
||||||
void Serialize(nf7::Serializer& ar) const noexcept override {
|
void Serialize(nf7::Serializer& ar) const noexcept override {
|
||||||
ar(target_, data().input_imm, data().input_map, data().output_map);
|
ar(mem_.data());
|
||||||
}
|
}
|
||||||
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
|
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
|
||||||
return std::make_unique<Adaptor>(env, Data {data()});
|
return std::make_unique<Adaptor>(env, Data {mem_.data()});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<nf7::Sequencer::Lambda> CreateLambda(
|
std::shared_ptr<nf7::Sequencer::Lambda> CreateLambda(
|
||||||
@@ -101,14 +92,8 @@ class Adaptor final : public nf7::FileBase, public nf7::Sequencer {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
nf7::Life<Adaptor> life_;
|
nf7::Life<Adaptor> life_;
|
||||||
nf7::FileHolder target_;
|
|
||||||
|
|
||||||
nf7::gui::FileHolderEditor target_editor_;
|
|
||||||
|
|
||||||
nf7::GenericMemento<Data> mem_;
|
nf7::GenericMemento<Data> mem_;
|
||||||
|
|
||||||
const Data& data() const noexcept { return mem_.data(); }
|
|
||||||
Data& data() noexcept { return mem_.data(); }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -116,10 +101,10 @@ class Adaptor::Session final : public nf7::Sequencer::Session {
|
|||||||
public:
|
public:
|
||||||
// ensure that Adaptor is alive
|
// ensure that Adaptor is alive
|
||||||
Session(Adaptor& f, const std::shared_ptr<nf7::Sequencer::Session>& parent) noexcept : parent_(parent) {
|
Session(Adaptor& f, const std::shared_ptr<nf7::Sequencer::Session>& parent) noexcept : parent_(parent) {
|
||||||
for (auto& p : f.data().input_imm) {
|
for (auto& p : f.mem_->input_imm) {
|
||||||
vars_[p.first] = p.second.entity();
|
vars_[p.first] = p.second.entity();
|
||||||
}
|
}
|
||||||
for (auto& p : f.data().input_map) {
|
for (auto& p : f.mem_->input_map) {
|
||||||
if (p.second.name.size() == 0) continue;
|
if (p.second.name.size() == 0) continue;
|
||||||
if (p.second.peek) {
|
if (p.second.peek) {
|
||||||
if (const auto ptr = parent->Peek(p.second.name)) {
|
if (const auto ptr = parent->Peek(p.second.name)) {
|
||||||
@@ -131,7 +116,7 @@ class Adaptor::Session final : public nf7::Sequencer::Session {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (auto& p : f.data().output_map) {
|
for (auto& p : f.mem_->output_map) {
|
||||||
outs_[p.first] = p.second;
|
outs_[p.first] = p.second;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,7 +169,7 @@ class Adaptor::Lambda final : public nf7::Sequencer::Lambda,
|
|||||||
try {
|
try {
|
||||||
f_.EnforceAlive();
|
f_.EnforceAlive();
|
||||||
|
|
||||||
auto& target = f_->target_.GetFileOrThrow();
|
auto& target = f_->ResolveOrThrow(f_->mem_->path);
|
||||||
auto& seq = target.interfaceOrThrow<nf7::Sequencer>();
|
auto& seq = target.interfaceOrThrow<nf7::Sequencer>();
|
||||||
if (!la_ || target.id() != cached_id_) {
|
if (!la_ || target.id() != cached_id_) {
|
||||||
la_ = seq.CreateLambda(shared_from_this());
|
la_ = seq.CreateLambda(shared_from_this());
|
||||||
@@ -215,25 +200,29 @@ class Adaptor::Editor final : public nf7::Sequencer::Editor {
|
|||||||
|
|
||||||
void Adaptor::UpdateItem(Sequencer::Editor&) noexcept {
|
void Adaptor::UpdateItem(Sequencer::Editor&) noexcept {
|
||||||
try {
|
try {
|
||||||
auto& seq = target_.GetFileOrThrow().interfaceOrThrow<nf7::Sequencer>();
|
auto& seq = ResolveOrThrow(mem_->path).interfaceOrThrow<nf7::Sequencer>();
|
||||||
if (seq.flags() & nf7::Sequencer::kCustomItem) {
|
if (seq.flags() & nf7::Sequencer::kCustomItem) {
|
||||||
Adaptor::Editor ed;
|
Adaptor::Editor ed;
|
||||||
seq.UpdateItem(ed);
|
seq.UpdateItem(ed);
|
||||||
}
|
}
|
||||||
} catch (nf7::Exception&) {
|
} catch (nf7::File::NotFoundException&) {
|
||||||
ImGui::Text("%s", target_editor_.GetDisplayText().c_str());
|
ImGui::TextUnformatted("file missing");
|
||||||
|
} catch (nf7::File::NotImplementedException&) {
|
||||||
|
ImGui::TextUnformatted("file does not have Sequencer interface");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void Adaptor::UpdateParamPanel(Sequencer::Editor&) noexcept {
|
void Adaptor::UpdateParamPanel(Sequencer::Editor&) noexcept {
|
||||||
bool commit = false;
|
bool commit = false;
|
||||||
|
|
||||||
auto& imm = data().input_imm;
|
auto& imm = mem_->input_imm;
|
||||||
auto& inputs = data().input_map;
|
auto& inputs = mem_->input_map;
|
||||||
auto& outputs = data().output_map;
|
auto& outputs = mem_->output_map;
|
||||||
|
|
||||||
const auto em = ImGui::GetFontSize();
|
const auto em = ImGui::GetFontSize();
|
||||||
if (ImGui::CollapsingHeader("Sequencer/Adaptor", ImGuiTreeNodeFlags_DefaultOpen)) {
|
if (ImGui::CollapsingHeader("Sequencer/Adaptor", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||||
target_editor_.ButtonWithLabel("target");
|
if (nf7::gui::PathButton("path", mem_->path, *this)) {
|
||||||
|
commit = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (ImGui::BeginTable("table", 3)) {
|
if (ImGui::BeginTable("table", 3)) {
|
||||||
ImGui::TableSetupColumn("left", ImGuiTableColumnFlags_WidthStretch, 1.f);
|
ImGui::TableSetupColumn("left", ImGuiTableColumnFlags_WidthStretch, 1.f);
|
||||||
@@ -390,7 +379,7 @@ void Adaptor::UpdateParamPanel(Sequencer::Editor&) noexcept {
|
|||||||
|
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
try {
|
try {
|
||||||
auto& seq = target_.GetFileOrThrow().interfaceOrThrow<nf7::Sequencer>();
|
auto& seq = ResolveOrThrow(mem_->path).interfaceOrThrow<nf7::Sequencer>();
|
||||||
if (seq.flags() & nf7::Sequencer::kParamPanel) {
|
if (seq.flags() & nf7::Sequencer::kParamPanel) {
|
||||||
Adaptor::Editor ed;
|
Adaptor::Editor ed;
|
||||||
seq.UpdateParamPanel(ed);
|
seq.UpdateParamPanel(ed);
|
||||||
@@ -406,4 +395,3 @@ void Adaptor::UpdateTooltip(Sequencer::Editor&) noexcept {
|
|||||||
|
|
||||||
}
|
}
|
||||||
} // namespace nf7
|
} // namespace nf7
|
||||||
|
|
||||||
|
|||||||
@@ -12,12 +12,10 @@
|
|||||||
#include <yas/types/std/vector.hpp>
|
#include <yas/types/std/vector.hpp>
|
||||||
|
|
||||||
#include "common/file_base.hh"
|
#include "common/file_base.hh"
|
||||||
#include "common/file_holder.hh"
|
#include "common/gui.hh"
|
||||||
#include "common/generic_context.hh"
|
#include "common/generic_context.hh"
|
||||||
#include "common/generic_memento.hh"
|
#include "common/generic_memento.hh"
|
||||||
#include "common/generic_type_info.hh"
|
#include "common/generic_type_info.hh"
|
||||||
#include "common/gui_file.hh"
|
|
||||||
#include "common/gui_popup.hh"
|
|
||||||
#include "common/life.hh"
|
#include "common/life.hh"
|
||||||
#include "common/node.hh"
|
#include "common/node.hh"
|
||||||
#include "common/ptr_selector.hh"
|
#include "common/ptr_selector.hh"
|
||||||
@@ -30,46 +28,40 @@ namespace {
|
|||||||
class Call final : public nf7::FileBase, public nf7::Sequencer {
|
class Call final : public nf7::FileBase, public nf7::Sequencer {
|
||||||
public:
|
public:
|
||||||
static inline const nf7::GenericTypeInfo<Call> kType = {
|
static inline const nf7::GenericTypeInfo<Call> kType = {
|
||||||
"Sequencer/Call", {"nf7::Sequencer"}};
|
"Sequencer/Call", {"nf7::Sequencer"},
|
||||||
static void UpdateTypeTooltip() noexcept {
|
"calls an external Node as a Sequencer",
|
||||||
ImGui::TextUnformatted("Calls a Node.");
|
};
|
||||||
ImGui::Bullet(); ImGui::TextUnformatted(
|
|
||||||
"implements nf7::Sequencer");
|
|
||||||
ImGui::Bullet(); ImGui::TextUnformatted(
|
|
||||||
"changes will be applied to active lambdas immediately");
|
|
||||||
}
|
|
||||||
|
|
||||||
class Lambda;
|
class Lambda;
|
||||||
class SessionLambda;
|
class SessionLambda;
|
||||||
|
|
||||||
struct Data {
|
struct Data {
|
||||||
nf7::FileHolder::Tag callee;
|
nf7::File::Path callee;
|
||||||
std::string expects;
|
std::string expects;
|
||||||
bool pure;
|
bool pure;
|
||||||
|
|
||||||
|
void serialize(auto& ar) {
|
||||||
|
ar(callee, expects, pure);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Call(nf7::Env& env, Data&& data = {}) noexcept :
|
Call(nf7::Env& env, Data&& data = {}) noexcept :
|
||||||
FileBase(kType, env, {&callee_, &callee_editor_}),
|
nf7::FileBase(kType, env),
|
||||||
Sequencer(Sequencer::kCustomItem |
|
Sequencer(Sequencer::kCustomItem |
|
||||||
Sequencer::kTooltip |
|
Sequencer::kTooltip |
|
||||||
Sequencer::kParamPanel),
|
Sequencer::kParamPanel),
|
||||||
life_(*this),
|
life_(*this),
|
||||||
callee_(*this, "callee", mem_),
|
mem_(*this, std::move(data)) {
|
||||||
callee_editor_(callee_,
|
|
||||||
[](auto& t) { return t.flags().contains("nf7::Node"); }),
|
|
||||||
mem_(std::move(data), *this) {
|
|
||||||
mem_.data().callee.SetTarget(callee_);
|
|
||||||
mem_.CommitAmend();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Call(nf7::Deserializer& ar) : Call(ar.env()) {
|
Call(nf7::Deserializer& ar) : Call(ar.env()) {
|
||||||
ar(callee_, data().expects, data().pure);
|
ar(mem_.data());
|
||||||
}
|
}
|
||||||
void Serialize(nf7::Serializer& ar) const noexcept override {
|
void Serialize(nf7::Serializer& ar) const noexcept override {
|
||||||
ar(callee_, data().expects, data().pure);
|
ar(mem_.data());
|
||||||
}
|
}
|
||||||
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
|
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
|
||||||
return std::make_unique<Call>(env, Data {data()});
|
return std::make_unique<Call>(env, Data {mem_.data()});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<nf7::Sequencer::Lambda> CreateLambda(
|
std::shared_ptr<nf7::Sequencer::Lambda> CreateLambda(
|
||||||
@@ -80,20 +72,14 @@ class Call final : public nf7::FileBase, public nf7::Sequencer {
|
|||||||
void UpdateTooltip(nf7::Sequencer::Editor&) noexcept override;
|
void UpdateTooltip(nf7::Sequencer::Editor&) noexcept override;
|
||||||
|
|
||||||
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
|
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
|
||||||
return InterfaceSelector<
|
return nf7::InterfaceSelector<
|
||||||
nf7::Memento, nf7::Sequencer>(t).Select(this, &mem_);
|
nf7::Memento, nf7::Sequencer>(t).Select(this, &mem_);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
nf7::Life<Call> life_;
|
nf7::Life<Call> life_;
|
||||||
nf7::FileHolder callee_;
|
|
||||||
|
|
||||||
nf7::gui::FileHolderEditor callee_editor_;
|
|
||||||
|
|
||||||
nf7::GenericMemento<Data> mem_;
|
nf7::GenericMemento<Data> mem_;
|
||||||
|
|
||||||
Data& data() noexcept { return mem_.data(); }
|
|
||||||
const Data& data() const noexcept { return mem_.data(); }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -127,7 +113,7 @@ class Call::SessionLambda final : public nf7::Node::Lambda {
|
|||||||
assert(!ss_);
|
assert(!ss_);
|
||||||
ss_ = ss;
|
ss_ = ss;
|
||||||
|
|
||||||
const auto ex = f.data().expects;
|
const auto ex = f.mem_->expects;
|
||||||
size_t begin = 0;
|
size_t begin = 0;
|
||||||
for (size_t i = 0; i <= ex.size(); ++i) {
|
for (size_t i = 0; i <= ex.size(); ++i) {
|
||||||
if (i == ex.size() || ex[i] == '\n') {
|
if (i == ex.size() || ex[i] == '\n') {
|
||||||
@@ -140,12 +126,10 @@ class Call::SessionLambda final : public nf7::Node::Lambda {
|
|||||||
}
|
}
|
||||||
FinishIf();
|
FinishIf();
|
||||||
}
|
}
|
||||||
void Handle(std::string_view name, const nf7::Value& val,
|
void Handle(const nf7::Node::Lambda::Msg& in) noexcept override {
|
||||||
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override {
|
|
||||||
if (!ss_) return;
|
if (!ss_) return;
|
||||||
ss_->Send(name, nf7::Value {val});
|
ss_->Send(in.name, nf7::Value {in.value});
|
||||||
|
expects_.erase(in.name);
|
||||||
expects_.erase(std::string {name});
|
|
||||||
FinishIf();
|
FinishIf();
|
||||||
}
|
}
|
||||||
void Abort() noexcept override {
|
void Abort() noexcept override {
|
||||||
@@ -177,8 +161,8 @@ try {
|
|||||||
if (abort_) return;
|
if (abort_) return;
|
||||||
file_.EnforceAlive();
|
file_.EnforceAlive();
|
||||||
|
|
||||||
auto& data = file_->data();
|
auto& data = file_->mem_.data();
|
||||||
auto& callee = file_->callee_.GetFileOrThrow();
|
auto& callee = file_->ResolveOrThrow(data.callee);
|
||||||
auto& node = callee.interfaceOrThrow<nf7::Node>();
|
auto& node = callee.interfaceOrThrow<nf7::Node>();
|
||||||
|
|
||||||
if (!ssla_) {
|
if (!ssla_) {
|
||||||
@@ -191,7 +175,8 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ssla_->Listen(*file_, ss);
|
ssla_->Listen(*file_, ss);
|
||||||
for (const auto& name : node.GetInputs()) {
|
const auto inputs = node.GetMeta().inputs;
|
||||||
|
for (const auto& name : inputs) {
|
||||||
if (auto v = ss->Receive(name)) {
|
if (auto v = ss->Receive(name)) {
|
||||||
la_->Handle(name, *v, ssla_);
|
la_->Handle(name, *v, ssla_);
|
||||||
}
|
}
|
||||||
@@ -201,11 +186,7 @@ try {
|
|||||||
ssla_ = nullptr;
|
ssla_ = nullptr;
|
||||||
la_ = nullptr;
|
la_ = nullptr;
|
||||||
}
|
}
|
||||||
} catch (nf7::LifeExpiredException&) {
|
} catch (nf7::Exception&) {
|
||||||
ss->Finish();
|
|
||||||
} catch (nf7::FileHolder::EmptyException&) {
|
|
||||||
ss->Finish();
|
|
||||||
} catch (nf7::File::NotImplementedException&) {
|
|
||||||
ss->Finish();
|
ss->Finish();
|
||||||
}
|
}
|
||||||
void Call::Lambda::Abort() noexcept {
|
void Call::Lambda::Abort() noexcept {
|
||||||
@@ -221,30 +202,35 @@ void Call::Lambda::Abort() noexcept {
|
|||||||
|
|
||||||
|
|
||||||
void Call::UpdateItem(Sequencer::Editor&) noexcept {
|
void Call::UpdateItem(Sequencer::Editor&) noexcept {
|
||||||
ImGui::Text("%s", callee_editor_.GetDisplayText().c_str());
|
ImGui::Text("%s", mem_->callee.Stringify().c_str());
|
||||||
}
|
}
|
||||||
void Call::UpdateParamPanel(Sequencer::Editor&) noexcept {
|
void Call::UpdateParamPanel(Sequencer::Editor&) noexcept {
|
||||||
const auto em = ImGui::GetFontSize();
|
const auto em = ImGui::GetFontSize();
|
||||||
|
|
||||||
|
bool commit = false;
|
||||||
if (ImGui::CollapsingHeader("Sequencer/Call", ImGuiTreeNodeFlags_DefaultOpen)) {
|
if (ImGui::CollapsingHeader("Sequencer/Call", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||||
callee_editor_.ButtonWithLabel("callee");
|
if (nf7::gui::PathButton("callee", mem_->callee, *this)) {
|
||||||
|
commit = true;
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::InputTextMultiline("expects", &data().expects, {0, 4.f*em});
|
ImGui::InputTextMultiline("expects", &mem_->expects, {0, 4.f*em});
|
||||||
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
||||||
mem_.Commit();
|
commit = true;
|
||||||
}
|
}
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ImGui::IsItemHovered()) {
|
||||||
ImGui::SetTooltip("session ends right after receiving these outputs");
|
ImGui::SetTooltip("session ends right after receiving these outputs");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui::Checkbox("pure", &data().pure)) {
|
if (ImGui::Checkbox("pure", &mem_->pure)) {
|
||||||
mem_.Commit();
|
commit = true;
|
||||||
}
|
}
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ImGui::IsItemHovered()) {
|
||||||
ImGui::SetTooltip("callee's lambda is created for each session");
|
ImGui::SetTooltip("callee's lambda is created for each session");
|
||||||
}
|
}
|
||||||
ImGui::Spacing();
|
}
|
||||||
callee_editor_.ItemWidget("callee");
|
|
||||||
|
if (commit) {
|
||||||
|
mem_.Commit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void Call::UpdateTooltip(Sequencer::Editor&) noexcept {
|
void Call::UpdateTooltip(Sequencer::Editor&) noexcept {
|
||||||
|
|||||||
@@ -25,12 +25,11 @@
|
|||||||
#include "common/file_base.hh"
|
#include "common/file_base.hh"
|
||||||
#include "common/generic_context.hh"
|
#include "common/generic_context.hh"
|
||||||
#include "common/generic_type_info.hh"
|
#include "common/generic_type_info.hh"
|
||||||
#include "common/gui_context.hh"
|
#include "common/gui.hh"
|
||||||
#include "common/gui_file.hh"
|
|
||||||
#include "common/gui_popup.hh"
|
|
||||||
#include "common/gui_timeline.hh"
|
#include "common/gui_timeline.hh"
|
||||||
#include "common/gui_window.hh"
|
#include "common/gui_window.hh"
|
||||||
#include "common/life.hh"
|
#include "common/life.hh"
|
||||||
|
#include "common/logger_ref.hh"
|
||||||
#include "common/memento.hh"
|
#include "common/memento.hh"
|
||||||
#include "common/memento_recorder.hh"
|
#include "common/memento_recorder.hh"
|
||||||
#include "common/node.hh"
|
#include "common/node.hh"
|
||||||
@@ -48,11 +47,8 @@ namespace {
|
|||||||
class TL final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
|
class TL final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
|
||||||
public:
|
public:
|
||||||
static inline const nf7::GenericTypeInfo<TL> kType = {
|
static inline const nf7::GenericTypeInfo<TL> kType = {
|
||||||
"Sequencer/Timeline", {"nf7::DirItem"}};
|
"Sequencer/Timeline", {"nf7::DirItem"},
|
||||||
static void UpdateTypeTooltip() noexcept {
|
};
|
||||||
ImGui::TextUnformatted("Timeline data");
|
|
||||||
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Node");
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Timing;
|
struct Timing;
|
||||||
|
|
||||||
@@ -63,60 +59,41 @@ class TL final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
|
|||||||
class Session;
|
class Session;
|
||||||
class Lambda;
|
class Lambda;
|
||||||
|
|
||||||
class ConfigModifyCommand;
|
|
||||||
|
|
||||||
using ItemId = uint64_t;
|
using ItemId = uint64_t;
|
||||||
|
|
||||||
TL(nf7::Env& env,
|
TL(nf7::Env& env,
|
||||||
std::vector<std::unique_ptr<Layer>>&& layers = {},
|
std::vector<std::unique_ptr<Layer>>&& layers = {},
|
||||||
ItemId next = 1,
|
ItemId next = 1) noexcept :
|
||||||
const nf7::gui::Window* win = nullptr) noexcept :
|
nf7::FileBase(kType, env),
|
||||||
nf7::FileBase(kType, env, {&popup_socket_, &popup_add_item_}),
|
nf7::DirItem(nf7::DirItem::kMenu),
|
||||||
nf7::DirItem(nf7::DirItem::kMenu | nf7::DirItem::kWidget),
|
nf7::Node(nf7::Node::kNone),
|
||||||
nf7::Node(nf7::Node::kMenu_DirItem),
|
life_(*this), log_(*this),
|
||||||
life_(*this),
|
|
||||||
layers_(std::move(layers)), next_(next),
|
layers_(std::move(layers)), next_(next),
|
||||||
win_(*this, "Timeline Editor", win), tl_("timeline"),
|
win_(*this, "Timeline Editor"), tl_("timeline") {
|
||||||
popup_add_item_(*this) {
|
win_.onUpdate = [this]() { TimelineEditor(); };
|
||||||
ApplySeqSocketChanges();
|
|
||||||
|
|
||||||
popup_socket_.onSubmit = [this](auto&& i, auto&& o) {
|
|
||||||
ExecChangeSeqSocket(std::move(i), std::move(o));
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
~TL() noexcept {
|
~TL() noexcept {
|
||||||
history_.Clear();
|
history_.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
TL(nf7::Deserializer& ar) : TL(ar.env()) {
|
TL(nf7::Deserializer& ar) : TL(ar.env()) {
|
||||||
ar(seq_inputs_, seq_outputs_, win_, tl_, layers_);
|
ar(win_, tl_, layers_);
|
||||||
AssignId();
|
AssignId();
|
||||||
ApplySeqSocketChanges();
|
|
||||||
}
|
}
|
||||||
void Serialize(nf7::Serializer& ar) const noexcept override {
|
void Serialize(nf7::Serializer& ar) const noexcept override {
|
||||||
ar(seq_inputs_, seq_outputs_, win_, tl_, layers_);
|
ar(win_, tl_, layers_);
|
||||||
}
|
}
|
||||||
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override;
|
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override;
|
||||||
|
|
||||||
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
|
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
|
||||||
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
|
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
|
||||||
std::span<const std::string> GetInputs() const noexcept override {
|
nf7::Node::Meta GetMeta() const noexcept override {
|
||||||
return inputs_;
|
return {{"exec"}, {"result"}};
|
||||||
}
|
|
||||||
std::span<const std::string> GetOutputs() const noexcept override {
|
|
||||||
return outputs_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Handle(const nf7::File::Event& ev) noexcept;
|
void PostHandle(const nf7::File::Event& ev) noexcept;
|
||||||
void Update() noexcept override;
|
void PostUpdate() noexcept override;
|
||||||
void UpdateMenu() noexcept override;
|
void UpdateMenu() noexcept override;
|
||||||
void UpdateWidget() noexcept override;
|
|
||||||
|
|
||||||
void UpdateEditorWindow() noexcept;
|
|
||||||
void UpdateLambdaSelector() noexcept;
|
|
||||||
void HandleTimelineAction() noexcept;
|
|
||||||
|
|
||||||
void UpdateParamPanelWindow() noexcept;
|
|
||||||
|
|
||||||
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
|
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
|
||||||
return nf7::InterfaceSelector<nf7::DirItem, nf7::Node>(t).Select(this);
|
return nf7::InterfaceSelector<nf7::DirItem, nf7::Node>(t).Select(this);
|
||||||
@@ -124,14 +101,13 @@ class TL final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
nf7::Life<TL> life_;
|
nf7::Life<TL> life_;
|
||||||
|
nf7::LoggerRef log_;
|
||||||
|
|
||||||
nf7::SquashedHistory history_;
|
nf7::SquashedHistory history_;
|
||||||
|
|
||||||
std::shared_ptr<TL::Lambda> lambda_;
|
std::shared_ptr<TL::Lambda> lambda_;
|
||||||
std::vector<std::weak_ptr<TL::Lambda>> lambdas_running_;
|
std::vector<std::weak_ptr<TL::Lambda>> lambdas_running_;
|
||||||
|
|
||||||
std::vector<std::string> inputs_, outputs_; // for GetInputs/GetOutputs
|
|
||||||
|
|
||||||
uint64_t action_time_;
|
uint64_t action_time_;
|
||||||
uint64_t action_layer_;
|
uint64_t action_layer_;
|
||||||
|
|
||||||
@@ -139,44 +115,12 @@ class TL final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
|
|||||||
uint64_t cursor_ = 0;
|
uint64_t cursor_ = 0;
|
||||||
std::vector<std::unique_ptr<Layer>> layers_;
|
std::vector<std::unique_ptr<Layer>> layers_;
|
||||||
|
|
||||||
std::vector<std::string> seq_inputs_;
|
|
||||||
std::vector<std::string> seq_outputs_;
|
|
||||||
|
|
||||||
ItemId next_;
|
ItemId next_;
|
||||||
|
|
||||||
nf7::gui::Window win_;
|
nf7::gui::Window win_;
|
||||||
nf7::gui::Timeline tl_;
|
nf7::gui::Timeline tl_;
|
||||||
|
|
||||||
|
|
||||||
// GUI popup
|
|
||||||
nf7::gui::IOSocketListPopup popup_socket_;
|
|
||||||
|
|
||||||
struct AddItemPopup final :
|
|
||||||
public nf7::FileBase::Feature, private nf7::gui::Popup {
|
|
||||||
public:
|
|
||||||
AddItemPopup(TL& f) noexcept :
|
|
||||||
Popup("AddItemPopup"),
|
|
||||||
owner_(&f),
|
|
||||||
factory_(f, [](auto& t) { return t.flags().contains("nf7::Sequencer"); }) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void Open(uint64_t t, TL::Layer& l) noexcept {
|
|
||||||
target_time_ = t;
|
|
||||||
target_layer_ = &l;
|
|
||||||
Popup::Open();
|
|
||||||
}
|
|
||||||
void Update() noexcept override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
TL* const owner_;
|
|
||||||
|
|
||||||
uint64_t target_time_ = 0;
|
|
||||||
TL::Layer* target_layer_ = nullptr;
|
|
||||||
|
|
||||||
nf7::gui::FileFactory factory_;
|
|
||||||
} popup_add_item_;
|
|
||||||
|
|
||||||
|
|
||||||
// GUI temporary params
|
// GUI temporary params
|
||||||
bool param_panel_request_focus_ = false;
|
bool param_panel_request_focus_ = false;
|
||||||
TL::Item* param_panel_target_ = nullptr;
|
TL::Item* param_panel_target_ = nullptr;
|
||||||
@@ -204,7 +148,7 @@ class TL final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
|
|||||||
void ExecApplyLayerOfSelected() noexcept;
|
void ExecApplyLayerOfSelected() noexcept;
|
||||||
void MoveDisplayLayerOfSelected(int64_t diff) noexcept;
|
void MoveDisplayLayerOfSelected(int64_t diff) noexcept;
|
||||||
|
|
||||||
// history
|
// history operation
|
||||||
void ExecUnDo() noexcept {
|
void ExecUnDo() noexcept {
|
||||||
env().ExecMain(
|
env().ExecMain(
|
||||||
std::make_shared<nf7::GenericContext>(*this, "reverting commands to undo"),
|
std::make_shared<nf7::GenericContext>(*this, "reverting commands to undo"),
|
||||||
@@ -216,19 +160,17 @@ class TL final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
|
|||||||
[this]() { history_.ReDo(); });
|
[this]() { history_.ReDo(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// instant running
|
// instant running
|
||||||
void MoveCursorTo(uint64_t t) noexcept;
|
void MoveCursorTo(uint64_t t) noexcept;
|
||||||
void AttachLambda(const std::shared_ptr<TL::Lambda>&) noexcept;
|
void AttachLambda(const std::shared_ptr<TL::Lambda>&) noexcept;
|
||||||
|
|
||||||
// socket operation
|
// gui
|
||||||
void ExecChangeSeqSocket(std::vector<std::string>&&, std::vector<std::string>&&) noexcept;
|
void TimelineEditor() noexcept;
|
||||||
void ApplySeqSocketChanges() noexcept {
|
void ParamPanel() noexcept;
|
||||||
inputs_ = seq_inputs_;
|
void LambdaSelector() noexcept;
|
||||||
inputs_.push_back("_exec");
|
void ItemAdder() noexcept;
|
||||||
|
|
||||||
outputs_ = seq_outputs_;
|
void HandleTimelineAction() noexcept;
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -419,6 +361,14 @@ class TL::Layer final {
|
|||||||
return std::make_unique<TL::Layer>(std::move(items), enabled_, height_);
|
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 {
|
void Attach(TL& f, TL::Layer* prev, TL::Layer* next) noexcept {
|
||||||
assert(!owner_);
|
assert(!owner_);
|
||||||
|
|
||||||
@@ -436,19 +386,6 @@ class TL::Layer final {
|
|||||||
next_ = nullptr;
|
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 {
|
TL::Item* GetAt(uint64_t t) const noexcept {
|
||||||
auto itr = std::find_if(
|
auto itr = std::find_if(
|
||||||
items_.begin(), items_.end(),
|
items_.begin(), items_.end(),
|
||||||
@@ -542,6 +479,26 @@ class TL::Layer final {
|
|||||||
// GUI temporary parameters
|
// GUI temporary parameters
|
||||||
size_t index_;
|
size_t index_;
|
||||||
float offset_y_;
|
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() {
|
void TL::AssignId() {
|
||||||
next_ = 1;
|
next_ = 1;
|
||||||
@@ -569,8 +526,7 @@ class TL::Lambda final : public Node::Lambda,
|
|||||||
Node::Lambda(f, parent), owner_(f.life_) {
|
Node::Lambda(f, parent), owner_(f.life_) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Handle(std::string_view, const nf7::Value&,
|
void Handle(const nf7::Node::Lambda::Msg& in) noexcept override;
|
||||||
const std::shared_ptr<Node::Lambda>&) noexcept override;
|
|
||||||
|
|
||||||
std::shared_ptr<TL::Session> CreateSession(uint64_t t) noexcept {
|
std::shared_ptr<TL::Session> CreateSession(uint64_t t) noexcept {
|
||||||
if (depth() != 0 && owner_ && owner_->lambda_.get() == this) {
|
if (depth() != 0 && owner_ && owner_->lambda_.get() == this) {
|
||||||
@@ -617,11 +573,11 @@ class TL::Lambda final : public Node::Lambda,
|
|||||||
auto caller = parent();
|
auto caller = parent();
|
||||||
if (!caller) return;
|
if (!caller) return;
|
||||||
|
|
||||||
for (const auto& name : owner_->seq_outputs_) {
|
std::vector<nf7::Value::TuplePair> tup;
|
||||||
auto itr = vars.find(name);
|
for (auto& p : vars) {
|
||||||
if (itr == vars.end()) continue;
|
tup.emplace_back(p.first, p.second);
|
||||||
caller->Handle(name, itr->second, shared_from_this());
|
|
||||||
}
|
}
|
||||||
|
caller->Handle("result", nf7::Value {std::move(tup)}, shared_from_this());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Abort() noexcept {
|
void Abort() noexcept {
|
||||||
@@ -765,21 +721,22 @@ class TL::Session final : public Sequencer::Session,
|
|||||||
static_cast<nf7::Value::Scalar>(t.dur());
|
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 {
|
void TL::Lambda::Handle(const nf7::Node::Lambda::Msg& in) noexcept {
|
||||||
if (name == "_exec") {
|
if (in.name == "_exec") {
|
||||||
if (!owner_) return;
|
if (!owner_) return;
|
||||||
|
|
||||||
uint64_t t;
|
uint64_t t;
|
||||||
if (v.isInteger()) {
|
if (in.value.isInteger()) {
|
||||||
const auto ti = std::max(v.integer(), int64_t{0});
|
const auto ti = std::max(in.value.integer(), int64_t{0});
|
||||||
t = static_cast<uint64_t>(ti);
|
t = static_cast<uint64_t>(ti);
|
||||||
} else {
|
} else {
|
||||||
// TODO: error
|
owner_->log_.Error("_exec takes a frame index");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CreateSession(t)->StartNext();
|
CreateSession(t)->StartNext();
|
||||||
} else {
|
} else {
|
||||||
vars_[std::string {name}] = v;
|
vars_[std::string {in.name}] = in.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void TL::MoveCursorTo(uint64_t time) noexcept {
|
void TL::MoveCursorTo(uint64_t time) noexcept {
|
||||||
@@ -805,7 +762,6 @@ class TL::Editor final : public nf7::Sequencer::Editor {
|
|||||||
public:
|
public:
|
||||||
Editor(TL::Item& item) noexcept : item_(&item) {
|
Editor(TL::Item& item) noexcept : item_(&item) {
|
||||||
}
|
}
|
||||||
// TODO
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TL::Item* const item_;
|
TL::Item* const item_;
|
||||||
@@ -936,28 +892,15 @@ class TL::Layer::ItemSwapCommand final : public nf7::History::Command {
|
|||||||
TL::Item* const ptr_;
|
TL::Item* const ptr_;
|
||||||
|
|
||||||
void Swap() {
|
void Swap() {
|
||||||
auto& items = layer_->items_;
|
|
||||||
if (item_) {
|
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_);
|
item_->Attach(*layer_->owner_, *layer_);
|
||||||
items.insert(itr, std::move(item_));
|
layer_->AddItem(std::move(item_));
|
||||||
} else {
|
} else {
|
||||||
auto itr = std::find_if(items.begin(), items.end(),
|
item_ = layer_->RemoveItem(*ptr_);
|
||||||
[ptr = ptr_](auto& x) { return x.get() == ptr; });
|
if (!item_) {
|
||||||
if (itr == items.end()) {
|
|
||||||
throw nf7::History::CorruptException {"target item missing"};
|
throw nf7::History::CorruptException {"target item missing"};
|
||||||
}
|
}
|
||||||
item_ = std::move(*itr);
|
|
||||||
item_->Detach();
|
item_->Detach();
|
||||||
items.erase(itr);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -983,8 +926,7 @@ class TL::Layer::ItemTimingSwapCommand final : public nf7::History::Command {
|
|||||||
void Exec() noexcept {
|
void Exec() noexcept {
|
||||||
std::swap(item_->timing(), timing_);
|
std::swap(item_->timing(), timing_);
|
||||||
item_->displayTiming() = item_->timing();
|
item_->displayTiming() = item_->timing();
|
||||||
|
item_->layer().ReorderItem(*item_);
|
||||||
// TODO: reorder item
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
void TL::ExecApplyTimingOfSelected() noexcept {
|
void TL::ExecApplyTimingOfSelected() noexcept {
|
||||||
@@ -1070,11 +1012,11 @@ class TL::Layer::ItemMoveCommand final : public nf7::History::Command {
|
|||||||
src_(&src), dst_(&dst), item_(&item) {
|
src_(&src), dst_(&dst), item_(&item) {
|
||||||
}
|
}
|
||||||
void Apply() noexcept override {
|
void Apply() noexcept override {
|
||||||
src_->MoveItemTo(*dst_, *item_);
|
dst_->AddItem(src_->RemoveItem(*item_));
|
||||||
item_->MoveTo(*dst_);
|
item_->MoveTo(*dst_);
|
||||||
}
|
}
|
||||||
void Revert() noexcept override {
|
void Revert() noexcept override {
|
||||||
dst_->MoveItemTo(*src_, *item_);
|
src_->AddItem(dst_->RemoveItem(*item_));
|
||||||
item_->MoveTo(*src_);
|
item_->MoveTo(*src_);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1124,81 +1066,25 @@ void TL::MoveDisplayLayerOfSelected(int64_t diff) noexcept {
|
|||||||
layers.emplace_back(item, &layer);
|
layers.emplace_back(item, &layer);
|
||||||
}
|
}
|
||||||
for (auto& p : layers) {
|
for (auto& p : layers) {
|
||||||
p.first->displayLayer().MoveItemTo(*p.second, *p.first);
|
auto& item = *p.first;
|
||||||
p.first->DisplayOn(*p.second);
|
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::unique_ptr<nf7::File> TL::Clone(nf7::Env& env) const noexcept {
|
||||||
std::vector<std::unique_ptr<TL::Layer>> layers;
|
std::vector<std::unique_ptr<TL::Layer>> layers;
|
||||||
layers.reserve(layers_.size());
|
layers.reserve(layers_.size());
|
||||||
ItemId next = 1;
|
ItemId next = 1;
|
||||||
for (const auto& layer : layers_) layers.push_back(layer->Clone(env, next));
|
for (const auto& layer : layers_) {
|
||||||
return std::make_unique<TL>(env, std::move(layers), next, &win_);
|
layers.push_back(layer->Clone(env, next));
|
||||||
}
|
}
|
||||||
void TL::Handle(const Event& ev) noexcept {
|
return std::make_unique<TL>(env, std::move(layers), next);
|
||||||
nf7::FileBase::Handle(ev);
|
}
|
||||||
|
void TL::PostHandle(const Event& ev) noexcept {
|
||||||
switch (ev.type) {
|
switch (ev.type) {
|
||||||
case Event::kAdd:
|
case Event::kAdd:
|
||||||
if (layers_.size() == 0) {
|
if (layers_.size() == 0) {
|
||||||
@@ -1228,51 +1114,42 @@ void TL::Handle(const Event& ev) noexcept {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TL::Update() noexcept {
|
void TL::PostUpdate() noexcept {
|
||||||
nf7::FileBase::Update();
|
// display param panel window
|
||||||
|
if (win_.shown()) {
|
||||||
|
const auto em = ImGui::GetFontSize();
|
||||||
|
const auto id = nf7::gui::Window::ConcatId(*this, "Parameter Panel");
|
||||||
|
|
||||||
|
if (std::exchange(param_panel_request_focus_, false)) {
|
||||||
|
ImGui::SetNextWindowFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SetNextWindowSize({16*em, 16*em}, ImGuiCond_FirstUseEver);
|
||||||
|
if (ImGui::Begin(id.c_str())) {
|
||||||
|
ParamPanel();
|
||||||
|
}
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
// update children
|
||||||
for (const auto& layer : layers_) {
|
for (const auto& layer : layers_) {
|
||||||
for (const auto& item : layer->items()) {
|
for (const auto& item : layer->items()) {
|
||||||
item->file().Update();
|
item->file().Update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateEditorWindow();
|
// squash queued commands
|
||||||
UpdateParamPanelWindow();
|
|
||||||
|
|
||||||
if (history_.Squash()) {
|
if (history_.Squash()) {
|
||||||
env().ExecMain(std::make_shared<nf7::GenericContext>(*this),
|
env().ExecMain(std::make_shared<nf7::GenericContext>(*this),
|
||||||
[this]() { Touch(); });
|
[this]() { Touch(); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void TL::UpdateMenu() noexcept {
|
void TL::UpdateMenu() noexcept {
|
||||||
if (ImGui::MenuItem("editor", nullptr, &win_.shown()) && win_.shown()) {
|
win_.MenuItem();
|
||||||
win_.SetFocus();
|
|
||||||
}
|
|
||||||
if (ImGui::MenuItem("I/O list")) {
|
|
||||||
popup_socket_.Open(seq_inputs_, seq_outputs_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void TL::UpdateWidget() noexcept {
|
|
||||||
ImGui::TextUnformatted("Sequencer/Timeline");
|
|
||||||
|
|
||||||
|
|
||||||
if (ImGui::Button("Editor")) {
|
|
||||||
win_.SetFocus();
|
|
||||||
}
|
|
||||||
if (ImGui::Button("I/O list")) {
|
|
||||||
popup_socket_.Open(seq_inputs_, seq_outputs_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
popup_socket_.Update();
|
void TL::TimelineEditor() noexcept {
|
||||||
}
|
LambdaSelector();
|
||||||
void TL::UpdateEditorWindow() noexcept {
|
|
||||||
if (win_.shownInCurrentFrame()) {
|
|
||||||
const auto em = ImGui::GetFontSize();
|
|
||||||
ImGui::SetNextWindowSizeConstraints({32*em, 16*em}, {1e8, 1e8});
|
|
||||||
}
|
|
||||||
if (win_.Begin()) {
|
|
||||||
UpdateLambdaSelector();
|
|
||||||
|
|
||||||
// timeline
|
// timeline
|
||||||
if (tl_.Begin()) {
|
if (tl_.Begin()) {
|
||||||
@@ -1295,9 +1172,10 @@ void TL::UpdateEditorWindow() noexcept {
|
|||||||
action_layer_ = layer->index();
|
action_layer_ = layer->index();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ImGui::MenuItem("add new item")) {
|
|
||||||
if (action_layer_ < layers_.size()) {
|
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()) {
|
if (selected_.size()) {
|
||||||
@@ -1313,10 +1191,6 @@ void TL::UpdateEditorWindow() noexcept {
|
|||||||
if (ImGui::MenuItem("redo", nullptr, false, !!history_.next())) {
|
if (ImGui::MenuItem("redo", nullptr, false, !!history_.next())) {
|
||||||
ExecReDo();
|
ExecReDo();
|
||||||
}
|
}
|
||||||
ImGui::Separator();
|
|
||||||
if (ImGui::MenuItem("I/O socket list")) {
|
|
||||||
popup_socket_.Open(seq_inputs_, seq_outputs_);
|
|
||||||
}
|
|
||||||
ImGui::EndPopup();
|
ImGui::EndPopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1398,14 +1272,24 @@ void TL::UpdateEditorWindow() noexcept {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
win_.End();
|
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");
|
||||||
}
|
}
|
||||||
void TL::UpdateLambdaSelector() noexcept {
|
} else {
|
||||||
|
ImGui::TextUnformatted("no item selected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void TL::LambdaSelector() noexcept {
|
||||||
const auto current_lambda =
|
const auto current_lambda =
|
||||||
lambda_? nf7::gui::GetParentContextDisplayName(*lambda_): "(unselected)";
|
lambda_? nf7::gui::GetParentContextDisplayName(*lambda_): "(unselected)";
|
||||||
if (ImGui::BeginCombo("##lambda", current_lambda.c_str())) {
|
if (ImGui::BeginCombo("##lambda", current_lambda.c_str())) {
|
||||||
if (lambda_) {
|
if (lambda_) {
|
||||||
if (ImGui::Selectable("detach from current lambda")) {
|
if (ImGui::Selectable("detach current lambda")) {
|
||||||
AttachLambda(nullptr);
|
AttachLambda(nullptr);
|
||||||
}
|
}
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
@@ -1430,6 +1314,84 @@ void TL::UpdateLambdaSelector() noexcept {
|
|||||||
ImGui::EndCombo();
|
ImGui::EndCombo();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
void TL::ItemAdder() noexcept {
|
||||||
|
// parameters
|
||||||
|
auto& layer = *layers_[action_layer_];
|
||||||
|
auto time = action_time_;
|
||||||
|
|
||||||
|
uint64_t dur = static_cast<uint64_t>(4.f / tl_.zoom());
|
||||||
|
if (auto item = layer.FindItemAfter(time)) {
|
||||||
|
dur = std::min(dur, item->timing().begin() - time);
|
||||||
|
}
|
||||||
|
|
||||||
|
// header and initialization
|
||||||
|
static const nf7::File::TypeInfo* type;
|
||||||
|
if (ImGui::IsWindowAppearing()) {
|
||||||
|
type = nullptr;
|
||||||
|
}
|
||||||
|
ImGui::TextUnformatted("Sequencer/Timeline: adding new item...");
|
||||||
|
|
||||||
|
const auto em = ImGui::GetFontSize();
|
||||||
|
|
||||||
|
// type list
|
||||||
|
bool exec = false;
|
||||||
|
if (ImGui::BeginListBox("type", {16*em, 8*em})) {
|
||||||
|
for (auto& p : nf7::File::registry()) {
|
||||||
|
const auto& t = *p.second;
|
||||||
|
if (!t.flags().contains("nf7::Sequencer")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr auto kFlags =
|
||||||
|
ImGuiSelectableFlags_SpanAllColumns |
|
||||||
|
ImGuiSelectableFlags_AllowItemOverlap;
|
||||||
|
if (ImGui::Selectable(t.name().c_str(), type == &t, kFlags)) {
|
||||||
|
type = &t;
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::BeginTooltip();
|
||||||
|
t.UpdateTooltip();
|
||||||
|
ImGui::EndTooltip();
|
||||||
|
|
||||||
|
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
|
||||||
|
exec = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndListBox();
|
||||||
|
}
|
||||||
|
|
||||||
|
// validation
|
||||||
|
bool valid = true;
|
||||||
|
if (type == nullptr) {
|
||||||
|
ImGui::Bullet(); ImGui::TextUnformatted("type not selected");
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
if (dur == 0) {
|
||||||
|
ImGui::Bullet(); ImGui::TextUnformatted("no space to insert new item");
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ok button
|
||||||
|
ImGui::BeginDisabled(!valid);
|
||||||
|
if (ImGui::Button("ok")) {
|
||||||
|
exec = true;
|
||||||
|
}
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
|
||||||
|
// adding
|
||||||
|
if (exec && valid) {
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
|
||||||
|
auto file = type->Create(env());
|
||||||
|
auto timing = TL::Timing::BeginDur(time, dur);
|
||||||
|
auto item = std::make_unique<TL::Item>(next_++, std::move(file), timing);
|
||||||
|
auto cmd = std::make_unique<TL::Layer::ItemSwapCommand>(layer, std::move(item));
|
||||||
|
auto ctx = std::make_shared<nf7::GenericContext>(*this, "adding new item");
|
||||||
|
history_.Add(std::move(cmd)).ExecApply(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void TL::HandleTimelineAction() noexcept {
|
void TL::HandleTimelineAction() noexcept {
|
||||||
auto item = reinterpret_cast<TL::Item*>(tl_.actionTarget());
|
auto item = reinterpret_cast<TL::Item*>(tl_.actionTarget());
|
||||||
const auto action_time = tl_.actionTime();
|
const auto action_time = tl_.actionTime();
|
||||||
@@ -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 {
|
void TL::Layer::UpdateHeader(size_t idx) noexcept {
|
||||||
index_ = idx;
|
index_ = idx;
|
||||||
@@ -1569,6 +1505,7 @@ void TL::Layer::UpdateHeader(size_t idx) noexcept {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TL::Item::Update() noexcept {
|
void TL::Item::Update() noexcept {
|
||||||
assert(owner_);
|
assert(owner_);
|
||||||
assert(layer_);
|
assert(layer_);
|
||||||
@@ -1584,6 +1521,8 @@ void TL::Item::Update() noexcept {
|
|||||||
if (ImGui::MenuItem("remove")) {
|
if (ImGui::MenuItem("remove")) {
|
||||||
layer_->ExecRemoveItem(*this);
|
layer_->ExecRemoveItem(*this);
|
||||||
}
|
}
|
||||||
|
nf7::gui::FileMenuItems(*file_);
|
||||||
|
|
||||||
if (seq_->flags() & nf7::Sequencer::kMenu) {
|
if (seq_->flags() & nf7::Sequencer::kMenu) {
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
seq_->UpdateMenu(ed);
|
seq_->UpdateMenu(ed);
|
||||||
@@ -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
|
} // namespace nf7
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <imgui_internal.h>
|
#include <imgui_internal.h>
|
||||||
@@ -11,16 +12,19 @@
|
|||||||
#include <yas/types/std/unordered_set.hpp>
|
#include <yas/types/std/unordered_set.hpp>
|
||||||
#include <yas/types/std/string.hpp>
|
#include <yas/types/std/string.hpp>
|
||||||
|
|
||||||
|
#include <tracy/Tracy.hpp>
|
||||||
|
|
||||||
#include "nf7.hh"
|
#include "nf7.hh"
|
||||||
|
|
||||||
|
#include "common/config.hh"
|
||||||
#include "common/dir.hh"
|
#include "common/dir.hh"
|
||||||
#include "common/dir_item.hh"
|
#include "common/dir_item.hh"
|
||||||
#include "common/file_base.hh"
|
#include "common/file_base.hh"
|
||||||
#include "common/generic_context.hh"
|
#include "common/generic_context.hh"
|
||||||
|
#include "common/generic_dir.hh"
|
||||||
#include "common/generic_type_info.hh"
|
#include "common/generic_type_info.hh"
|
||||||
|
#include "common/gui.hh"
|
||||||
#include "common/gui_dnd.hh"
|
#include "common/gui_dnd.hh"
|
||||||
#include "common/gui_file.hh"
|
|
||||||
#include "common/gui_popup.hh"
|
|
||||||
#include "common/gui_window.hh"
|
#include "common/gui_window.hh"
|
||||||
#include "common/ptr_selector.hh"
|
#include "common/ptr_selector.hh"
|
||||||
#include "common/yas_nf7.hh"
|
#include "common/yas_nf7.hh"
|
||||||
@@ -30,240 +34,96 @@ namespace nf7 {
|
|||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
class Dir final : public nf7::FileBase,
|
class Dir final : public nf7::FileBase,
|
||||||
public nf7::Dir,
|
|
||||||
public nf7::DirItem {
|
public nf7::DirItem {
|
||||||
public:
|
public:
|
||||||
static inline const GenericTypeInfo<Dir> kType = {"System/Dir", {"nf7::DirItem"}};
|
static inline const nf7::GenericTypeInfo<Dir> kType = {
|
||||||
static constexpr const char* kTypeDescription = "generic directory";
|
"System/Dir", {"nf7::DirItem"}, "generic directory",
|
||||||
|
};
|
||||||
|
|
||||||
using ItemMap = std::map<std::string, std::unique_ptr<File>>;
|
using ItemMap = std::map<std::string, std::unique_ptr<File>>;
|
||||||
|
|
||||||
Dir(nf7::Env& env, ItemMap&& items = {}, const gui::Window* src = nullptr) noexcept :
|
Dir(nf7::Env& env, nf7::GenericDir::ItemMap&& items = {}) noexcept :
|
||||||
nf7::FileBase(kType, env, {&widget_popup_, &add_popup_, &rename_popup_}),
|
nf7::FileBase(kType, env),
|
||||||
nf7::DirItem(nf7::DirItem::kTree |
|
nf7::DirItem(nf7::DirItem::kTree |
|
||||||
nf7::DirItem::kMenu |
|
nf7::DirItem::kMenu |
|
||||||
nf7::DirItem::kTooltip |
|
nf7::DirItem::kTooltip |
|
||||||
nf7::DirItem::kDragDropTarget),
|
nf7::DirItem::kDragDropTarget),
|
||||||
items_(std::move(items)), win_(*this, "TreeView System/Dir", src),
|
dir_(*this, std::move(items)), win_(*this, "Tree View") {
|
||||||
widget_popup_(*this), add_popup_(*this), rename_popup_(*this) {
|
win_.onConfig = []() {
|
||||||
|
const auto em = ImGui::GetFontSize();
|
||||||
|
ImGui::SetNextWindowSize({8*em, 8*em}, ImGuiCond_FirstUseEver);
|
||||||
|
};
|
||||||
|
win_.onUpdate = [this]() { TreeView(); };
|
||||||
}
|
}
|
||||||
|
|
||||||
Dir(nf7::Deserializer& ar) : Dir(ar.env()) {
|
Dir(nf7::Deserializer& ar) : Dir(ar.env()) {
|
||||||
ar(opened_, win_);
|
ar(dir_, opened_, win_);
|
||||||
|
|
||||||
uint64_t size;
|
|
||||||
ar(size);
|
|
||||||
for (size_t i = 0; i < size; ++i) {
|
|
||||||
std::string name;
|
|
||||||
ar(name);
|
|
||||||
|
|
||||||
std::unique_ptr<nf7::File> f;
|
|
||||||
try {
|
|
||||||
ar(f);
|
|
||||||
items_[name] = std::move(f);
|
|
||||||
} catch (nf7::Exception&) {
|
|
||||||
env().Throw(std::current_exception());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
void Serialize(nf7::Serializer& ar) const noexcept override {
|
void Serialize(nf7::Serializer& ar) const noexcept override {
|
||||||
ar(opened_, win_);
|
ar(dir_, opened_, win_);
|
||||||
|
|
||||||
ar(static_cast<uint64_t>(items_.size()));
|
|
||||||
for (auto& p : items_) {
|
|
||||||
ar(p.first, p.second);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
|
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
|
||||||
ItemMap items;
|
return std::make_unique<Dir>(env, dir_.CloneItems(env));
|
||||||
for (const auto& item : items_) {
|
|
||||||
items[item.first] = item.second->Clone(env);
|
|
||||||
}
|
|
||||||
return std::make_unique<Dir>(env, std::move(items));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
File* Find(std::string_view name) const noexcept override {
|
|
||||||
auto itr = items_.find(std::string(name));
|
|
||||||
if (itr == items_.end()) return nullptr;
|
|
||||||
return itr->second.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
File& Add(std::string_view name, std::unique_ptr<File>&& f) override {
|
|
||||||
const auto sname = std::string(name);
|
|
||||||
|
|
||||||
auto [itr, ok] = items_.emplace(sname, std::move(f));
|
|
||||||
if (!ok) throw DuplicateException("item name duplication: "+sname);
|
|
||||||
|
|
||||||
auto& ret = *itr->second;
|
|
||||||
if (id()) ret.MoveUnder(*this, name);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
std::unique_ptr<File> Remove(std::string_view name) noexcept override {
|
|
||||||
auto itr = items_.find(std::string(name));
|
|
||||||
if (itr == items_.end()) return nullptr;
|
|
||||||
|
|
||||||
auto ret = std::move(itr->second);
|
|
||||||
items_.erase(itr);
|
|
||||||
if (id()) ret->Isolate();
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Update() noexcept override;
|
|
||||||
void UpdateTree() noexcept override;
|
void UpdateTree() noexcept override;
|
||||||
void UpdateMenu() noexcept override;
|
void UpdateMenu() noexcept override;
|
||||||
void UpdateTooltip() noexcept override;
|
void UpdateTooltip() noexcept override;
|
||||||
void UpdateDragDropTarget() noexcept override;
|
void UpdateDragDropTarget() noexcept override;
|
||||||
|
|
||||||
void Handle(const Event& ev) noexcept override {
|
void PostHandle(const Event& ev) noexcept override {
|
||||||
nf7::FileBase::Handle(ev);
|
|
||||||
switch (ev.type) {
|
switch (ev.type) {
|
||||||
case Event::kAdd:
|
case Event::kAdd:
|
||||||
// force to show window if this is the root
|
// force to show window if this is the root
|
||||||
if (name() == "$") {
|
if (name() == "$") {
|
||||||
win_.shown() = true;
|
win_.Show();
|
||||||
}
|
}
|
||||||
for (const auto& item : items_) item.second->MoveUnder(*this, item.first);
|
return;
|
||||||
break;
|
|
||||||
case Event::kRemove:
|
|
||||||
for (const auto& item : items_) item.second->Isolate();
|
|
||||||
break;
|
|
||||||
case Event::kReqFocus:
|
|
||||||
win_.SetFocus();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
File::Interface* interface(const std::type_info& t) noexcept override {
|
File::Interface* interface(const std::type_info& t) noexcept override {
|
||||||
return InterfaceSelector<nf7::Dir, nf7::DirItem>(t).Select(this);
|
return nf7::InterfaceSelector<nf7::Dir, nf7::DirItem>(t).Select(this, &dir_);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// persistent params
|
nf7::GenericDir dir_;
|
||||||
ItemMap items_;
|
std::unordered_set<std::string> opened_;
|
||||||
gui::Window win_;
|
gui::Window win_;
|
||||||
|
|
||||||
std::unordered_set<std::string> opened_;
|
std::vector<std::pair<std::string, std::unique_ptr<nf7::File>>> trash_;
|
||||||
|
|
||||||
|
|
||||||
// GUI popup
|
static bool TestFlags(nf7::File& f, nf7::DirItem::Flags flags) noexcept
|
||||||
class WidgetPopup final :
|
try {
|
||||||
public nf7::FileBase::Feature, private nf7::gui::Popup {
|
return f.interfaceOrThrow<nf7::DirItem>().flags() & flags;
|
||||||
public:
|
} catch (nf7::Exception&) {
|
||||||
WidgetPopup(Dir& owner) noexcept :
|
return false;
|
||||||
nf7::gui::Popup("WidgetPopup"), owner_(&owner) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Open(nf7::File& f) noexcept {
|
// imgui widgets
|
||||||
target_ = &f;
|
void TreeView() noexcept;
|
||||||
nf7::gui::Popup::Open();
|
void ItemAdder() noexcept;
|
||||||
}
|
void ItemRenamer(const std::string& name) noexcept;
|
||||||
void Update() noexcept override;
|
bool ValidateName(const std::string& name) noexcept;
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void Dir::Update() noexcept {
|
|
||||||
nf7::FileBase::Update();
|
|
||||||
|
|
||||||
const auto em = ImGui::GetFontSize();
|
|
||||||
|
|
||||||
// update children
|
|
||||||
for (const auto& item : items_) {
|
|
||||||
ImGui::PushID(item.second.get());
|
|
||||||
item.second->Update();
|
|
||||||
ImGui::PopID();
|
|
||||||
}
|
|
||||||
|
|
||||||
// tree view window
|
|
||||||
if (win_.shownInCurrentFrame()) {
|
|
||||||
ImGui::SetNextWindowSize({8*em, 8*em}, ImGuiCond_FirstUseEver);
|
|
||||||
}
|
|
||||||
if (win_.Begin()) {
|
|
||||||
if (ImGui::BeginPopupContextWindow()) {
|
|
||||||
UpdateMenu();
|
|
||||||
ImGui::EndPopup();
|
|
||||||
}
|
|
||||||
UpdateTree();
|
|
||||||
|
|
||||||
if (nf7::gui::dnd::IsFirstAccept()) {
|
|
||||||
ImGui::SetCursorPos({0, 0});
|
|
||||||
ImGui::Dummy(ImGui::GetContentRegionAvail());
|
|
||||||
if (ImGui::BeginDragDropTarget()) {
|
|
||||||
UpdateDragDropTarget();
|
|
||||||
ImGui::EndDragDropTarget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
win_.End();
|
|
||||||
}
|
|
||||||
void Dir::UpdateTree() noexcept {
|
void Dir::UpdateTree() noexcept {
|
||||||
for (const auto& item : items_) {
|
for (const auto& item : dir_.items()) {
|
||||||
ImGuiTreeNodeFlags flags =
|
|
||||||
ImGuiTreeNodeFlags_NoTreePushOnOpen |
|
|
||||||
ImGuiTreeNodeFlags_SpanFullWidth;
|
|
||||||
|
|
||||||
const auto& name = item.first;
|
const auto& name = item.first;
|
||||||
auto& file = *item.second;
|
auto& file = *item.second;
|
||||||
ImGui::PushID(&file);
|
ImGui::PushID(&file);
|
||||||
|
|
||||||
auto* ditem = file.interface<nf7::DirItem>();
|
auto* ditem = file.interface<nf7::DirItem>();
|
||||||
if (ditem && !(ditem->flags() & DirItem::kTree)) {
|
const auto flags = ditem? ditem->flags(): 0;
|
||||||
flags |= ImGuiTreeNodeFlags_Leaf;
|
|
||||||
|
ImGuiTreeNodeFlags node_flags =
|
||||||
|
ImGuiTreeNodeFlags_NoTreePushOnOpen |
|
||||||
|
ImGuiTreeNodeFlags_SpanFullWidth;
|
||||||
|
if (!(flags & DirItem::kTree)) {
|
||||||
|
node_flags |= ImGuiTreeNodeFlags_Leaf;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool opened = opened_.contains(name);
|
const bool opened = opened_.contains(name);
|
||||||
@@ -272,72 +132,58 @@ void Dir::UpdateTree() noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const auto top = ImGui::GetCursorPosY();
|
const auto top = ImGui::GetCursorPosY();
|
||||||
const bool open = ImGui::TreeNodeEx(item.second.get(), flags, "%s", name.c_str());
|
const bool open = ImGui::TreeNodeEx(
|
||||||
|
item.second.get(), node_flags, "%s", name.c_str());
|
||||||
if (!opened && open) {
|
if (!opened && open) {
|
||||||
opened_.insert(name);
|
opened_.insert(name);
|
||||||
} else if (opened && !open) {
|
} else if (opened && !open) {
|
||||||
opened_.erase(name);
|
opened_.erase(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ditem && (ditem->flags() & DirItem::kWidget)) {
|
|
||||||
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
|
|
||||||
widget_popup_.Open(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// tooltip
|
// tooltip
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ImGui::IsItemHovered()) {
|
||||||
ImGui::BeginTooltip();
|
ImGui::BeginTooltip();
|
||||||
ImGui::TextUnformatted(file.type().name().c_str());
|
nf7::gui::FileTooltip(file);
|
||||||
ImGui::SameLine();
|
|
||||||
ImGui::TextDisabled(file.abspath().Stringify().c_str());
|
|
||||||
if (ditem && (ditem->flags() & DirItem::kTooltip)) {
|
|
||||||
ImGui::Indent();
|
|
||||||
ditem->UpdateTooltip();
|
|
||||||
ImGui::Unindent();
|
|
||||||
}
|
|
||||||
ImGui::EndTooltip();
|
ImGui::EndTooltip();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// send nf7::File::Event::kReqFocus on double click
|
||||||
|
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
|
||||||
|
file.RequestFocus();
|
||||||
|
}
|
||||||
|
|
||||||
// context menu
|
// context menu
|
||||||
if (ImGui::BeginPopupContextItem()) {
|
if (ImGui::BeginPopupContextItem()) {
|
||||||
if (ditem && (ditem->flags() & DirItem::kWidget)) {
|
ImGui::BeginDisabled(flags & nf7::DirItem::kImportant);
|
||||||
if (ImGui::MenuItem("open widget")) {
|
|
||||||
widget_popup_.Open(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ImGui::MenuItem("copy path")) {
|
|
||||||
ImGui::SetClipboardText(file.abspath().Stringify().c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::Separator();
|
|
||||||
if (ImGui::MenuItem("remove")) {
|
if (ImGui::MenuItem("remove")) {
|
||||||
env().ExecMain(
|
env().ExecMain(
|
||||||
std::make_shared<nf7::GenericContext>(*this, "removing item"),
|
std::make_shared<nf7::GenericContext>(*this, "removing item"),
|
||||||
[this, name]() { Remove(name); });
|
[this, name]() { trash_.emplace_back(name, dir_.Remove(name)); });
|
||||||
}
|
}
|
||||||
if (ImGui::MenuItem("rename")) {
|
if (ImGui::BeginMenu("rename")) {
|
||||||
rename_popup_.Open(name);
|
ItemRenamer(name);
|
||||||
|
ImGui::EndMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui::MenuItem("renew")) {
|
if (ImGui::MenuItem("renew")) {
|
||||||
env().ExecMain(
|
env().ExecMain(
|
||||||
std::make_shared<nf7::GenericContext>(*this, "removing item"),
|
std::make_shared<nf7::GenericContext>(*this, "renewing item"),
|
||||||
[this, name]() { Add(name, Remove(name)); });
|
[this, name]() { dir_.Renew(name); });
|
||||||
}
|
}
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ImGui::IsItemHovered()) {
|
||||||
ImGui::SetTooltip("re-initialize the item by re-adding after removing");
|
ImGui::SetTooltip("re-initialize the item by re-adding after removing");
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Separator();
|
if (ImGui::MenuItem("clone")) {
|
||||||
if (ImGui::MenuItem("add new sibling")) {
|
env().ExecMain(
|
||||||
add_popup_.Open();
|
std::make_shared<nf7::GenericContext>(*this, "duplicating item"),
|
||||||
|
[this, name, &file]() { dir_.Add(dir_.GetUniqueName(name), file.Clone(env())); });
|
||||||
}
|
}
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
|
||||||
if (ditem && (ditem->flags() & DirItem::kMenu)) {
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
ditem->UpdateMenu();
|
nf7::gui::FileMenuItems(file);
|
||||||
}
|
|
||||||
ImGui::EndPopup();
|
ImGui::EndPopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,7 +199,7 @@ void Dir::UpdateTree() noexcept {
|
|||||||
// displayed contents
|
// displayed contents
|
||||||
if (open) {
|
if (open) {
|
||||||
ImGui::TreePush(&file);
|
ImGui::TreePush(&file);
|
||||||
if (ditem && (ditem->flags() & DirItem::kTree)) {
|
if (flags & DirItem::kTree) {
|
||||||
ditem->UpdateTree();
|
ditem->UpdateTree();
|
||||||
}
|
}
|
||||||
ImGui::TreePop();
|
ImGui::TreePop();
|
||||||
@@ -362,7 +208,7 @@ void Dir::UpdateTree() noexcept {
|
|||||||
|
|
||||||
// dnd target
|
// dnd target
|
||||||
if (nf7::gui::dnd::IsFirstAccept()) {
|
if (nf7::gui::dnd::IsFirstAccept()) {
|
||||||
if (ditem && (ditem->flags() & DirItem::kDragDropTarget)) {
|
if (flags & DirItem::kDragDropTarget) {
|
||||||
ImGui::SetCursorPosY(top);
|
ImGui::SetCursorPosY(top);
|
||||||
ImGui::Dummy({ImGui::GetContentRegionAvail().x, bottom-top});
|
ImGui::Dummy({ImGui::GetContentRegionAvail().x, bottom-top});
|
||||||
if (ImGui::BeginDragDropTarget()) {
|
if (ImGui::BeginDragDropTarget()) {
|
||||||
@@ -377,21 +223,50 @@ void Dir::UpdateTree() noexcept {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
void Dir::UpdateMenu() noexcept {
|
void Dir::UpdateMenu() noexcept {
|
||||||
if (ImGui::MenuItem("add new child")) {
|
if (ImGui::BeginMenu("add new child")) {
|
||||||
add_popup_.Open();
|
ItemAdder();
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
if (ImGui::BeginMenu("restore item", trash_.size() > 0)) {
|
||||||
|
for (auto itr = trash_.rbegin(); itr < trash_.rend();) {
|
||||||
|
const auto idx = std::distance(trash_.rbegin(), itr);
|
||||||
|
const auto& type = itr->second->type();
|
||||||
|
const auto id = itr->first + " (" + type.name() + ") ##" + std::to_string(idx);
|
||||||
|
|
||||||
|
const auto uniq = !dir_.Find(itr->first);
|
||||||
|
if (ImGui::MenuItem(id.c_str(), nullptr, false, uniq)) {
|
||||||
|
auto ctx = std::make_shared<nf7::GenericContext>(*this, "restoring an item");
|
||||||
|
auto p = std::make_shared<std::pair<std::string, std::unique_ptr<nf7::File>>>(std::move(*itr)); // this sucks
|
||||||
|
env().ExecMain(ctx, [this, p]() mutable {
|
||||||
|
dir_.Add(p->first, std::move(p->second));
|
||||||
|
});
|
||||||
|
|
||||||
|
trash_.erase(std::next(itr).base());
|
||||||
|
itr = trash_.rbegin()+idx;
|
||||||
|
} else {
|
||||||
|
++itr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
}
|
}
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
ImGui::MenuItem("TreeView", nullptr, &win_.shown());
|
win_.MenuItem();
|
||||||
}
|
}
|
||||||
void Dir::UpdateTooltip() noexcept {
|
void Dir::UpdateTooltip() noexcept {
|
||||||
ImGui::Text("children: %zu", items_.size());
|
ImGui::Text("children: %zu", dir_.items().size());
|
||||||
}
|
}
|
||||||
void Dir::UpdateDragDropTarget() noexcept
|
void Dir::UpdateDragDropTarget() noexcept
|
||||||
try {
|
try {
|
||||||
nf7::File::Path p;
|
nf7::File::Path p;
|
||||||
if (auto pay = gui::dnd::Peek<Path>(gui::dnd::kFilePath, p)) {
|
if (auto pay = gui::dnd::Peek<Path>(gui::dnd::kFilePath, p)) {
|
||||||
auto& target = ResolveOrThrow(p);
|
auto& target = ResolveOrThrow(p);
|
||||||
if (target.parent() == this) {
|
if (target.parent() == nullptr || target.parent() == this) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& ditem = target.interfaceOrThrow<nf7::DirItem>();
|
||||||
|
if (ditem.flags() & nf7::DirItem::kImportant) {
|
||||||
|
ImGui::SetTooltip("cannot move an important file");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,94 +276,169 @@ try {
|
|||||||
parent = parent->parent();
|
parent = parent->parent();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& dir = target.parent()->interfaceOrThrow<nf7::Dir>();
|
const auto pid = target.parent()->id();
|
||||||
|
auto& src = target.parent()->interfaceOrThrow<nf7::Dir>();
|
||||||
|
|
||||||
nf7::gui::dnd::DrawRect();
|
nf7::gui::dnd::DrawRect();
|
||||||
if (pay->IsDelivery()) {
|
if (pay->IsDelivery()) {
|
||||||
env().ExecMain(
|
env().ExecMain(
|
||||||
std::make_shared<nf7::GenericContext>(*this, "moving an item"),
|
std::make_shared<nf7::GenericContext>(*this, "moving an item"),
|
||||||
[this, &dir, name = target.name()]() { Add(GetUniqueName(name), dir.Remove(name)); });
|
[this, pid, &src, name = target.name()]() {
|
||||||
|
if (env().GetFile(pid)) {
|
||||||
|
if (auto f = src.Remove(name)) {
|
||||||
|
dir_.Add(dir_.GetUniqueName(name), std::move(f));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (nf7::File::NotImplementedException&) {
|
||||||
|
ImGui::SetTooltip("the file is not an item of nf7::Dir");
|
||||||
} catch (nf7::Exception&) {
|
} catch (nf7::Exception&) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Dir::WidgetPopup::Update() noexcept {
|
|
||||||
if (nf7::gui::Popup::Begin()) {
|
void Dir::TreeView() noexcept {
|
||||||
if (auto item = target_->interface<nf7::DirItem>()) {
|
if (ImGui::BeginPopupContextWindow()) {
|
||||||
ImGui::PushID(item);
|
UpdateMenu();
|
||||||
item->UpdateWidget();
|
|
||||||
ImGui::PopID();
|
|
||||||
}
|
|
||||||
ImGui::EndPopup();
|
ImGui::EndPopup();
|
||||||
}
|
}
|
||||||
|
UpdateTree();
|
||||||
|
|
||||||
|
if (nf7::gui::dnd::IsFirstAccept()) {
|
||||||
|
ImGui::SetCursorPos({0, 0});
|
||||||
|
ImGui::Dummy(ImGui::GetContentRegionAvail());
|
||||||
|
if (ImGui::BeginDragDropTarget()) {
|
||||||
|
UpdateDragDropTarget();
|
||||||
|
ImGui::EndDragDropTarget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
void Dir::AddPopup::Update() noexcept {
|
|
||||||
if (nf7::gui::Popup::Begin()) {
|
|
||||||
ImGui::TextUnformatted("System/Dir: adding new file...");
|
ImGui::TextUnformatted("System/Dir: adding new file...");
|
||||||
if (factory_.Update()) {
|
|
||||||
ImGui::CloseCurrentPopup();
|
|
||||||
|
|
||||||
auto& env = owner_->env();
|
const auto em = ImGui::GetFontSize();
|
||||||
auto ctx = std::make_shared<nf7::GenericContext>(*owner_, "adding new item");
|
|
||||||
auto task = [this, &env]() { owner_->Add(factory_.name(), factory_.Create(env)); };
|
|
||||||
env.ExecMain(ctx, std::move(task));
|
|
||||||
}
|
|
||||||
ImGui::EndPopup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void Dir::RenamePopup::Update() noexcept {
|
|
||||||
if (nf7::gui::Popup::Begin()) {
|
|
||||||
ImGui::TextUnformatted("System/Dir: renaming an exsting item...");
|
|
||||||
ImGui::InputText("before", &before_);
|
|
||||||
|
|
||||||
bool submit = false;
|
bool exec = false;
|
||||||
if (ImGui::IsWindowAppearing()) ImGui::SetKeyboardFocusHere();
|
if (ImGui::BeginListBox("type", {16*em, 8*em})) {
|
||||||
if (ImGui::InputText("after", &after_, ImGuiInputTextFlags_EnterReturnsTrue)) {
|
for (auto& p : nf7::File::registry()) {
|
||||||
submit = true;
|
const auto& t = *p.second;
|
||||||
|
if (type_filtered && !t.flags().contains("nf7::DirItem")) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool err = false;
|
constexpr auto kFlags =
|
||||||
if (!owner_->Find(before_)) {
|
ImGuiSelectableFlags_SpanAllColumns |
|
||||||
ImGui::Bullet(); ImGui::TextUnformatted("before is invalid: missing target");
|
ImGuiSelectableFlags_AllowItemOverlap;
|
||||||
err = true;
|
if (ImGui::Selectable(t.name().c_str(), type == &t, kFlags)) {
|
||||||
}
|
type = &t;
|
||||||
if (owner_->Find(after_)) {
|
|
||||||
ImGui::Bullet(); ImGui::TextUnformatted("after is invalid: duplicated name");
|
|
||||||
err = true;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
Path::ValidateTerm(after_);
|
|
||||||
} catch (Exception& e) {
|
|
||||||
ImGui::Bullet(); ImGui::Text("after is invalid: %s", e.msg().c_str());
|
|
||||||
err = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!err) {
|
|
||||||
if (ImGui::Button("ok")) {
|
|
||||||
submit = true;
|
|
||||||
}
|
}
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ImGui::IsItemHovered()) {
|
||||||
ImGui::SetTooltip(
|
ImGui::BeginTooltip();
|
||||||
"rename '%s' to '%s' on '%s'",
|
t.UpdateTooltip();
|
||||||
before_.c_str(), after_.c_str(),
|
ImGui::EndTooltip();
|
||||||
owner_->abspath().Stringify().c_str());
|
|
||||||
|
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
|
||||||
|
exec = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (submit) {
|
if (type_filtered) {
|
||||||
|
ImGui::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();
|
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");
|
void Dir::ItemRenamer(const std::string& name) noexcept {
|
||||||
auto task = [this, before = std::move(before_), after = std::move(after_)]() {
|
static std::string editing_name;
|
||||||
auto f = owner_->Remove(before);
|
static std::string err;
|
||||||
if (!f) throw nf7::Exception {"missing target"};
|
if (ImGui::IsWindowAppearing()) {
|
||||||
owner_->Add(after, std::move(f));
|
editing_name = name;
|
||||||
};
|
err = "";
|
||||||
owner_->env().ExecMain(ctx, std::move(task));
|
|
||||||
}
|
}
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
@@ -8,113 +9,170 @@
|
|||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
||||||
|
#include <yaml-cpp/yaml.h>
|
||||||
|
|
||||||
|
#include <yas/serialize.hpp>
|
||||||
|
#include <yas/types/std/vector.hpp>
|
||||||
|
|
||||||
#include "nf7.hh"
|
#include "nf7.hh"
|
||||||
|
|
||||||
#include "common/dir_item.hh"
|
#include "common/dir_item.hh"
|
||||||
#include "common/file_base.hh"
|
#include "common/file_base.hh"
|
||||||
#include "common/file_holder.hh"
|
#include "common/generic_config.hh"
|
||||||
#include "common/gui_file.hh"
|
|
||||||
#include "common/generic_context.hh"
|
#include "common/generic_context.hh"
|
||||||
#include "common/generic_memento.hh"
|
#include "common/generic_memento.hh"
|
||||||
#include "common/generic_type_info.hh"
|
#include "common/generic_type_info.hh"
|
||||||
#include "common/life.hh"
|
#include "common/generic_watcher.hh"
|
||||||
|
#include "common/gui.hh"
|
||||||
#include "common/logger.hh"
|
#include "common/logger.hh"
|
||||||
#include "common/logger_ref.hh"
|
#include "common/logger_ref.hh"
|
||||||
#include "common/node.hh"
|
#include "common/node.hh"
|
||||||
#include "common/ptr_selector.hh"
|
#include "common/ptr_selector.hh"
|
||||||
#include "common/value.hh"
|
#include "common/value.hh"
|
||||||
|
#include "common/yaml_nf7.hh"
|
||||||
|
#include "common/yas_nf7.hh"
|
||||||
|
|
||||||
|
|
||||||
namespace nf7 {
|
namespace nf7 {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
class Event final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
|
class Event final : public nf7::FileBase,
|
||||||
|
public nf7::GenericConfig, public nf7::DirItem {
|
||||||
public:
|
public:
|
||||||
static inline const nf7::GenericTypeInfo<Event> kType = {
|
static inline const nf7::GenericTypeInfo<Event> kType = {
|
||||||
"System/Event", {"nf7::DirItem"}};
|
"System/Event", {"nf7::DirItem"}};
|
||||||
static void UpdateTypeTooltip() noexcept {
|
|
||||||
ImGui::TextUnformatted("Records log output from other files.");
|
|
||||||
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Node");
|
|
||||||
}
|
|
||||||
|
|
||||||
class Lambda;
|
class Lambda;
|
||||||
|
|
||||||
struct Data final {
|
struct Data {
|
||||||
nf7::FileHolder::Tag handler;
|
nf7::File::Path handler;
|
||||||
|
|
||||||
|
// feature switch
|
||||||
|
bool init = false;
|
||||||
|
bool key = false;
|
||||||
|
bool mouse = false;
|
||||||
|
|
||||||
|
std::vector<nf7::File::Path> watch;
|
||||||
|
|
||||||
|
Data() noexcept { }
|
||||||
|
void serialize(auto& ar) {
|
||||||
|
ar(handler, init, key, mouse, watch);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Stringify() const noexcept {
|
||||||
|
YAML::Emitter st;
|
||||||
|
st << YAML::BeginMap;
|
||||||
|
st << YAML::Key << "handler";
|
||||||
|
st << YAML::Value << handler;
|
||||||
|
st << YAML::Key << "event";
|
||||||
|
st << YAML::BeginMap;
|
||||||
|
st << YAML::Key << "init";
|
||||||
|
st << YAML::Value << init;
|
||||||
|
st << YAML::Key << "key";
|
||||||
|
st << YAML::Value << key;
|
||||||
|
st << YAML::Key << "mouse";
|
||||||
|
st << YAML::Value << mouse;
|
||||||
|
st << YAML::Key << "watch";
|
||||||
|
st << YAML::Value << watch;
|
||||||
|
st << YAML::EndMap;
|
||||||
|
st << YAML::EndMap;
|
||||||
|
return {st.c_str(), st.size()};
|
||||||
|
}
|
||||||
|
void Parse(const std::string& str) {
|
||||||
|
const auto yaml = YAML::Load(str);
|
||||||
|
|
||||||
|
Data d;
|
||||||
|
d.handler = yaml["handler"].as<nf7::File::Path>();
|
||||||
|
|
||||||
|
const auto& ev = yaml["event"];
|
||||||
|
d.init = ev["init"].as<bool>();
|
||||||
|
d.key = ev["key"].as<bool>();
|
||||||
|
d.mouse = ev["mouse"].as<bool>();
|
||||||
|
d.watch = ev["watch"].as<std::vector<nf7::File::Path>>();
|
||||||
|
|
||||||
|
*this = std::move(d);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Event(nf7::Env& env, Data&& data = {}) noexcept :
|
Event(nf7::Env& env, Data&& d = {}) noexcept :
|
||||||
nf7::FileBase(kType, env, {&logger_, &handler_, &handler_editor_}),
|
nf7::FileBase(kType, env),
|
||||||
nf7::DirItem(nf7::DirItem::kMenu | nf7::DirItem::kWidget),
|
nf7::GenericConfig(mem_),
|
||||||
nf7::Node(nf7::Node::kMenu_DirItem),
|
nf7::DirItem(nf7::DirItem::kMenu |
|
||||||
life_(*this), logger_(*this),
|
nf7::DirItem::kTooltip),
|
||||||
handler_(*this, "handler", mem_),
|
log_(*this),
|
||||||
handler_editor_(handler_,
|
|
||||||
[](auto& t) { return t.flags().contains("nf7::Node"); }),
|
|
||||||
la_root_(std::make_shared<nf7::Node::Lambda>(*this)),
|
la_root_(std::make_shared<nf7::Node::Lambda>(*this)),
|
||||||
mem_(std::move(data)) {
|
mem_(*this, std::move(d)) {
|
||||||
handler_.onEmplace = [this]() { la_ = nullptr; };
|
mem_.onCommit = [this]() { SetUpWatcher(); };
|
||||||
}
|
}
|
||||||
|
|
||||||
Event(nf7::Deserializer& ar) : Event(ar.env()) {
|
Event(nf7::Deserializer& ar) : Event(ar.env()) {
|
||||||
ar(handler_);
|
ar(mem_.data());
|
||||||
}
|
}
|
||||||
void Serialize(nf7::Serializer& ar) const noexcept override {
|
void Serialize(nf7::Serializer& ar) const noexcept override {
|
||||||
ar(handler_);
|
ar(mem_.data());
|
||||||
}
|
}
|
||||||
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
|
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
|
||||||
return std::make_unique<Event>(env, Data {data()});
|
return std::make_unique<Event>(env, Data {mem_.data()});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
|
void PostHandle(const nf7::File::Event& e) noexcept override {
|
||||||
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
|
switch (e.type) {
|
||||||
std::span<const std::string> GetInputs() const noexcept override {
|
case nf7::File::Event::kAdd:
|
||||||
static const std::vector<std::string> kInputs = {"value"};
|
if (mem_->init) {
|
||||||
return kInputs;
|
env().ExecMain(
|
||||||
|
std::make_shared<nf7::GenericContext>(*this, "trigger init event"),
|
||||||
|
[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 UpdateMenu() noexcept override;
|
||||||
void UpdateWidget() noexcept override;
|
void UpdateTooltip() noexcept override;
|
||||||
|
|
||||||
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
|
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
|
||||||
return nf7::InterfaceSelector<nf7::DirItem, nf7::Node>(t).Select(this);
|
return nf7::InterfaceSelector<
|
||||||
|
nf7::Config, nf7::DirItem, nf7::Node>(t).Select(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
nf7::Life<Event> life_;
|
nf7::LoggerRef log_;
|
||||||
|
|
||||||
nf7::LoggerRef logger_;
|
|
||||||
|
|
||||||
nf7::FileHolder handler_;
|
|
||||||
nf7::gui::FileHolderEditor handler_editor_;
|
|
||||||
|
|
||||||
std::shared_ptr<nf7::Node::Lambda> la_root_;
|
std::shared_ptr<nf7::Node::Lambda> la_root_;
|
||||||
std::shared_ptr<nf7::Node::Lambda> la_;
|
std::shared_ptr<nf7::Node::Lambda> la_;
|
||||||
|
|
||||||
nf7::GenericMemento<Data> mem_;
|
nf7::GenericMemento<Data> mem_;
|
||||||
Data& data() noexcept { return mem_.data(); }
|
|
||||||
const Data& data() const noexcept { return mem_.data(); }
|
class Watcher final : public nf7::Env::Watcher {
|
||||||
|
public:
|
||||||
|
Watcher(Event& f) noexcept : nf7::Env::Watcher(f.env()), f_(f) {
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
Event& f_;
|
||||||
|
|
||||||
|
void Handle(const nf7::File::Event& e) noexcept override { f_.TriggerWatch(e); }
|
||||||
|
};
|
||||||
|
std::optional<Watcher> watch_;
|
||||||
|
|
||||||
|
|
||||||
std::span<const std::string> GetHandlerInputs() noexcept
|
nf7::Node& GetHandler() const {
|
||||||
try {
|
return ResolveOrThrow(mem_->handler).interfaceOrThrow<nf7::Node>();
|
||||||
return handler_.GetFileOrThrow().interfaceOrThrow<nf7::Node>().GetInputs();
|
|
||||||
} catch (nf7::Exception&) {
|
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
std::shared_ptr<nf7::Node::Lambda> CreateLambdaIf() noexcept {
|
std::shared_ptr<nf7::Node::Lambda> CreateLambdaIf() noexcept {
|
||||||
try {
|
try {
|
||||||
if (!la_) {
|
if (!la_) {
|
||||||
auto& n = handler_.GetFileOrThrow().interfaceOrThrow<nf7::Node>();
|
la_ = GetHandler().CreateLambda(la_root_);
|
||||||
la_ = n.CreateLambda(la_root_);
|
|
||||||
}
|
}
|
||||||
return la_;
|
return la_;
|
||||||
} catch (nf7::Exception& e) {
|
} catch (nf7::Exception& e) {
|
||||||
logger_.Warn("failed to create handler's lambda: "+e.msg());
|
log_.Warn("failed to create handler's lambda: "+e.msg());
|
||||||
la_ = nullptr;
|
la_ = nullptr;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -128,44 +186,45 @@ class Event final : public nf7::FileBase, public nf7::DirItem, public nf7::Node
|
|||||||
}}, la_root_);
|
}}, la_root_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void TriggerCustomEvent(const nf7::Value& v) noexcept {
|
void TriggerWatch(const nf7::File::Event& e) noexcept {
|
||||||
if (auto la = CreateLambdaIf()) {
|
if (auto la = CreateLambdaIf()) {
|
||||||
la->Handle("custom", v, la_root_);
|
std::string type;
|
||||||
|
switch (e.type) {
|
||||||
|
case nf7::File::Event::kAdd:
|
||||||
|
type = "add";
|
||||||
|
break;
|
||||||
|
case nf7::File::Event::kUpdate:
|
||||||
|
type = "update";
|
||||||
|
break;
|
||||||
|
case nf7::File::Event::kRemove:
|
||||||
|
type = "remove";
|
||||||
|
break;
|
||||||
|
case nf7::File::Event::kReqFocus:
|
||||||
|
type = "focus";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
la->Handle("watch", nf7::Value {std::vector<nf7::Value::TuplePair> {
|
||||||
|
{"file", static_cast<nf7::Value::Integer>(e.id)},
|
||||||
|
{"type", std::move(type)},
|
||||||
|
}}, la_root_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
|
void SetUpWatcher() noexcept {
|
||||||
class Event::Lambda final : public nf7::Node::Lambda {
|
watch_.emplace(*this);
|
||||||
public:
|
for (const auto& p : mem_->watch)
|
||||||
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
|
|
||||||
try {
|
try {
|
||||||
f_.EnforceAlive();
|
watch_->Watch(ResolveOrThrow(p).id());
|
||||||
f_->TriggerCustomEvent(v);
|
} catch (nf7::File::NotFoundException&) {
|
||||||
} catch (nf7::Exception&) {
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
nf7::Life<Event>::Ref f_;
|
|
||||||
};
|
};
|
||||||
std::shared_ptr<nf7::Node::Lambda> Event::CreateLambda(
|
|
||||||
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
|
|
||||||
return std::make_shared<Event::Lambda>(*this, parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Event::Update() noexcept {
|
void Event::PostUpdate() noexcept {
|
||||||
nf7::FileBase::Update();
|
|
||||||
|
|
||||||
const auto& io = ImGui::GetIO();
|
const auto& io = ImGui::GetIO();
|
||||||
const auto in = GetHandlerInputs();
|
|
||||||
|
|
||||||
if (in.end() != std::find(in.begin(), in.end(), "key")) {
|
if (mem_->key) {
|
||||||
for (size_t i = 0; i < ImGuiKey_KeysData_SIZE; ++i) {
|
for (size_t i = 0; i < ImGuiKey_KeysData_SIZE; ++i) {
|
||||||
const auto& key = io.KeysData[i];
|
const auto& key = io.KeysData[i];
|
||||||
const char* event = nullptr;
|
const char* event = nullptr;
|
||||||
@@ -180,18 +239,32 @@ void Event::Update() noexcept {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (mem_->mouse) {
|
||||||
|
// TODO
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Event::UpdateMenu() noexcept {
|
void Event::UpdateMenu() noexcept {
|
||||||
if (ImGui::MenuItem("drop handler's lambda")) {
|
if (ImGui::MenuItem("abort and drop lambda", nullptr, false, !!la_)) {
|
||||||
|
la_->Abort();
|
||||||
la_ = nullptr;
|
la_ = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void Event::UpdateWidget() noexcept {
|
void Event::UpdateTooltip() noexcept {
|
||||||
ImGui::TextUnformatted("System/Event");
|
ImGui::Text("handler: %s", mem_->handler.Stringify().c_str());
|
||||||
|
ImGui::Text("events :");
|
||||||
handler_editor_.ButtonWithLabel("handler");
|
if (mem_->init) {
|
||||||
handler_editor_.ItemWidget("handler");
|
ImGui::Bullet(); ImGui::TextUnformatted("init");
|
||||||
handler_editor_.Update();
|
}
|
||||||
|
if (mem_->key) {
|
||||||
|
ImGui::Bullet(); ImGui::TextUnformatted("key");
|
||||||
|
}
|
||||||
|
if (mem_->mouse) {
|
||||||
|
ImGui::Bullet(); ImGui::TextUnformatted("mouse");
|
||||||
|
}
|
||||||
|
if (mem_->watch.size() > 0) {
|
||||||
|
ImGui::Bullet(); ImGui::TextUnformatted("watch");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|||||||
@@ -1,21 +1,36 @@
|
|||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdlib>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <typeinfo>
|
#include <typeinfo>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
#include <imgui_internal.h>
|
||||||
|
|
||||||
|
#include <yaml-cpp/yaml.h>
|
||||||
|
|
||||||
#include <yas/serialize.hpp>
|
#include <yas/serialize.hpp>
|
||||||
#include <yas/types/std/string.hpp>
|
#include <yas/types/std/string.hpp>
|
||||||
#include <yas/types/std/string_view.hpp>
|
#include <yas/types/std/string_view.hpp>
|
||||||
|
#include <yas/types/std/vector.hpp>
|
||||||
|
#include <yas/types/utility/usertype.hpp>
|
||||||
|
|
||||||
#include "nf7.hh"
|
#include "nf7.hh"
|
||||||
|
|
||||||
#include "common/dir_item.hh"
|
#include "common/dir_item.hh"
|
||||||
|
#include "common/file_base.hh"
|
||||||
|
#include "common/generic_config.hh"
|
||||||
|
#include "common/generic_memento.hh"
|
||||||
#include "common/generic_type_info.hh"
|
#include "common/generic_type_info.hh"
|
||||||
|
#include "common/gui.hh"
|
||||||
#include "common/gui_window.hh"
|
#include "common/gui_window.hh"
|
||||||
#include "common/ptr_selector.hh"
|
#include "common/ptr_selector.hh"
|
||||||
|
#include "common/util_algorithm.hh"
|
||||||
|
|
||||||
|
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
@@ -24,16 +39,56 @@ using namespace std::literals;
|
|||||||
namespace nf7 {
|
namespace nf7 {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
class ImGui_ final : public nf7::File, public nf7::DirItem {
|
class ImGui_ final : public nf7::FileBase,
|
||||||
|
public nf7::GenericConfig, public nf7::DirItem {
|
||||||
public:
|
public:
|
||||||
static inline const nf7::GenericTypeInfo<ImGui_> kType = {"System/ImGui", {}};
|
static inline const nf7::GenericTypeInfo<ImGui_> kType = {"System/ImGui", {}};
|
||||||
|
|
||||||
ImGui_(nf7::Env& env) noexcept :
|
struct Data {
|
||||||
nf7::File(kType, env), nf7::DirItem(nf7::DirItem::kNone) {
|
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()) {
|
ImGui_(nf7::Deserializer& ar) : ImGui_(ar.env()) {
|
||||||
std::string config;
|
std::string config;
|
||||||
ar(config);
|
ar(config, mem_.data());
|
||||||
|
|
||||||
if (config.size() > 0) {
|
if (config.size() > 0) {
|
||||||
ImGui::LoadIniSettingsFromMemory(config.data(), config.size());
|
ImGui::LoadIniSettingsFromMemory(config.data(), config.size());
|
||||||
@@ -42,40 +97,170 @@ class ImGui_ final : public nf7::File, public nf7::DirItem {
|
|||||||
void Serialize(nf7::Serializer& ar) const noexcept override {
|
void Serialize(nf7::Serializer& ar) const noexcept override {
|
||||||
size_t n;
|
size_t n;
|
||||||
const char* config = ImGui::SaveIniSettingsToMemory(&n);
|
const char* config = ImGui::SaveIniSettingsToMemory(&n);
|
||||||
ar(std::string_view(config, n));
|
ar(std::string_view(config, n), mem_.data());
|
||||||
}
|
}
|
||||||
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
|
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
|
||||||
return std::make_unique<ImGui_>(env);
|
return std::make_unique<ImGui_>(env);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Update() noexcept override;
|
void PostUpdate() noexcept override;
|
||||||
|
void UpdateMenu() noexcept override;
|
||||||
|
|
||||||
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
|
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
|
||||||
return nf7::InterfaceSelector<nf7::DirItem>(t).Select(this);
|
return nf7::InterfaceSelector<
|
||||||
|
nf7::Config, nf7::DirItem>(t).Select(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
nf7::GenericMemento<Data> mem_;
|
||||||
|
|
||||||
|
|
||||||
|
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});
|
void ImGui_::PostUpdate() noexcept {
|
||||||
ImGui::SetNextWindowBgAlpha(0.f);
|
DrawLogo();
|
||||||
if (ImGui::Begin(id.c_str(), nullptr, kFlags)) {
|
Dockspace();
|
||||||
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_::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();
|
ImGui::End();
|
||||||
|
|
||||||
|
if (shown) {
|
||||||
|
++itr;
|
||||||
|
} else {
|
||||||
|
itr = ds.erase(itr);
|
||||||
|
mod = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
ImGui::PopStyleVar(1);
|
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_
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,17 +10,23 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <imgui.h>
|
||||||
|
|
||||||
|
#include <yaml-cpp/yaml.h>
|
||||||
|
|
||||||
#include "nf7.hh"
|
#include "nf7.hh"
|
||||||
|
|
||||||
#include "common/dir_item.hh"
|
#include "common/dir_item.hh"
|
||||||
#include "common/file_base.hh"
|
#include "common/file_base.hh"
|
||||||
|
#include "common/generic_config.hh"
|
||||||
#include "common/generic_context.hh"
|
#include "common/generic_context.hh"
|
||||||
|
#include "common/generic_memento.hh"
|
||||||
#include "common/generic_type_info.hh"
|
#include "common/generic_type_info.hh"
|
||||||
|
#include "common/gui.hh"
|
||||||
#include "common/gui_window.hh"
|
#include "common/gui_window.hh"
|
||||||
#include "common/life.hh"
|
#include "common/life.hh"
|
||||||
#include "common/logger.hh"
|
#include "common/logger.hh"
|
||||||
#include "common/logger_ref.hh"
|
#include "common/logger_ref.hh"
|
||||||
#include "common/node.hh"
|
|
||||||
#include "common/ptr_selector.hh"
|
#include "common/ptr_selector.hh"
|
||||||
#include "common/yas_std_atomic.hh"
|
#include "common/yas_std_atomic.hh"
|
||||||
|
|
||||||
@@ -30,21 +36,12 @@ using namespace std::literals;
|
|||||||
namespace nf7 {
|
namespace nf7 {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
class Logger final : public nf7::File,
|
class Logger final : public nf7::FileBase,
|
||||||
public nf7::DirItem {
|
public nf7::GenericConfig, public nf7::DirItem {
|
||||||
public:
|
public:
|
||||||
static inline const nf7::GenericTypeInfo<Logger> kType = {
|
static inline const nf7::GenericTypeInfo<Logger> kType = {
|
||||||
"System/Logger", {"nf7::DirItem"}};
|
"System/Logger", {"nf7::DirItem"}, "records log output from other files",
|
||||||
static void UpdateTypeTooltip() noexcept {
|
};
|
||||||
ImGui::TextUnformatted("Records log output from other files.");
|
|
||||||
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Logger");
|
|
||||||
ImGui::Bullet(); ImGui::TextUnformatted(
|
|
||||||
"logged are children and grandchildren of a dir that has this with name '_logger'");
|
|
||||||
ImGui::Bullet(); ImGui::TextUnformatted(
|
|
||||||
"recorded logs won't be permanentized");
|
|
||||||
}
|
|
||||||
|
|
||||||
class Node;
|
|
||||||
|
|
||||||
struct Row final {
|
struct Row final {
|
||||||
public:
|
public:
|
||||||
@@ -66,63 +63,109 @@ class Logger final : public nf7::File,
|
|||||||
return st.str();
|
return st.str();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
struct Param final {
|
|
||||||
public:
|
|
||||||
Param(uint32_t mr, bool p, bool f) : max_rows(mr), propagate(p), freeze(f) {
|
|
||||||
}
|
|
||||||
std::atomic<uint32_t> max_rows;
|
|
||||||
std::atomic<bool> propagate;
|
|
||||||
std::atomic<bool> freeze;
|
|
||||||
};
|
|
||||||
class ItemStore;
|
|
||||||
|
|
||||||
Logger(nf7::Env& env, uint32_t max_rows = 1024, bool propagate = false, bool freeze = false) noexcept :
|
struct Data {
|
||||||
File(kType, env), DirItem(DirItem::kMenu),
|
uint32_t max_rows = 1024;
|
||||||
param_(std::make_shared<Param>(max_rows, propagate, freeze)),
|
bool propagate = false;
|
||||||
win_(*this, "LogView") {
|
bool freeze = false;
|
||||||
win_.shown() = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger(nf7::Deserializer& ar) : Logger(ar.env()) {
|
Data() noexcept { }
|
||||||
ar(win_, param_->max_rows, param_->propagate, param_->freeze);
|
void serialize(auto& ar) {
|
||||||
|
ar(max_rows, propagate, freeze);
|
||||||
if (param_->max_rows == 0) {
|
if (max_rows == 0) {
|
||||||
throw DeserializeException("max_rows must be 1 or more");
|
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 UpdateMenu() noexcept override;
|
||||||
void UpdateRowMenu(const Row&) noexcept;
|
void UpdateRowMenu(const Row&) noexcept;
|
||||||
|
|
||||||
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
|
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
|
||||||
return InterfaceSelector<nf7::DirItem, nf7::Logger>(t).
|
return nf7::InterfaceSelector<nf7::Config, nf7::DirItem, nf7::Logger>(t).
|
||||||
Select(this, store_.get());
|
Select(this, store_.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<Param> param_;
|
class ItemStore;
|
||||||
std::shared_ptr<ItemStore> store_;
|
std::shared_ptr<ItemStore> store_;
|
||||||
std::deque<Row> rows_;
|
std::deque<Row> rows_;
|
||||||
|
|
||||||
const char* popup_ = nullptr;
|
nf7::GenericMemento<Data> mem_;
|
||||||
|
|
||||||
nf7::gui::Window win_;
|
nf7::gui::Window win_;
|
||||||
|
|
||||||
|
|
||||||
|
// log record management
|
||||||
void DropExceededRows() noexcept {
|
void DropExceededRows() noexcept {
|
||||||
if (rows_.size() <= param_->max_rows) return;
|
if (rows_.size() <= mem_->max_rows) return;
|
||||||
rows_.erase(rows_.begin(), rows_.end()-param_->max_rows);
|
rows_.erase(rows_.begin(), rows_.end()-mem_->max_rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stringify
|
||||||
std::string GetPathString(File::Id id) const noexcept
|
std::string GetPathString(File::Id id) const noexcept
|
||||||
try {
|
try {
|
||||||
return env().GetFileOrThrow(id).abspath().Stringify();
|
return env().GetFileOrThrow(id).abspath().Stringify();
|
||||||
@@ -147,14 +190,17 @@ class Logger final : public nf7::File,
|
|||||||
static std::string GetLocationString(const std::source_location loc) noexcept {
|
static std::string GetLocationString(const std::source_location loc) noexcept {
|
||||||
return loc.file_name()+":"s+std::to_string(loc.line());
|
return loc.file_name()+":"s+std::to_string(loc.line());
|
||||||
}
|
}
|
||||||
};
|
|
||||||
class Logger::ItemStore final : public nf7::Context,
|
// gui
|
||||||
|
void LogView() noexcept;
|
||||||
|
|
||||||
|
|
||||||
|
class ItemStore final : public nf7::Context,
|
||||||
public nf7::Logger,
|
public nf7::Logger,
|
||||||
public std::enable_shared_from_this<ItemStore> {
|
public std::enable_shared_from_this<ItemStore> {
|
||||||
public:
|
public:
|
||||||
ItemStore() = delete;
|
ItemStore() = delete;
|
||||||
ItemStore(File& owner, const std::shared_ptr<Param>& param) noexcept :
|
ItemStore(File& f) noexcept : nf7::Context(f) {
|
||||||
Context(owner.env(), owner.id()), param_(param) {
|
|
||||||
}
|
}
|
||||||
ItemStore(const ItemStore&) = delete;
|
ItemStore(const ItemStore&) = delete;
|
||||||
ItemStore(ItemStore&&) = delete;
|
ItemStore(ItemStore&&) = delete;
|
||||||
@@ -162,28 +208,28 @@ class Logger::ItemStore final : public nf7::Context,
|
|||||||
ItemStore& operator=(ItemStore&&) = delete;
|
ItemStore& operator=(ItemStore&&) = delete;
|
||||||
|
|
||||||
void Write(nf7::Logger::Item&& item) noexcept override {
|
void Write(nf7::Logger::Item&& item) noexcept override {
|
||||||
if (param_->freeze) return;
|
if (param_.freeze) return;
|
||||||
if (param_->propagate) {
|
if (param_.propagate) {
|
||||||
// TODO propagation
|
// TODO propagation
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_lock<std::mutex> k(mtx_);
|
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));
|
items_.push_back(std::move(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MoveItemsTo(auto& owner) noexcept {
|
bool MoveItemsTo(auto& owner) noexcept {
|
||||||
std::unique_lock<std::mutex> k(mtx_);
|
std::unique_lock<std::mutex> k(mtx_);
|
||||||
if (items_.empty()) return false;
|
if (items_.empty()) return false;
|
||||||
auto& rows = owner.rows_;
|
auto& rows = owner.rows_;
|
||||||
|
|
||||||
auto itr = items_.begin();
|
auto itr = items_.begin();
|
||||||
if (rows.size()+items_.size() > param_->max_rows) {
|
if (rows.size()+items_.size() > param_.max_rows) {
|
||||||
// max_rows may be changed
|
if (items_.size() > param_.max_rows) {
|
||||||
if (items_.size() > param_->max_rows) {
|
itr += static_cast<intmax_t>(param_.max_rows - items_.size());
|
||||||
itr += static_cast<intmax_t>(param_->max_rows - items_.size());
|
|
||||||
}
|
}
|
||||||
const auto keep =
|
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);
|
rows.erase(rows.begin(), rows.end()-keep);
|
||||||
}
|
}
|
||||||
for (; itr < items_.end(); ++itr) {
|
for (; itr < items_.end(); ++itr) {
|
||||||
@@ -205,144 +251,42 @@ class Logger::ItemStore final : public nf7::Context,
|
|||||||
std::string GetDescription() const noexcept override {
|
std::string GetDescription() const noexcept override {
|
||||||
return "System/Logger shared instance";
|
return "System/Logger shared instance";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<nf7::Logger> self(nf7::Logger*) noexcept override {
|
std::shared_ptr<nf7::Logger> self(nf7::Logger*) noexcept override {
|
||||||
return shared_from_this();
|
return shared_from_this();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void param(const Data& d) noexcept {
|
||||||
|
std::unique_lock<std::mutex> k(mtx_);
|
||||||
|
param_ = d;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::mutex mtx_;
|
std::mutex mtx_;
|
||||||
std::deque<nf7::Logger::Item> items_;
|
std::deque<nf7::Logger::Item> items_;
|
||||||
std::shared_ptr<Param> param_;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
Data 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_;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
void Logger::Handle(const Event& ev) noexcept {
|
void Logger::UpdateMenu() noexcept {
|
||||||
switch (ev.type) {
|
win_.MenuItem();
|
||||||
case Event::kAdd:
|
|
||||||
store_ = std::make_shared<ItemStore>(*this, param_);
|
|
||||||
return;
|
|
||||||
case Event::kRemove:
|
|
||||||
store_ = nullptr;
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
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();
|
void Logger::LogView() noexcept {
|
||||||
|
|
||||||
// config popup
|
|
||||||
if (ImGui::BeginPopup("ConfigPopup")) {
|
|
||||||
ImGui::TextUnformatted("System/Logger Config");
|
|
||||||
ImGui::Spacing();
|
|
||||||
|
|
||||||
static const uint32_t kMinRows = 1, kMaxRows = 1024*1024;
|
|
||||||
uint32_t max_rows = param_->max_rows;
|
|
||||||
if (ImGui::DragScalar("max rows", ImGuiDataType_U32, &max_rows, 1, &kMinRows, &kMaxRows)) {
|
|
||||||
param_->max_rows = max_rows;
|
|
||||||
DropExceededRows();
|
|
||||||
}
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("the oldest row is dropped when exceed");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool propagate = param_->propagate;
|
|
||||||
if (ImGui::Checkbox("propagate", &propagate)) {
|
|
||||||
param_->propagate = propagate;
|
|
||||||
}
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("after handling, passes the msg to outer logger if exists");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool freeze = param_->freeze;
|
|
||||||
if (ImGui::Checkbox("freeze", &freeze)) {
|
|
||||||
param_->freeze = freeze;
|
|
||||||
}
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("stop handling except propagation");
|
|
||||||
}
|
|
||||||
ImGui::EndPopup();
|
|
||||||
}
|
|
||||||
|
|
||||||
// LogView
|
|
||||||
if (win_.shownInCurrentFrame()) {
|
|
||||||
ImGui::SetNextWindowSize({48*em, 16*em}, ImGuiCond_FirstUseEver);
|
|
||||||
}
|
|
||||||
if (win_.Begin()) {
|
|
||||||
constexpr auto kTableFlags =
|
constexpr auto kTableFlags =
|
||||||
ImGuiTableFlags_Resizable |
|
ImGuiTableFlags_Resizable |
|
||||||
ImGuiTableFlags_Hideable |
|
ImGuiTableFlags_Hideable |
|
||||||
@@ -375,7 +319,11 @@ void Logger::Update() noexcept {
|
|||||||
constexpr auto kFlags =
|
constexpr auto kFlags =
|
||||||
ImGuiSelectableFlags_SpanAllColumns |
|
ImGuiSelectableFlags_SpanAllColumns |
|
||||||
ImGuiSelectableFlags_AllowItemOverlap;
|
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()) {
|
if (ImGui::BeginPopupContextItem()) {
|
||||||
UpdateRowMenu(row);
|
UpdateRowMenu(row);
|
||||||
ImGui::EndPopup();
|
ImGui::EndPopup();
|
||||||
@@ -383,9 +331,31 @@ void Logger::Update() noexcept {
|
|||||||
}
|
}
|
||||||
// msg column
|
// msg column
|
||||||
if (ImGui::TableNextColumn()) {
|
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()) {
|
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
|
// path column
|
||||||
@@ -421,25 +391,6 @@ void Logger::Update() noexcept {
|
|||||||
ImGui::EndTable();
|
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(); });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
} // namespace nf7
|
} // namespace nf7
|
||||||
|
|||||||
@@ -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
84
file/system_node.cc
Normal 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
|
||||||
@@ -7,15 +7,18 @@
|
|||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <imgui_internal.h>
|
#include <imgui_internal.h>
|
||||||
|
|
||||||
|
#include <ImNodes.h>
|
||||||
|
|
||||||
#include <yas/serialize.hpp>
|
#include <yas/serialize.hpp>
|
||||||
#include <yas/types/utility/usertype.hpp>
|
#include <yas/types/utility/usertype.hpp>
|
||||||
|
|
||||||
#include "nf7.hh"
|
#include "nf7.hh"
|
||||||
|
|
||||||
#include "common/dir_item.hh"
|
#include "common/dir_item.hh"
|
||||||
|
#include "common/file_base.hh"
|
||||||
#include "common/generic_memento.hh"
|
#include "common/generic_memento.hh"
|
||||||
#include "common/generic_type_info.hh"
|
#include "common/generic_type_info.hh"
|
||||||
#include "common/gui_node.hh"
|
#include "common/gui.hh"
|
||||||
#include "common/life.hh"
|
#include "common/life.hh"
|
||||||
#include "common/node.hh"
|
#include "common/node.hh"
|
||||||
#include "common/ptr_selector.hh"
|
#include "common/ptr_selector.hh"
|
||||||
@@ -27,20 +30,15 @@
|
|||||||
namespace nf7 {
|
namespace nf7 {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
class Curve final : public nf7::File,
|
class Curve final : public nf7::FileBase,
|
||||||
public nf7::DirItem,
|
public nf7::DirItem,
|
||||||
public nf7::Node,
|
public nf7::Node,
|
||||||
public nf7::Sequencer {
|
public nf7::Sequencer {
|
||||||
public:
|
public:
|
||||||
static inline const nf7::GenericTypeInfo<Curve> kType =
|
static inline const nf7::GenericTypeInfo<Curve> kType = {
|
||||||
{"Value/Curve", {"nf7::DirItem", "nf7::Node", "nf7::Sequencer"}};
|
"Value/Curve", {"nf7::DirItem", "nf7::Node", "nf7::Sequencer"},
|
||||||
static void UpdateTypeTooltip() noexcept {
|
"bezier curve editor",
|
||||||
ImGui::TextUnformatted("bezier curve");
|
};
|
||||||
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Node");
|
|
||||||
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Sequencer");
|
|
||||||
ImGui::Bullet(); ImGui::TextUnformatted(
|
|
||||||
"changes will be applied to active lambdas immediately");
|
|
||||||
}
|
|
||||||
|
|
||||||
class NodeLambda;
|
class NodeLambda;
|
||||||
class SeqLambda;
|
class SeqLambda;
|
||||||
@@ -67,12 +65,12 @@ class Curve final : public nf7::File,
|
|||||||
};
|
};
|
||||||
|
|
||||||
Curve(nf7::Env& env, Data&& data = {}) noexcept :
|
Curve(nf7::Env& env, Data&& data = {}) noexcept :
|
||||||
nf7::File(kType, env),
|
nf7::FileBase(kType, env),
|
||||||
nf7::DirItem(nf7::DirItem::kWidget),
|
nf7::DirItem(nf7::DirItem::kWidget),
|
||||||
nf7::Node(nf7::Node::kCustomNode),
|
nf7::Node(nf7::Node::kCustomNode),
|
||||||
nf7::Sequencer(nf7::Sequencer::kCustomItem |
|
nf7::Sequencer(nf7::Sequencer::kCustomItem |
|
||||||
nf7::Sequencer::kParamPanel),
|
nf7::Sequencer::kParamPanel),
|
||||||
life_(*this), mem_(std::move(data), *this) {
|
life_(*this), mem_(*this, std::move(data)) {
|
||||||
AssignId();
|
AssignId();
|
||||||
Sanitize();
|
Sanitize();
|
||||||
}
|
}
|
||||||
@@ -94,13 +92,8 @@ class Curve final : public nf7::File,
|
|||||||
std::shared_ptr<nf7::Sequencer::Lambda> CreateLambda(
|
std::shared_ptr<nf7::Sequencer::Lambda> CreateLambda(
|
||||||
const std::shared_ptr<nf7::Context>&) noexcept override;
|
const std::shared_ptr<nf7::Context>&) noexcept override;
|
||||||
|
|
||||||
std::span<const std::string> GetInputs() const noexcept override {
|
nf7::Node::Meta GetMeta() const noexcept override {
|
||||||
static const std::vector<std::string> kInputs = {"x"};
|
return {{"x"}, {"y"}};
|
||||||
return kInputs;
|
|
||||||
}
|
|
||||||
std::span<const std::string> GetOutputs() const noexcept override {
|
|
||||||
static const std::vector<std::string> kOutputs = {"y"};
|
|
||||||
return kOutputs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateItem(nf7::Sequencer::Editor&) noexcept override;
|
void UpdateItem(nf7::Sequencer::Editor&) noexcept override;
|
||||||
@@ -309,11 +302,10 @@ class Curve::NodeLambda final : public nf7::Node::Lambda,
|
|||||||
nf7::Node::Lambda(f, parent), f_(f.life_) {
|
nf7::Node::Lambda(f, parent), f_(f.life_) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Handle(std::string_view, const nf7::Value& v,
|
void Handle(const nf7::Node::Lambda::Msg& in) noexcept override
|
||||||
const std::shared_ptr<nf7::Node::Lambda>& caller) noexcept override
|
|
||||||
try {
|
try {
|
||||||
f_.EnforceAlive();
|
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&) {
|
} catch (nf7::Exception&) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,8 +386,8 @@ void Curve::UpdateCurveEditorWindow(const ImVec2& size) noexcept {
|
|||||||
const auto pad = ImGui::GetStyle().WindowPadding / 2;
|
const auto pad = ImGui::GetStyle().WindowPadding / 2;
|
||||||
ImGui::SetCursorPos(pad);
|
ImGui::SetCursorPos(pad);
|
||||||
UpdateCurveEditor(ImGui::GetContentRegionAvail()-pad*2);
|
UpdateCurveEditor(ImGui::GetContentRegionAvail()-pad*2);
|
||||||
ImGui::EndChild();
|
|
||||||
}
|
}
|
||||||
|
ImGui::EndChild();
|
||||||
}
|
}
|
||||||
void Curve::UpdateCurveEditor(const ImVec2& sz) noexcept {
|
void Curve::UpdateCurveEditor(const ImVec2& sz) noexcept {
|
||||||
const auto& io = ImGui::GetIO();
|
const auto& io = ImGui::GetIO();
|
||||||
|
|||||||
590
file/value_imm.cc
Normal file
590
file/value_imm.cc
Normal 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
447
file/value_plot.cc
Normal 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
28
init.hh
@@ -7,13 +7,29 @@
|
|||||||
|
|
||||||
inline std::unique_ptr<nf7::File> CreateRoot(nf7::Env& env) noexcept {
|
inline std::unique_ptr<nf7::File> CreateRoot(nf7::Env& env) noexcept {
|
||||||
auto ret = nf7::File::registry("System/Dir").Create(env);
|
auto ret = nf7::File::registry("System/Dir").Create(env);
|
||||||
auto& dir = ret->interfaceOrThrow<nf7::Dir>();
|
auto& root = ret->interfaceOrThrow<nf7::Dir>();
|
||||||
|
|
||||||
dir.Add("_audio", nf7::File::registry("Audio/Context").Create(env));
|
const auto Add = [&](nf7::Dir& dir, const char* name, const char* type) -> nf7::File& {
|
||||||
dir.Add("_imgui", nf7::File::registry("System/ImGui").Create(env));
|
return dir.Add(name, nf7::File::registry(type).Create(env));
|
||||||
dir.Add("_logger", nf7::File::registry("System/Logger").Create(env));
|
};
|
||||||
dir.Add("_luajit", nf7::File::registry("LuaJIT/Context").Create(env));
|
|
||||||
|
|
||||||
dir.Add("home", nf7::File::registry("System/Dir").Create(env));
|
Add(root, "_audio", "Audio/Context");
|
||||||
|
Add(root, "_font", "Font/Context");
|
||||||
|
Add(root, "_imgui", "System/ImGui");
|
||||||
|
Add(root, "_logger", "System/Logger");
|
||||||
|
Add(root, "_luajit", "LuaJIT/Context");
|
||||||
|
|
||||||
|
auto& node = Add(root, "node", "System/Dir").interfaceOrThrow<nf7::Dir>();
|
||||||
|
{
|
||||||
|
auto& system = Add(node, "system", "System/Dir").interfaceOrThrow<nf7::Dir>();
|
||||||
|
{
|
||||||
|
Add(system, "save", "System/Node/Save");
|
||||||
|
Add(system, "exit", "System/Node/Exit");
|
||||||
|
Add(system, "panic", "System/Node/Panic");
|
||||||
|
Add(system, "time", "System/Node/Time");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Add(root, "home", "System/Dir").interfaceOrThrow<nf7::Dir>();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user