From 04869ec813c4a7501f8acfdc06d3d7e71f16358b Mon Sep 17 00:00:00 2001 From: Dario Date: Sun, 16 Aug 2026 10:51:45 -0300 Subject: [PATCH] Update to use RecompFrontend. --- .gitmodules | 13 +- CMakeLists.txt | 106 +- assets/config_menu/controls.rml | 360 -- assets/config_menu/debug.rml | 108 - assets/config_menu/general.rml | 369 -- assets/config_menu/graphics.rml | 325 -- assets/config_menu/mods.rml | 9 - assets/config_menu/sound.rml | 89 - assets/icons/Cont.svg | 3 + assets/icons/Keyboard.svg | 3 + assets/scss/.nvmrc | 1 - assets/scss/.stylelintrc | 99 - assets/scss/main.scss | 2 - assets/scss/package-lock.json | 2261 ---------- assets/scss/package.json | 29 - assets/scss/styles/base.scss | 11 - assets/scss/styles/components/BottomLeft.scss | 14 - assets/scss/styles/components/Button.scss | 81 - .../scss/styles/components/CenteredPage.scss | 73 - assets/scss/styles/components/Config.scss | 101 - .../styles/components/ConfigDescription.scss | 27 - .../scss/styles/components/ConfigGroup.scss | 29 - .../scss/styles/components/ConfigOption.scss | 413 -- .../scss/styles/components/ControlOption.scss | 170 - assets/scss/styles/components/IconButton.scss | 92 - .../scss/styles/components/InputConfig.scss | 349 -- assets/scss/styles/components/Launcher.scss | 108 - .../scss/styles/components/MenuListItem.scss | 71 - assets/scss/styles/components/Prompt.scss | 70 - .../scss/styles/components/SubtitleTitle.scss | 71 - assets/scss/styles/components/Tabs.scss | 62 - assets/scss/styles/components/Toggle.scss | 97 - .../scss/styles/components/_components.scss | 16 - assets/scss/styles/functions/_spacing.scss | 4 - assets/scss/styles/global.scss | 132 - assets/scss/styles/globals/_old.scss | 212 - assets/scss/styles/globals/_scrollbars.scss | 52 - assets/scss/styles/mixins/_helpers.scss | 39 - assets/scss/styles/mixins/_transitions.scss | 36 - assets/scss/styles/mixins/_typography.scss | 83 - assets/scss/styles/pages/_pages.scss | 1 - assets/scss/styles/pages/config/_config.scss | 1 - assets/scss/styles/pages/config/debug.scss | 178 - assets/scss/styles/vars/_animations.scss | 4 - assets/scss/styles/vars/_borders.scss | 42 - assets/scss/styles/vars/_colors.scss | 71 - assets/scss/styles/vars/_gradients.scss | 7 - assets/scss/styles/vars/_spacing.scss | 7 - assets/scss/styles/vars/_transitions.scss | 3 - include/recomp_input.h | 202 - include/recomp_ui.h | 146 - include/zelda_config.h | 93 +- include/zelda_debug.h | 5 + include/zelda_launcher.h | 11 + include/zelda_render.h | 61 - include/zelda_sound.h | 4 - lib/FindFreetype.cmake | 9 - lib/GamepadMotionHelpers/.gitignore | 32 - lib/GamepadMotionHelpers/CMakeLists.txt | 11 - lib/GamepadMotionHelpers/GamepadMotion.hpp | 1312 ------ lib/GamepadMotionHelpers/LICENSE | 21 - lib/GamepadMotionHelpers/README.md | 76 - lib/N64ModernRuntime | 2 +- lib/RecompFrontend | 1 + lib/RmlUi | 1 - lib/concurrentqueue/blockingconcurrentqueue.h | 582 --- lib/concurrentqueue/concurrentqueue.h | 3747 ----------------- lib/concurrentqueue/lightweightsemaphore.h | 425 -- lib/freetype-windows-binaries | 1 - lib/lunasvg | 1 - lib/rt64 | 2 +- patches/camera_transform_tagging.c | 12 +- patches/effect_patches.c | 6 +- patches/effect_transform_tagging.c | 6 +- patches/item_transform_tagging.c | 9 +- patches/patches.h | 16 +- patches/recompui_event_structs.h | 25 +- patches/sky_transform_tagging.c | 6 +- patches/specific_actor_transform_tagging.c | 9 +- patches/ui_patches.c | 12 +- shadercache/.gitkeep | 0 shaders/InterfacePS.hlsl | 11 - shaders/InterfaceVS.hlsl | 23 - src/game/config.cpp | 669 +-- src/game/controls.cpp | 116 - src/game/input.cpp | 909 ---- src/game/recomp_actor_api.cpp | 2 +- src/game/recomp_api.cpp | 19 +- src/game/recomp_data_api.cpp | 2 +- src/main/launcher_animation.cpp | 13 + src/main/main.cpp | 156 +- src/main/rt64_render_context.cpp | 530 --- src/main/theme.cpp | 106 + src/main/theme.h | 6 + src/ui/core/ui_context.cpp | 725 ---- src/ui/core/ui_context.h | 70 - src/ui/core/ui_resource.h | 24 - src/ui/elements/ui_button.cpp | 114 - src/ui/elements/ui_button.h | 33 - src/ui/elements/ui_clickable.cpp | 77 - src/ui/elements/ui_clickable.h | 23 - src/ui/elements/ui_container.cpp | 13 - src/ui/elements/ui_container.h | 14 - src/ui/elements/ui_element.cpp | 516 --- src/ui/elements/ui_element.h | 106 - src/ui/elements/ui_image.cpp | 11 - src/ui/elements/ui_image.h | 14 - src/ui/elements/ui_label.cpp | 43 - src/ui/elements/ui_label.h | 22 - src/ui/elements/ui_radio.cpp | 268 -- src/ui/elements/ui_radio.h | 58 - src/ui/elements/ui_scroll_container.cpp | 27 - src/ui/elements/ui_scroll_container.h | 19 - src/ui/elements/ui_slider.cpp | 243 -- src/ui/elements/ui_slider.h | 58 - src/ui/elements/ui_span.cpp | 15 - src/ui/elements/ui_span.h | 16 - src/ui/elements/ui_style.cpp | 612 --- src/ui/elements/ui_style.h | 108 - src/ui/elements/ui_text_input.cpp | 61 - src/ui/elements/ui_text_input.h | 23 - src/ui/elements/ui_toggle.cpp | 161 - src/ui/elements/ui_toggle.h | 38 - src/ui/elements/ui_types.h | 282 -- src/ui/ui_api.cpp | 1031 ----- src/ui/ui_api_events.cpp | 118 - src/ui/ui_api_images.cpp | 131 - src/ui/ui_api_images.h | 10 - src/ui/ui_color_hack.cpp | 172 - src/ui/ui_config.cpp | 1060 ----- src/ui/ui_config_sub_menu.cpp | 269 -- src/ui/ui_config_sub_menu.h | 117 - src/ui/ui_elements.cpp | 42 - src/ui/ui_elements.h | 16 - src/ui/ui_helpers.h | 154 - src/ui/ui_launcher.cpp | 127 - src/ui/ui_mod_details_panel.cpp | 153 - src/ui/ui_mod_details_panel.h | 52 - src/ui/ui_mod_installer.cpp | 343 -- src/ui/ui_mod_installer.h | 42 - src/ui/ui_mod_menu.cpp | 799 ---- src/ui/ui_mod_menu.h | 130 - src/ui/ui_prompt.cpp | 360 -- src/ui/ui_renderer.cpp | 721 ---- src/ui/ui_renderer.h | 38 - src/ui/ui_rml_hacks.cpp | 106 - src/ui/ui_rml_hacks.hpp | 13 - src/ui/ui_state.cpp | 1019 ----- src/ui/ui_utils.cpp | 20 - src/ui/ui_utils.h | 11 - src/ui/util/bem.h | 12 - src/ui/util/hsv.cpp | 145 - src/ui/util/hsv.h | 77 - 153 files changed, 620 insertions(+), 26374 deletions(-) delete mode 100644 assets/config_menu/controls.rml delete mode 100644 assets/config_menu/debug.rml delete mode 100644 assets/config_menu/general.rml delete mode 100644 assets/config_menu/graphics.rml delete mode 100644 assets/config_menu/mods.rml delete mode 100644 assets/config_menu/sound.rml create mode 100644 assets/icons/Cont.svg create mode 100644 assets/icons/Keyboard.svg delete mode 100644 assets/scss/.nvmrc delete mode 100644 assets/scss/.stylelintrc delete mode 100644 assets/scss/main.scss delete mode 100644 assets/scss/package-lock.json delete mode 100644 assets/scss/package.json delete mode 100644 assets/scss/styles/base.scss delete mode 100644 assets/scss/styles/components/BottomLeft.scss delete mode 100644 assets/scss/styles/components/Button.scss delete mode 100644 assets/scss/styles/components/CenteredPage.scss delete mode 100644 assets/scss/styles/components/Config.scss delete mode 100644 assets/scss/styles/components/ConfigDescription.scss delete mode 100644 assets/scss/styles/components/ConfigGroup.scss delete mode 100644 assets/scss/styles/components/ConfigOption.scss delete mode 100644 assets/scss/styles/components/ControlOption.scss delete mode 100644 assets/scss/styles/components/IconButton.scss delete mode 100644 assets/scss/styles/components/InputConfig.scss delete mode 100644 assets/scss/styles/components/Launcher.scss delete mode 100644 assets/scss/styles/components/MenuListItem.scss delete mode 100644 assets/scss/styles/components/Prompt.scss delete mode 100644 assets/scss/styles/components/SubtitleTitle.scss delete mode 100644 assets/scss/styles/components/Tabs.scss delete mode 100644 assets/scss/styles/components/Toggle.scss delete mode 100644 assets/scss/styles/components/_components.scss delete mode 100644 assets/scss/styles/functions/_spacing.scss delete mode 100644 assets/scss/styles/global.scss delete mode 100644 assets/scss/styles/globals/_old.scss delete mode 100644 assets/scss/styles/globals/_scrollbars.scss delete mode 100644 assets/scss/styles/mixins/_helpers.scss delete mode 100644 assets/scss/styles/mixins/_transitions.scss delete mode 100644 assets/scss/styles/mixins/_typography.scss delete mode 100644 assets/scss/styles/pages/_pages.scss delete mode 100644 assets/scss/styles/pages/config/_config.scss delete mode 100644 assets/scss/styles/pages/config/debug.scss delete mode 100644 assets/scss/styles/vars/_animations.scss delete mode 100644 assets/scss/styles/vars/_borders.scss delete mode 100644 assets/scss/styles/vars/_colors.scss delete mode 100644 assets/scss/styles/vars/_gradients.scss delete mode 100644 assets/scss/styles/vars/_spacing.scss delete mode 100644 assets/scss/styles/vars/_transitions.scss delete mode 100644 include/recomp_input.h delete mode 100644 include/recomp_ui.h create mode 100644 include/zelda_launcher.h delete mode 100644 include/zelda_render.h delete mode 100644 lib/FindFreetype.cmake delete mode 100644 lib/GamepadMotionHelpers/.gitignore delete mode 100644 lib/GamepadMotionHelpers/CMakeLists.txt delete mode 100644 lib/GamepadMotionHelpers/GamepadMotion.hpp delete mode 100644 lib/GamepadMotionHelpers/LICENSE delete mode 100644 lib/GamepadMotionHelpers/README.md create mode 160000 lib/RecompFrontend delete mode 160000 lib/RmlUi delete mode 100644 lib/concurrentqueue/blockingconcurrentqueue.h delete mode 100644 lib/concurrentqueue/concurrentqueue.h delete mode 100644 lib/concurrentqueue/lightweightsemaphore.h delete mode 160000 lib/freetype-windows-binaries delete mode 160000 lib/lunasvg delete mode 100644 shadercache/.gitkeep delete mode 100644 shaders/InterfacePS.hlsl delete mode 100644 shaders/InterfaceVS.hlsl delete mode 100644 src/game/controls.cpp delete mode 100644 src/game/input.cpp create mode 100644 src/main/launcher_animation.cpp delete mode 100644 src/main/rt64_render_context.cpp create mode 100644 src/main/theme.cpp create mode 100644 src/main/theme.h delete mode 100644 src/ui/core/ui_context.cpp delete mode 100644 src/ui/core/ui_context.h delete mode 100644 src/ui/core/ui_resource.h delete mode 100644 src/ui/elements/ui_button.cpp delete mode 100644 src/ui/elements/ui_button.h delete mode 100644 src/ui/elements/ui_clickable.cpp delete mode 100644 src/ui/elements/ui_clickable.h delete mode 100644 src/ui/elements/ui_container.cpp delete mode 100644 src/ui/elements/ui_container.h delete mode 100644 src/ui/elements/ui_element.cpp delete mode 100644 src/ui/elements/ui_element.h delete mode 100644 src/ui/elements/ui_image.cpp delete mode 100644 src/ui/elements/ui_image.h delete mode 100644 src/ui/elements/ui_label.cpp delete mode 100644 src/ui/elements/ui_label.h delete mode 100644 src/ui/elements/ui_radio.cpp delete mode 100644 src/ui/elements/ui_radio.h delete mode 100644 src/ui/elements/ui_scroll_container.cpp delete mode 100644 src/ui/elements/ui_scroll_container.h delete mode 100644 src/ui/elements/ui_slider.cpp delete mode 100644 src/ui/elements/ui_slider.h delete mode 100644 src/ui/elements/ui_span.cpp delete mode 100644 src/ui/elements/ui_span.h delete mode 100644 src/ui/elements/ui_style.cpp delete mode 100644 src/ui/elements/ui_style.h delete mode 100644 src/ui/elements/ui_text_input.cpp delete mode 100644 src/ui/elements/ui_text_input.h delete mode 100644 src/ui/elements/ui_toggle.cpp delete mode 100644 src/ui/elements/ui_toggle.h delete mode 100644 src/ui/elements/ui_types.h delete mode 100644 src/ui/ui_api.cpp delete mode 100644 src/ui/ui_api_events.cpp delete mode 100644 src/ui/ui_api_images.cpp delete mode 100644 src/ui/ui_api_images.h delete mode 100644 src/ui/ui_color_hack.cpp delete mode 100644 src/ui/ui_config.cpp delete mode 100644 src/ui/ui_config_sub_menu.cpp delete mode 100644 src/ui/ui_config_sub_menu.h delete mode 100644 src/ui/ui_elements.cpp delete mode 100644 src/ui/ui_elements.h delete mode 100644 src/ui/ui_helpers.h delete mode 100644 src/ui/ui_launcher.cpp delete mode 100644 src/ui/ui_mod_details_panel.cpp delete mode 100644 src/ui/ui_mod_details_panel.h delete mode 100644 src/ui/ui_mod_installer.cpp delete mode 100644 src/ui/ui_mod_installer.h delete mode 100644 src/ui/ui_mod_menu.cpp delete mode 100644 src/ui/ui_mod_menu.h delete mode 100644 src/ui/ui_prompt.cpp delete mode 100644 src/ui/ui_renderer.cpp delete mode 100644 src/ui/ui_renderer.h delete mode 100644 src/ui/ui_rml_hacks.cpp delete mode 100644 src/ui/ui_rml_hacks.hpp delete mode 100644 src/ui/ui_state.cpp delete mode 100644 src/ui/ui_utils.cpp delete mode 100644 src/ui/ui_utils.h delete mode 100644 src/ui/util/bem.h delete mode 100644 src/ui/util/hsv.cpp delete mode 100644 src/ui/util/hsv.h diff --git a/.gitmodules b/.gitmodules index df5b4ae..cba1613 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,22 +1,15 @@ -[submodule "lib/RmlUi"] - path = lib/RmlUi - url = https://github.com/mikke89/RmlUi/ [submodule "lib/rt64"] path = lib/rt64 url = https://github.com/rt64/rt64 -[submodule "lib/freetype-windows-binaries"] - path = lib/freetype-windows-binaries - url = https://github.com/ubawurinna/freetype-windows-binaries [submodule "lib/mm-decomp"] path = lib/mm-decomp url = https://github.com/zeldaret/mm -[submodule "lib/lunasvg"] - path = lib/lunasvg - url = https://github.com/sammycage/lunasvg [submodule "lib/N64ModernRuntime"] path = lib/N64ModernRuntime url = https://github.com/N64Recomp/N64ModernRuntime.git [submodule "Zelda64RecompSyms"] path = Zelda64RecompSyms url = https://github.com/Zelda64Recomp/Zelda64RecompSyms - +[submodule "lib/RecompFrontend"] + path = lib/RecompFrontend + url = https://github.com/N64Recomp/RecompFrontend diff --git a/CMakeLists.txt b/CMakeLists.txt index 1cdda09..51e19b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,15 +51,7 @@ endif() add_subdirectory(${CMAKE_SOURCE_DIR}/lib/rt64 ${CMAKE_BINARY_DIR}/rt64) -# set(BUILD_SHARED_LIBS_SAVED "${BUILD_SHARED_LIBS}") set(BUILD_SHARED_LIBS OFF) -SET(LUNASVG_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) -add_subdirectory(${CMAKE_SOURCE_DIR}/lib/lunasvg) -# set(BUILD_SHARED_LIBS "${BUILD_SHARED_LIBS_SAVED}") -SET(RMLUI_SVG_PLUGIN ON CACHE BOOL "" FORCE) -SET(RMLUI_TESTS_ENABLED OFF CACHE BOOL "" FORCE) -add_subdirectory(${CMAKE_SOURCE_DIR}/lib/RmlUi) -target_compile_definitions(rmlui_core PRIVATE LUNASVG_BUILD_STATIC) add_subdirectory(${CMAKE_SOURCE_DIR}/lib/N64ModernRuntime) @@ -148,14 +140,13 @@ add_custom_command(OUTPUT add_executable(Zelda64Recompiled) set (SOURCES + ${CMAKE_SOURCE_DIR}/src/main/launcher_animation.cpp ${CMAKE_SOURCE_DIR}/src/main/main.cpp ${CMAKE_SOURCE_DIR}/src/main/support.cpp ${CMAKE_SOURCE_DIR}/src/main/register_overlays.cpp ${CMAKE_SOURCE_DIR}/src/main/register_patches.cpp - ${CMAKE_SOURCE_DIR}/src/main/rt64_render_context.cpp + ${CMAKE_SOURCE_DIR}/src/main/theme.cpp - ${CMAKE_SOURCE_DIR}/src/game/input.cpp - ${CMAKE_SOURCE_DIR}/src/game/controls.cpp ${CMAKE_SOURCE_DIR}/src/game/config.cpp ${CMAKE_SOURCE_DIR}/src/game/scene_table.cpp ${CMAKE_SOURCE_DIR}/src/game/debug.cpp @@ -165,68 +156,35 @@ set (SOURCES ${CMAKE_SOURCE_DIR}/src/game/recomp_data_api.cpp ${CMAKE_SOURCE_DIR}/src/game/rom_decompression.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_renderer.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_state.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_launcher.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_config.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_prompt.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_config_sub_menu.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_color_hack.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_rml_hacks.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_elements.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_mod_details_panel.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_mod_installer.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_mod_menu.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_api.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_api_events.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_api_images.cpp - ${CMAKE_SOURCE_DIR}/src/ui/ui_utils.cpp - ${CMAKE_SOURCE_DIR}/src/ui/util/hsv.cpp - ${CMAKE_SOURCE_DIR}/src/ui/core/ui_context.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_button.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_clickable.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_container.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_element.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_image.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_label.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_radio.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_scroll_container.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_slider.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_span.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_style.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_text_input.cpp - ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_toggle.cpp - ${CMAKE_SOURCE_DIR}/rsp/aspMain.cpp ${CMAKE_SOURCE_DIR}/rsp/njpgdspMain.cpp - - ${CMAKE_SOURCE_DIR}/lib/RmlUi/Backends/RmlUi_Platform_SDL.cpp ) -if (APPLE) - list(APPEND SOURCES ${CMAKE_SOURCE_DIR}/src/main/support_apple.mm) -endif() - target_include_directories(Zelda64Recompiled PRIVATE ${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/lib/N64ModernRuntime/N64Recomp/include - ${CMAKE_SOURCE_DIR}/lib/concurrentqueue - ${CMAKE_SOURCE_DIR}/lib/GamepadMotionHelpers - ${CMAKE_SOURCE_DIR}/lib/RmlUi/Include - ${CMAKE_SOURCE_DIR}/lib/RmlUi/Backends + ${CMAKE_SOURCE_DIR}/lib/N64ModernRuntime/thirdparty/sse2neon ${CMAKE_SOURCE_DIR}/lib/rt64/src/contrib ${CMAKE_SOURCE_DIR}/lib/rt64/src/contrib/hlslpp/include ${CMAKE_SOURCE_DIR}/lib/rt64/src/contrib/dxc/inc ${CMAKE_SOURCE_DIR}/lib/rt64/src ${CMAKE_SOURCE_DIR}/lib/rt64/src/rhi ${CMAKE_SOURCE_DIR}/lib/rt64/src/render - ${CMAKE_SOURCE_DIR}/lib/freetype-windows-binaries/include + ${CMAKE_SOURCE_DIR}/lib/RecompFrontend/recompinput/include + ${CMAKE_SOURCE_DIR}/lib/RecompFrontend/recompui/include + ${CMAKE_SOURCE_DIR}/lib/RecompFrontend/recompui/src ${CMAKE_SOURCE_DIR}/lib/rt64/src/contrib/nativefiledialog-extended/src/include ${CMAKE_SOURCE_DIR}/lib/SlotMap - ${CMAKE_BINARY_DIR}/shaders ${CMAKE_CURRENT_BINARY_DIR} ) +# Generate icon_bytes.c from the app icon PNG. +add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/icon_bytes.c ${CMAKE_CURRENT_BINARY_DIR}/icon_bytes.h + COMMAND file_to_c ${CMAKE_SOURCE_DIR}/icons/512.png icon_bytes ${CMAKE_CURRENT_BINARY_DIR}/icon_bytes.c ${CMAKE_CURRENT_BINARY_DIR}/icon_bytes.h + DEPENDS ${CMAKE_SOURCE_DIR}/icons/512.png +) +target_sources(Zelda64Recompiled PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/icon_bytes.c) + if(CMAKE_SIZEOF_VOID_P EQUAL 8 AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|amd64|AMD64") target_compile_options(Zelda64Recompiled PRIVATE -march=nehalem @@ -309,14 +267,9 @@ endif() if (CMAKE_SYSTEM_NAME MATCHES "Linux") find_package(SDL2 REQUIRED) - add_compile_definitions("RT64_SDL_WINDOW_VULKAN") - # Generate icon_bytes.c from the app icon PNG. - add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/icon_bytes.c ${CMAKE_CURRENT_BINARY_DIR}/icon_bytes.h - COMMAND file_to_c ${CMAKE_SOURCE_DIR}/icons/512.png icon_bytes ${CMAKE_CURRENT_BINARY_DIR}/icon_bytes.c ${CMAKE_CURRENT_BINARY_DIR}/icon_bytes.h - DEPENDS ${CMAKE_SOURCE_DIR}/icons/512.png - ) - target_sources(Zelda64Recompiled PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/icon_bytes.c) + add_compile_definitions("PLUME_SDL_VULKAN_ENABLED") + add_compile_definitions("RT64_SDL_WINDOW_VULKAN") message(STATUS "SDL2_FOUND = ${SDL2_FOUND}") message(STATUS "SDL2_INCLUDE_DIRS = ${SDL2_INCLUDE_DIRS}") @@ -339,18 +292,6 @@ if (CMAKE_SYSTEM_NAME MATCHES "Linux") target_link_libraries(Zelda64Recompiled PRIVATE "-latomic -static-libstdc++" ${CMAKE_DL_LIBS} Threads::Threads) endif() -target_link_libraries(Zelda64Recompiled PRIVATE - PatchesLib - RecompiledFuncs - librecomp - ultramodern - rt64 - RmlUi::Core - RmlUi::Debugger - nfd - lunasvg -) - # TODO fix the rt64 CMake script so that this doesn't need to be duplicated here # For DXC set (DXC_COMMON_OPTS "-I${PROJECT_SOURCE_DIR}/src") @@ -383,8 +324,21 @@ else() endif() endif() -build_vertex_shader(Zelda64Recompiled "shaders/InterfaceVS.hlsl" "shaders/InterfaceVS.hlsl") -build_pixel_shader(Zelda64Recompiled "shaders/InterfacePS.hlsl" "shaders/InterfacePS.hlsl") +set(RECOMP_FRONTEND_N64MODERNRUNTIME_PATH ${CMAKE_SOURCE_DIR}/lib/N64ModernRuntime) +set(RECOMP_FRONTEND_RT64_PATH ${CMAKE_SOURCE_DIR}/lib/rt64) + +add_subdirectory(${CMAKE_SOURCE_DIR}/lib/RecompFrontend) + +target_link_libraries(Zelda64Recompiled PRIVATE + PatchesLib + RecompiledFuncs + recompui + recompinput + librecomp + ultramodern + rt64 + nfd +) # Embed all .nrm files in the "mods" directory file(GLOB NRM_FILES "${CMAKE_SOURCE_DIR}/mods/*.nrm") diff --git a/assets/config_menu/controls.rml b/assets/config_menu/controls.rml deleted file mode 100644 index d947ae8..0000000 --- a/assets/config_menu/controls.rml +++ /dev/null @@ -1,360 +0,0 @@ - - \ No newline at end of file diff --git a/assets/config_menu/debug.rml b/assets/config_menu/debug.rml deleted file mode 100644 index 22cb26e..0000000 --- a/assets/config_menu/debug.rml +++ /dev/null @@ -1,108 +0,0 @@ - - \ No newline at end of file diff --git a/assets/config_menu/general.rml b/assets/config_menu/general.rml deleted file mode 100644 index 291bf8c..0000000 --- a/assets/config_menu/general.rml +++ /dev/null @@ -1,369 +0,0 @@ - diff --git a/assets/config_menu/graphics.rml b/assets/config_menu/graphics.rml deleted file mode 100644 index a11de72..0000000 --- a/assets/config_menu/graphics.rml +++ /dev/null @@ -1,325 +0,0 @@ - diff --git a/assets/config_menu/mods.rml b/assets/config_menu/mods.rml deleted file mode 100644 index debab39..0000000 --- a/assets/config_menu/mods.rml +++ /dev/null @@ -1,9 +0,0 @@ - diff --git a/assets/config_menu/sound.rml b/assets/config_menu/sound.rml deleted file mode 100644 index badadce..0000000 --- a/assets/config_menu/sound.rml +++ /dev/null @@ -1,89 +0,0 @@ - - \ No newline at end of file diff --git a/assets/icons/Cont.svg b/assets/icons/Cont.svg new file mode 100644 index 0000000..5959613 --- /dev/null +++ b/assets/icons/Cont.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/Keyboard.svg b/assets/icons/Keyboard.svg new file mode 100644 index 0000000..880ceb0 --- /dev/null +++ b/assets/icons/Keyboard.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/scss/.nvmrc b/assets/scss/.nvmrc deleted file mode 100644 index b7586d6..0000000 --- a/assets/scss/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -iron diff --git a/assets/scss/.stylelintrc b/assets/scss/.stylelintrc deleted file mode 100644 index 60ad85f..0000000 --- a/assets/scss/.stylelintrc +++ /dev/null @@ -1,99 +0,0 @@ -{ - "extends": [ - "stylelint-config-recommended", - "stylelint-config-standard", - "stylelint-config-sass-guidelines", - "stylelint-config-property-sort-order-smacss", - "stylelint-config-standard-scss" - ], - "plugins": ["stylelint-scss"], - "overrides": [{ - "files": ["**/*.scss"], - "customSyntax": "postcss-scss" - }], - "rules": { - "no-descending-specificity": null, - "declaration-empty-line-before": null, - "declaration-colon-newline-after": null, - "declaration-block-no-duplicate-properties": true, - "declaration-block-trailing-semicolon": null, - "declaration-no-important": true, - "declaration-property-value-disallowed-list": { - "/^transition/": ["/all/"] - }, - "block-closing-brace-newline-after": null, - "max-empty-lines": null, - "selector-list-comma-newline-after": null, - "at-rule-empty-line-before": null, - "at-rule-semicolon-newline-after": null, - "selector-pseudo-element-colon-notation": "single", - "color-no-hex": true, - "function-url-quotes": "always", - "max-nesting-depth": [ - 4, - { - "ignoreAtRules": ["each", "media", "supports", "include"], - "severity": "warning" - } - ], - "number-leading-zero": null, - "order/order": [ - [ - "custom-properties", - "dollar-variables", - { - "type": "at-rule", - "name": "extend" - }, - { - "type": "at-rule", - "name": "include" - }, - "declarations", - "rules" - ] - ], - "order/properties-alphabetical-order": null, - "property-no-vendor-prefix": [ - true, - { - "severity": "warning" - } - ], - "value-no-vendor-prefix": [ - true, - { - "severity": "warning" - } - ], - "scss/selector-no-redundant-nesting-selector": null, - "selector-class-pattern": null, - "selector-max-compound-selectors": [ - 4, - { - "severity": "warning" - } - ], - "selector-no-qualifying-type": null, - "string-quotes": null, - "max-line-length": null, - "keyframes-name-pattern": null, - "scss/double-slash-comment-empty-line-before": null, - "scss/double-slash-comment-whitespace-inside": null, - "scss/dollar-variable-empty-line-before": null, - "scss/dollar-variable-pattern": "[a-z-]", - "scss/at-import-partial-extension": null, - "declaration-block-no-redundant-longhand-properties": null, - "color-function-notation": null, - "alpha-value-notation": null, - "at-rule-no-unknown": null, - "property-no-unknown": null, - "scss/at-rule-no-unknown": true, - "selector-pseudo-class-no-unknown": [true, { "ignorePseudoClasses": ["selected"] }], - "font-family-no-missing-generic-family-keyword": null, - "scss/no-global-function-names": null, - "unit-no-unknown": [true, { "ignoreUnits": ["dp"] }], - "selector-type-no-unknown": [true, { "ignore": ["custom-elements", "default-namespace"] }], - "value-keyword-case": null - } -} diff --git a/assets/scss/main.scss b/assets/scss/main.scss deleted file mode 100644 index 9d03ad3..0000000 --- a/assets/scss/main.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import "styles/base"; -@import "styles/global"; diff --git a/assets/scss/package-lock.json b/assets/scss/package-lock.json deleted file mode 100644 index 78ab77a..0000000 --- a/assets/scss/package-lock.json +++ /dev/null @@ -1,2261 +0,0 @@ -{ - "name": "mmrecomp-ui-scss", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "mmrecomp-ui-scss", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "sass": "^1.75.0" - }, - "devDependencies": { - "postcss-scss": "^4.0.9", - "stylelint": "^15.11.0", - "stylelint-config-property-sort-order-smacss": "^9.1.0", - "stylelint-config-sass-guidelines": "^10.0.0", - "stylelint-config-scss": "^1.0.0-security", - "stylelint-config-standard": "^34.0.0", - "stylelint-config-standard-scss": "^11.1.0", - "stylelint-scss": "^5.3.1" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@csstools/css-parser-algorithms": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.3.2.tgz", - "integrity": "sha512-sLYGdAdEY2x7TSw9FtmdaTrh2wFtRJO5VMbBrA8tEqEod7GEggFmxTSK9XqExib3yMuYNcvcTdCZIP6ukdjAIA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "@csstools/css-tokenizer": "^2.2.1" - } - }, - "node_modules/@csstools/css-tokenizer": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.2.1.tgz", - "integrity": "sha512-Zmsf2f/CaEPWEVgw29odOj+WEVoiJy9s9NOv5GgNY9mZ1CZ7394By6wONrONrTsnNDv6F9hR02nvFihrGVGHBg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - } - }, - "node_modules/@csstools/media-query-list-parser": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.5.tgz", - "integrity": "sha512-IxVBdYzR8pYe89JiyXQuYk4aVVoCPhMJkz6ElRwlVysjwURTsTk/bmY/z4FfeRE+CRBMlykPwXEVUg8lThv7AQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.3.2", - "@csstools/css-tokenizer": "^2.2.1" - } - }, - "node_modules/@csstools/selector-specificity": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.0.0.tgz", - "integrity": "sha512-hBI9tfBtuPIi885ZsZ32IMEU/5nlZH/KOVYJCOh7gyMxaVLGmLedYqFN6Ui1LXkI8JlC8IsuC0rF0btcRZKd5g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^6.0.13" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@types/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", - "dev": true - }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", - "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", - "dev": true - }, - "node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/balanced-match": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", - "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", - "dev": true - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/brace-expansion/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/camelcase-keys": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-7.0.2.tgz", - "integrity": "sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==", - "dev": true, - "dependencies": { - "camelcase": "^6.3.0", - "map-obj": "^4.1.0", - "quick-lru": "^5.1.1", - "type-fest": "^1.2.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/cosmiconfig": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", - "dev": true, - "dependencies": { - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/css-functions-list": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.1.tgz", - "integrity": "sha512-Nj5YcaGgBtuUmn1D7oHqPW0c9iui7xsTsj5lIX8ZgevdfhmjFfKB3r8moHJtNJnctnYXJyYX5I1pp90HM4TPgQ==", - "dev": true, - "engines": { - "node": ">=12 || >=16" - } - }, - "node_modules/css-property-sort-order-smacss": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/css-property-sort-order-smacss/-/css-property-sort-order-smacss-2.2.0.tgz", - "integrity": "sha512-nXutswsivIEBOrPo/OZw2KQjFPLvtg68aovJf6Kqrm3L6FmTvvFPaeDrk83hh0+pRJGuP3PeKJwMS0E6DFipdQ==", - "dev": true - }, - "node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", - "dev": true, - "dependencies": { - "mdn-data": "2.0.30", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-5.0.1.tgz", - "integrity": "sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decamelize-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", - "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", - "dev": true, - "dependencies": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decamelize-keys/node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decamelize-keys/node_modules/map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true, - "engines": { - "node": ">= 4.9.1" - } - }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-7.0.2.tgz", - "integrity": "sha512-TfW7/1iI4Cy7Y8L6iqNdZQVvdXn0f8B4QcIXmkIbtTIe/Okm/nSlHb4IwGzRVOd3WfSieCgvf5cMzEfySAIl0g==", - "dev": true, - "dependencies": { - "flat-cache": "^3.2.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", - "dev": true - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/global-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", - "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", - "dev": true, - "dependencies": { - "global-prefix": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", - "dev": true, - "dependencies": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globjoin": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", - "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", - "dev": true - }, - "node_modules/hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/html-tags": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", - "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/immutable": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz", - "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==" - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/import-lazy": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", - "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", - "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", - "dev": true, - "dependencies": { - "hasown": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/known-css-properties": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.29.0.tgz", - "integrity": "sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ==", - "dev": true - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/map-obj": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", - "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mathml-tag-names": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", - "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", - "dev": true - }, - "node_modules/meow": { - "version": "10.1.5", - "resolved": "https://registry.npmjs.org/meow/-/meow-10.1.5.tgz", - "integrity": "sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==", - "dev": true, - "dependencies": { - "@types/minimist": "^1.2.2", - "camelcase-keys": "^7.0.0", - "decamelize": "^5.0.0", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.2", - "read-pkg-up": "^8.0.0", - "redent": "^4.0.0", - "trim-newlines": "^4.0.2", - "type-fest": "^1.2.2", - "yargs-parser": "^20.2.9" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dev": true, - "dependencies": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.4.32", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", - "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-media-query-parser": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", - "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", - "dev": true - }, - "node_modules/postcss-resolve-nested-selector": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz", - "integrity": "sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==", - "dev": true - }, - "node_modules/postcss-safe-parser": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", - "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", - "dev": true, - "engines": { - "node": ">=12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.3.3" - } - }, - "node_modules/postcss-scss": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz", - "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss-scss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "postcss": "^8.4.29" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.0.13", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", - "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", - "dev": true, - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-sorting": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-8.0.2.tgz", - "integrity": "sha512-M9dkSrmU00t/jK7rF6BZSZauA5MAaBW4i5EnJXspMwt4iqTh/L9j6fgMnbElEOfyRyfLfVbIHj/R52zHzAPe1Q==", - "dev": true, - "peerDependencies": { - "postcss": "^8.4.20" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-6.0.0.tgz", - "integrity": "sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q==", - "dev": true, - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^3.0.2", - "parse-json": "^5.2.0", - "type-fest": "^1.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-8.0.0.tgz", - "integrity": "sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ==", - "dev": true, - "dependencies": { - "find-up": "^5.0.0", - "read-pkg": "^6.0.0", - "type-fest": "^1.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/redent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-4.0.0.tgz", - "integrity": "sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==", - "dev": true, - "dependencies": { - "indent-string": "^5.0.0", - "strip-indent": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/sass": { - "version": "1.75.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.75.0.tgz", - "integrity": "sha512-ShMYi3WkrDWxExyxSZPst4/okE9ts46xZmJDSawJQrnte7M1V9fScVB+uNXOVKRBt0PggHOwoZcn8mYX4trnBw==", - "dependencies": { - "chokidar": ">=3.0.0 <4.0.0", - "immutable": "^4.0.0", - "source-map-js": ">=0.6.2 <2.0.0" - }, - "bin": { - "sass": "sass.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/slice-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dev": true, - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz", - "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==", - "dev": true - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.0.0.tgz", - "integrity": "sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==", - "dev": true, - "dependencies": { - "min-indent": "^1.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/style-search": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", - "integrity": "sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==", - "dev": true - }, - "node_modules/stylelint": { - "version": "15.11.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.11.0.tgz", - "integrity": "sha512-78O4c6IswZ9TzpcIiQJIN49K3qNoXTM8zEJzhaTE/xRTCZswaovSEVIa/uwbOltZrk16X4jAxjaOhzz/hTm1Kw==", - "dev": true, - "dependencies": { - "@csstools/css-parser-algorithms": "^2.3.1", - "@csstools/css-tokenizer": "^2.2.0", - "@csstools/media-query-list-parser": "^2.1.4", - "@csstools/selector-specificity": "^3.0.0", - "balanced-match": "^2.0.0", - "colord": "^2.9.3", - "cosmiconfig": "^8.2.0", - "css-functions-list": "^3.2.1", - "css-tree": "^2.3.1", - "debug": "^4.3.4", - "fast-glob": "^3.3.1", - "fastest-levenshtein": "^1.0.16", - "file-entry-cache": "^7.0.0", - "global-modules": "^2.0.0", - "globby": "^11.1.0", - "globjoin": "^0.1.4", - "html-tags": "^3.3.1", - "ignore": "^5.2.4", - "import-lazy": "^4.0.0", - "imurmurhash": "^0.1.4", - "is-plain-object": "^5.0.0", - "known-css-properties": "^0.29.0", - "mathml-tag-names": "^2.1.3", - "meow": "^10.1.5", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.4.28", - "postcss-resolve-nested-selector": "^0.1.1", - "postcss-safe-parser": "^6.0.0", - "postcss-selector-parser": "^6.0.13", - "postcss-value-parser": "^4.2.0", - "resolve-from": "^5.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "style-search": "^0.1.0", - "supports-hyperlinks": "^3.0.0", - "svg-tags": "^1.0.0", - "table": "^6.8.1", - "write-file-atomic": "^5.0.1" - }, - "bin": { - "stylelint": "bin/stylelint.mjs" - }, - "engines": { - "node": "^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/stylelint" - } - }, - "node_modules/stylelint-config-property-sort-order-smacss": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/stylelint-config-property-sort-order-smacss/-/stylelint-config-property-sort-order-smacss-9.1.0.tgz", - "integrity": "sha512-TijYeDoDgHAFjpn9NnziQrmUCGrm2AM4e1HzsdI2mCWBRkQRuewc343YqDwdFgQ5eHoMZ3JRL02i72W3vktuDA==", - "dev": true, - "dependencies": { - "css-property-sort-order-smacss": "~2.2.0", - "stylelint-order": "^6.0.2" - }, - "peerDependencies": { - "stylelint": "^14.0.0 || ^15.0.0" - } - }, - "node_modules/stylelint-config-recommended": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-13.0.0.tgz", - "integrity": "sha512-EH+yRj6h3GAe/fRiyaoO2F9l9Tgg50AOFhaszyfov9v6ayXJ1IkSHwTxd7lB48FmOeSGDPLjatjO11fJpmarkQ==", - "dev": true, - "engines": { - "node": "^14.13.1 || >=16.0.0" - }, - "peerDependencies": { - "stylelint": "^15.10.0" - } - }, - "node_modules/stylelint-config-recommended-scss": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-13.1.0.tgz", - "integrity": "sha512-8L5nDfd+YH6AOoBGKmhH8pLWF1dpfY816JtGMePcBqqSsLU+Ysawx44fQSlMOJ2xTfI9yTGpup5JU77c17w1Ww==", - "dev": true, - "dependencies": { - "postcss-scss": "^4.0.9", - "stylelint-config-recommended": "^13.0.0", - "stylelint-scss": "^5.3.0" - }, - "peerDependencies": { - "postcss": "^8.3.3", - "stylelint": "^15.10.0" - }, - "peerDependenciesMeta": { - "postcss": { - "optional": true - } - } - }, - "node_modules/stylelint-config-sass-guidelines": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-sass-guidelines/-/stylelint-config-sass-guidelines-10.0.0.tgz", - "integrity": "sha512-+Rr2Dd4b72CWA4qoj1Kk+y449nP/WJsrD0nzQAWkmPPIuyVcy2GMIcfNr0Z8JJOLjRvtlkKxa49FCNXMePBikQ==", - "dev": true, - "dependencies": { - "postcss-scss": "^4.0.6", - "stylelint-scss": "^4.4.0" - }, - "engines": { - "node": "^14.13.1 || >=16.13.0 || >=18.0.0" - }, - "peerDependencies": { - "postcss": "^8.4.21", - "stylelint": "^15.2.0" - } - }, - "node_modules/stylelint-config-sass-guidelines/node_modules/stylelint-scss": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-4.7.0.tgz", - "integrity": "sha512-TSUgIeS0H3jqDZnby1UO1Qv3poi1N8wUYIJY6D1tuUq2MN3lwp/rITVo0wD+1SWTmRm0tNmGO0b7nKInnqF6Hg==", - "dev": true, - "dependencies": { - "postcss-media-query-parser": "^0.2.3", - "postcss-resolve-nested-selector": "^0.1.1", - "postcss-selector-parser": "^6.0.11", - "postcss-value-parser": "^4.2.0" - }, - "peerDependencies": { - "stylelint": "^14.5.1 || ^15.0.0" - } - }, - "node_modules/stylelint-config-scss": { - "version": "1.0.0-security", - "resolved": "https://registry.npmjs.org/stylelint-config-scss/-/stylelint-config-scss-1.0.0-security.tgz", - "integrity": "sha512-8Pgul2mNpzTeK2KCuyV5RcDC1BgzWzU7dCLVJWuxpkKgmxrMqCvjqgyosaKbAVZy2AiaMU0zfHBt7prg7/NaxA==", - "dev": true - }, - "node_modules/stylelint-config-standard": { - "version": "34.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-34.0.0.tgz", - "integrity": "sha512-u0VSZnVyW9VSryBG2LSO+OQTjN7zF9XJaAJRX/4EwkmU0R2jYwmBSN10acqZisDitS0CLiEiGjX7+Hrq8TAhfQ==", - "dev": true, - "dependencies": { - "stylelint-config-recommended": "^13.0.0" - }, - "engines": { - "node": "^14.13.1 || >=16.0.0" - }, - "peerDependencies": { - "stylelint": "^15.10.0" - } - }, - "node_modules/stylelint-config-standard-scss": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/stylelint-config-standard-scss/-/stylelint-config-standard-scss-11.1.0.tgz", - "integrity": "sha512-5gnBgeNTgRVdchMwiFQPuBOtj9QefYtfXiddrOMJA2pI22zxt6ddI2s+e5Oh7/6QYl7QLJujGnaUR5YyGq72ow==", - "dev": true, - "dependencies": { - "stylelint-config-recommended-scss": "^13.1.0", - "stylelint-config-standard": "^34.0.0" - }, - "peerDependencies": { - "postcss": "^8.3.3", - "stylelint": "^15.10.0" - }, - "peerDependenciesMeta": { - "postcss": { - "optional": true - } - } - }, - "node_modules/stylelint-order": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-6.0.3.tgz", - "integrity": "sha512-1j1lOb4EU/6w49qZeT2SQVJXm0Ht+Qnq9GMfUa3pMwoyojIWfuA+JUDmoR97Bht1RLn4ei0xtLGy87M7d29B1w==", - "dev": true, - "dependencies": { - "postcss": "^8.4.21", - "postcss-sorting": "^8.0.2" - }, - "peerDependencies": { - "stylelint": "^14.0.0 || ^15.0.0" - } - }, - "node_modules/stylelint-scss": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-5.3.1.tgz", - "integrity": "sha512-5I9ZDIm77BZrjOccma5WyW2nJEKjXDd4Ca8Kk+oBapSO4pewSlno3n+OyimcyVJJujQZkBN2D+xuMkIamSc6hA==", - "dev": true, - "dependencies": { - "known-css-properties": "^0.29.0", - "postcss-media-query-parser": "^0.2.3", - "postcss-resolve-nested-selector": "^0.1.1", - "postcss-selector-parser": "^6.0.13", - "postcss-value-parser": "^4.2.0" - }, - "peerDependencies": { - "stylelint": "^14.5.1 || ^15.0.0" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/supports-hyperlinks": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.0.0.tgz", - "integrity": "sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=14.18" - } - }, - "node_modules/supports-hyperlinks/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/svg-tags": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", - "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", - "dev": true - }, - "node_modules/table": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", - "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", - "dev": true, - "dependencies": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/trim-newlines": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-4.1.1.tgz", - "integrity": "sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/write-file-atomic": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", - "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/assets/scss/package.json b/assets/scss/package.json deleted file mode 100644 index 1055122..0000000 --- a/assets/scss/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "mmrecomp-ui-scss", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "start": "npm run watch", - "watch": "sass --no-source-map --no-charset --style=compressed main.scss ..\\recomp.rcss --watch", - "watch:debug": "sass --no-source-map --no-charset main.scss ..\\recomp.rcss --watch", - "build": "sass --no-source-map --no-charset --style=compressed main.scss ..\\recomp.rcss", - "build:debug": "sass --no-source-map --no-charset main.scss ..\\recomp.rcss", - "lint": "stylelint '.\\**\\*.scss'" - }, - "author": "", - "license": "ISC", - "devDependencies": { - "postcss-scss": "^4.0.9", - "stylelint": "^15.11.0", - "stylelint-config-property-sort-order-smacss": "^9.1.0", - "stylelint-config-sass-guidelines": "^10.0.0", - "stylelint-config-scss": "^1.0.0-security", - "stylelint-config-standard": "^34.0.0", - "stylelint-config-standard-scss": "^11.1.0", - "stylelint-scss": "^5.3.1" - }, - "dependencies": { - "sass": "^1.75.0" - } -} diff --git a/assets/scss/styles/base.scss b/assets/scss/styles/base.scss deleted file mode 100644 index 88e158e..0000000 --- a/assets/scss/styles/base.scss +++ /dev/null @@ -1,11 +0,0 @@ -@use 'sass:math'; -@import "./functions/spacing"; -@import "./vars/spacing"; -@import "./vars/colors"; -@import "./vars/borders"; -@import "./vars/gradients"; -@import "./vars/transitions"; -@import "./vars/animations"; -@import "./mixins/typography"; -@import "./mixins/transitions"; -@import "./mixins/helpers"; diff --git a/assets/scss/styles/components/BottomLeft.scss b/assets/scss/styles/components/BottomLeft.scss deleted file mode 100644 index 28821c8..0000000 --- a/assets/scss/styles/components/BottomLeft.scss +++ /dev/null @@ -1,14 +0,0 @@ -@use 'sass:math'; - -.bottom-left { - display: flex; - position: absolute; - bottom: space(4); - flex-direction: row; - align-items: flex-start; - justify-content: flex-start; - width: 100%; - max-width: space($base-modal-max-width); - height: auto; - margin: 0 space(4); -} diff --git a/assets/scss/styles/components/Button.scss b/assets/scss/styles/components/Button.scss deleted file mode 100644 index d5d1514..0000000 --- a/assets/scss/styles/components/Button.scss +++ /dev/null @@ -1,81 +0,0 @@ -@use 'sass:color'; - -@mixin create-button-variation($base-col) { - border-color: rgba($base-col, 0.8); - background-color: rgba($base-col, 0.05); - color: $color-text-dim; - - &:focus, &:hover { - border-color: $base-col; - background-color: rgba($base-col, 0.3); - color: $color-text; - } - - &:disabled,&[disabled] { - color: $color-text-inactive; - } - - &:active { - background-color: rgba($base-col, 0.2); - color: color.scale($color-text, $lightness: 20%); - } -} - -.button { - @extend %label-md; - @extend %nav-all; - @include create-button-variation($color-primary); - @include trans-colors; - - display: block; - width: auto; - height: auto; - // leave 1dp room for border expansion - padding: space(24 - 1); - border-width: $border-width-thickness; - - border-radius: $border-radius-md; - - // Setting it by default for convenience - &--primary { - @include create-button-variation($color-primary); - } - - &--large { - @extend %label-lg; - } - - &--secondary { - @include create-button-variation($color-secondary); - } - - &--tertiary { - @include create-button-variation($color-text); - } - - &--success { - @include create-button-variation($color-success); - } - - &--error { - @include create-button-variation($color-error); - } - - &--warning { - @include create-button-variation($color-warning); - } - - &:not([disabled]) { - @extend %nav-all; - cursor: pointer; - } - - &:disabled,&[disabled] { - opacity: 0.5; - } - - &__label { - width: auto; - height: auto; - } -} diff --git a/assets/scss/styles/components/CenteredPage.scss b/assets/scss/styles/components/CenteredPage.scss deleted file mode 100644 index a724472..0000000 --- a/assets/scss/styles/components/CenteredPage.scss +++ /dev/null @@ -1,73 +0,0 @@ -@use 'sass:math'; - -.centered-page { - display: flex; - // visibility: hidden; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - padding: space($page-margin); - background-color: $color-border-soft; -} - -.centered-page__modal { - display: flex; - position: relative; - flex: 1 1 100%; - flex-direction: column; - width: 100%; - max-width: space($base-modal-max-width); - height: 100%; - margin: auto; - border-width: $border-width-thickness; - border-radius: $border-radius-modal; - border-color: $color-border; - background: $color-modal-overlay; - - > .tabs { - display: flex; - position: relative; - flex: 1 1 100%; - flex-direction: column; - width: 100%; - max-width: space($base-modal-max-width); - height: 100%; - margin: auto; - } - - panels { - flex: 1 1 100%; - } -} - -.centered-page__controls { - display: flex; - position: absolute; - bottom: space(24); - flex-direction: row; - align-items: center; - justify-content: center; - width: 100%; - max-width: space($base-modal-max-width); - height: auto; - margin: 0 auto; - - > label { - @extend %label-sm; - display: inline-block; - align-items: center; - justify-content: space-between; - width: auto; - height: space(24); - - &:not(:last-child) { - margin-right: space(40); - } - - > span:first-child { - margin-right: space(4); - } - } -} diff --git a/assets/scss/styles/components/Config.scss b/assets/scss/styles/components/Config.scss deleted file mode 100644 index 54c6e1d..0000000 --- a/assets/scss/styles/components/Config.scss +++ /dev/null @@ -1,101 +0,0 @@ - -.config__icon-buttons { - display: flex; - position: absolute; - top: space(8); - right: space(0); - flex-direction: row; - align-items: center; - justify-content: flex-end; - width: auto; - - .icon-button { - margin: 0 space(8); - } -} - -.config__form { - @include border-top($color-border-soft); - display: flex; - flex: 1 1 100%; - flex-direction: column; - justify-content: space-between; - width: 100%; - height: 100%; - border-bottom-right-radius: $border-radius-modal; - border-bottom-left-radius: $border-radius-modal; -} - -.config__wrapper { - flex: 1 1 100%; - width: auto; - height: auto; - padding: space(16); - border-radius: 0dp; - border-bottom-right-radius: $border-radius-modal; - border-bottom-left-radius: $border-radius-modal; - background-color: $color-bg-shadow; - text-align: left; - - p { - @extend %body; - padding: space(16); - line-height: space(28); - white-space: pre-line; - - b { - color: $color-primary; - } - - i { - color: $color-warning; - font-style: normal; - } - } -} - -.config__hz-wrapper { - display: flex; - flex: 1 1 100%; - flex-direction: row; - width: 100%; - height: 100%; - border-radius: 0dp; - text-align: left; -} - -.config__header, .config__footer { - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - background-color: $color-bg-shadow; -} - -.config__header { - @include border-bottom($color-border-soft); - padding: space(12) space(20); -} - -.config__footer { - @include border-top($color-border-soft); - padding: space(20) space(20); - border-bottom-right-radius: $border-radius-modal; - border-bottom-left-radius: $border-radius-modal; -} - -.config__header-left { - display: flex; - flex: 1 1 auto; - flex-direction: row; - - > :not(:first-child) { - margin-left: space(8); - } -} - -.config__row { - display: flex; - flex-direction: row; -} - diff --git a/assets/scss/styles/components/ConfigDescription.scss b/assets/scss/styles/components/ConfigDescription.scss deleted file mode 100644 index 2f4966f..0000000 --- a/assets/scss/styles/components/ConfigDescription.scss +++ /dev/null @@ -1,27 +0,0 @@ -.config-description { - flex: 1 1 100%; - width: auto; - height: auto; - padding: space(16); - border-radius: 0dp; - border-bottom-right-radius: $border-radius-modal; - border-bottom-left-radius: $border-radius-modal; - background-color: $color-bg-shadow; - text-align: left; - - &__contents { - @extend %body; - padding: space(16); - line-height: space(28); - white-space: pre-line; - - b { - color: $color-primary; - } - - i { - color: $color-warning; - font-style: normal; - } - } -} diff --git a/assets/scss/styles/components/ConfigGroup.scss b/assets/scss/styles/components/ConfigGroup.scss deleted file mode 100644 index 9f6444b..0000000 --- a/assets/scss/styles/components/ConfigGroup.scss +++ /dev/null @@ -1,29 +0,0 @@ - -.config-group { - position: relative; - - &--scrollable { - flex: 1 1 100%; - width: auto; - height: auto; - padding: 0 0 0 space(16); - - .config-group__wrapper { - max-height: 100%; - overflow-y: auto; - } - } - - &__title { - @extend %label-md; - color: $color-primary; - - &--hidden { - display: none; - } - } - - &__wrapper { - padding: space(16) 0; - } -} diff --git a/assets/scss/styles/components/ConfigOption.scss b/assets/scss/styles/components/ConfigOption.scss deleted file mode 100644 index 26acd85..0000000 --- a/assets/scss/styles/components/ConfigOption.scss +++ /dev/null @@ -1,413 +0,0 @@ -.config-option { - display: flex; - flex: 1; - flex-direction: column; - align-items: flex-start; - justify-content: flex-start; - margin: space(16) space(0) space(24); - - &--hz { - flex-direction: row-reverse; - align-items: center; - margin-top: space(4); - margin-bottom: space(4); - - .config-option__title { - @extend %label-md; - flex: 1 1 100%; - } - - .config-option__list { - flex: 1 1 auto; - width: auto; - } - - &:first-child { - margin-top: 0; - } - - &:last-child { - margin-bottom: 0; - } - } -} - -.config-option__title { - @extend %label-md; - padding: 0 space(12); -} - -.config-option__radio-tabs, -.config-option__list { - display: flex; - flex-direction: row; - align-items: flex-start; - justify-content: flex-start; - width: 100%; - height: auto; - padding: 0; - - input:first-of-type { - nav-left: none; - } - - input:last-of-type { - nav-right: none; - } - - .config-option__tab-label { - @extend %label-sm; - @include trans-colors-opa; - display: block; - position: relative; - height: auto; - margin: space(4) space(12) 0; - padding: space(8) 0; - color: $color-text-inactive; - tab-index: none; - - &:hover { - color: $color-text; - cursor: pointer; - } - } - - .config-option__checkbox-wrapper { - @include trans-colors-opa; - width: space(32); - height: space(32); - margin: space(4) space(12) 0; - border-radius: $border-radius-sm; - opacity: 0.5; - background-color: $color-bg-overlay; - cursor: pointer; - - &:hover { - opacity: 1; - } - - &[checked] { - background-color: $color-a; - } - } - - .config-option__checkbox { - @extend %nav-all; - @include trans-colors-opa; - visibility: visible; - width: 0; - height: 0; - } - - // TODO: Remove & Replace old stylings - input.radio { - @extend %nav-all; - @include trans-colors-opa; - visibility: visible; - width: 0; - height: 0; - - &:not(:disabled) { - &:checked + .config-option__tab-label { - border-bottom: 1dp; - border-color: $color-text; - color: $color-text; - - &:hover { - cursor: default; - } - } - - .rmlui-window:not([mouse-active]) &:focus + .config-option__tab-label { - transition: none; - animation: $focus-anim-border; - border-color: $color-secondary; - color: $color-secondary; - } - - &:focus + .config-option__tab-label, - &:hover + .config-option__tab-label { - color: $color-text; - } - } - - &:disabled + .config-option__tab-label { - opacity: 0.5; - - &:hover { - cursor: default; - } - } - } - - input.range slidertrack { - @include trans-colors; - height: 2dp; - margin-top: space(8); - background-color: $color-border; - } - - input.range sliderbar { - @include trans-colors; - width: space(16); - height: space(16); - margin-top: space(1); - margin-right: space(-8); - margin-left: space(-8); - transition: background-color $transition-quick; - border-radius: 8dp; - background-color: $color-text-dim; - - .rmlui-window:not([mouse-active]) &:focus { - @include border($color-a); - animation: $focus-anim-bg; - } - - &:hover { - background-color: $color-text; - cursor: pointer; - } - } - - input.range sliderbar:active, - input.range slidertrack:active + sliderbar { - background-color: $color-secondary; - } - - input.range sliderarrowdec, - input.range sliderarrowinc { - display: none; - } -} - -.config-option__details { - @extend %label-xs; - height: space(18); - margin: space(14) space(12) 0; - color: $color-primary; -} - -.config-option-color { - width: 100%; - max-width: space(360); - height: auto; - margin-top: space(4); - margin-left: space(12); - padding: 0; - - &__preview-wrapper { - display: flex; - flex-direction: row; - width: 100%; - height: space(8 * 9); - } - - &__preview-block { - display: block; - width: space(8 * 11); - height: 100%; - border-width: $border-width-thickness; - border-radius: $border-radius-lg; - border-color: $color-border; - } - - &__hsv-wrapper { - display: flex; - flex: 1 1 100%; - flex-direction: column; - width: auto; - height: auto; - padding-left: space(8); - - .config-option-range { - flex: 1 1 auto; - - label { - min-width: space(72); - } - - input { - flex: 1 1 auto; - } - } - } -} - -.config-option-range { - display: flex; - flex-direction: row; - align-items: flex-start; - justify-content: flex-start; - width: 100%; - max-width: space(360); - height: auto; - margin-top: space(4); - padding: 0; - - &__label { - @extend %label-sm; - - display: block; - width: space(56); - margin: 0 12dp; - margin-right: space(16); - padding: 0; - color: $color-text; - tab-index: none; - } - - &__range-input { - flex: 1; - - slidertrack { - @include trans-colors; - height: 2dp; - margin-top: space(8); - background-color: $color-border; - } - - sliderbar { - @include trans-colors; - width: space(16); - height: space(16); - margin-top: space(1); - margin-right: space(-8); - margin-left: space(-8); - transition: background-color $transition-quick; - border-radius: 8dp; - background-color: $color-text-dim; - - .rmlui-window:not([mouse-active]) &:focus { - @include border($color-a); - animation: $focus-anim-bg; - } - - &:hover { - background-color: $color-text; - cursor: pointer; - } - } - - sliderbar:active, - slidertrack:active + sliderbar { - background-color: $color-secondary; - } - - sliderarrowdec, - sliderarrowinc { - display: none; - } - } - -} - -.config-option__range-wrapper { - max-width: space(360); - margin-top: space(4); -} - -.config-option__range-label { - @extend %label-sm; - - display: block; - // flex: 0 0 space(32); - width: space(56); - margin: 0 12dp; - margin-right: space(16); - padding: 0; - color: $color-text; - tab-index: none; -} - -.config-option-dropdown, .config-option-textfield { - display: flex; - position: relative; - flex: 1 1 100%; - flex-direction: row; - align-items: center; - justify-content: flex-start; - width: auto; - height: auto; - padding: space(8) space(24) space(8) space(12); - - &__select { - display: block; - height: space(48); - padding: space(14); - cursor: pointer; - } - - &__wrapper { - // Cursed guess & check so that this appears to be the same height as the select - $extra-space: 2; - - display: flex; - align-items: center; - justify-content: flex-start; - width: 100%; - height: auto; - padding: space(0 + $extra-space) 0 space(10 + $extra-space); - cursor: text; - - input { - width: 100%; - height: auto; - vertical-align: middle; - } - } - - &__select, &__wrapper { - @extend %body; - @extend %nav-all; - @include trans-colors-border; - @include border($color-white-a50); - position: relative; - box-sizing: border-box; - flex: 1 1 100%; - - width: auto; - border-radius: $border-radius-md; - background-color: $color-white-a5; - - &:hover, &:focus { - @include border($color-white-a80); - background-color: $color-white-a20; - } - - selectvalue { - display: inline; - height: auto; - margin: auto 0; - } - - selectbox { - @include border($color-border); - margin-top: space(2); - padding: space(4) 0; - border-radius: $border-radius-md; - background-color: $color-background-3; - - option { - @extend %nav-all; - @include trans-colors; - padding: space(8) space(12); - background-color: $color-transparent; - color: $color-text-dim; - font-weight: 400; - - &:hover, &:focus { - background-color: $color-white-a20; - } - - &:hover:not(:checked) { - cursor: pointer; - } - - &:checked { - background-color: $color-white-a5; - color: $color-white; - } - } - } - } -} diff --git a/assets/scss/styles/components/ControlOption.scss b/assets/scss/styles/components/ControlOption.scss deleted file mode 100644 index 662fd63..0000000 --- a/assets/scss/styles/components/ControlOption.scss +++ /dev/null @@ -1,170 +0,0 @@ -@use 'sass:color'; -@use 'sass:math'; - -.control-option { - @include set-color($color-text-dim); - @include trans-colors-svg; - display: flex; - position: relative; - flex-direction: row; - align-items: center; - justify-content: space-between; - width: 100%; - height: auto; - padding: space(4) space(16) space(4) space(20); - border-radius: $border-radius-sm; - background-color: rgba(0, 0, 0, 0); - - &:focus-visible:not(:disabled, [disabled]), - &:hover:not(:disabled, [disabled]) { - @include set-color($color-text); - background-color: $color-bg-overlay; - } - - &:disabled, &[disabled] { - opacity: 0.5; - } - - &--active { - // while actively looking for inputs, set styles to the correct slots - $valid-binding-slots: 0, 1; - @each $slot in $valid-binding-slots { - // global attr -> this active row -> binding slot - [cur-binding-slot="#{$slot}"] & .control-option__binding[bind-slot="#{$slot}"] { - border-color: $color-error; - - .control-option__binding-icon { - opacity: 0; - } - - .control-option__binding-recording { - opacity: 1; - } - } - } - } - - .icon-button { - flex: 1 1 auto; - } -} - -.control-option__label { - @extend %label-md; - flex: 2 1 space(300); - height: auto; - white-space: nowrap; -} - -.control-option__bindings { - display: flex; - position: relative; - flex: 2 1 space(400); - flex-direction: row; - align-items: center; - justify-content: space-between; - width: 100%; - height: space(56); - padding: 0 space(12) 0 space(4); -} - -.control-option__binding { - @include set-color($color-text-dim); - @include trans-colors-border; - display: flex; - position: relative; - - flex: 1 1 100%; - align-items: center; - justify-content: center; - width: 100%; - height: space(56); - margin: 0 space(4); - padding: space(8); - border-width: $border-width-thickness; - border-radius: $border-radius-sm; - border-color: $color-bg-overlay; - background-color: $color-bg-overlay; - - &:focus, &:hover { - @include set-color($color-text); - border-color: $color-text; - background-color: $color-border-soft; - } - - &:active { - @include set-color(color.scale($color-text, $lightness: 20%)); - } - - &:disabled, &[disabled] { - @include set-color($color-text-dim); - opacity: 0.5; - } - - &:not([disabled]) { - @extend %nav-all; - cursor: pointer; - } -} - -.control-option__binding-icon { - @include trans-colors-opa; - opacity: 1; -} - -@keyframes control-option__binding-recording-scale { - 0% { - transform: scale(1); - } - - 50% { - transform: scale(0.85); - } - - 100% { - transform: scale(1); - } -} - -.control-option__binding-recording { - @include trans-colors-opa; - display: flex; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - align-items: center; - justify-content: center; - opacity: 0; - - .control-option__binding-circle { - $rec-size: 24; - - width: space($rec-size); - height: space($rec-size); - animation: 1.5s sine-in-out infinite control-option__binding-recording-scale; - border-radius: space($rec-size); - background-color: $color-error; - } - - .control-option__binding-edge { - $edge-size: 36; - $h-edge-size: math.div($edge-size, 2); - - position: absolute; - - top: 50%; - left: 50%; - width: space($edge-size); - height: space($edge-size); - - transform: translate(-50%, -50%); - - > svg.control-option__binding-edge-svg { - width: space($edge-size); - height: space($edge-size); - image-color: $color-error; - } - } -} diff --git a/assets/scss/styles/components/IconButton.scss b/assets/scss/styles/components/IconButton.scss deleted file mode 100644 index 4e1822a..0000000 --- a/assets/scss/styles/components/IconButton.scss +++ /dev/null @@ -1,92 +0,0 @@ -@use 'sass:color'; - -/* - -*/ -@mixin create-icon-button-variation($base-col) { - border-color: rgba($base-col, 0.8); - background-color: rgba($base-col, 0.05); - - &:focus, &:hover { - border-color: $base-col; - background-color: rgba($base-col, 0.3); - } - - &:active { - background-color: rgba($base-col, 0.2); - } -} - -$icon-button-size: 56 - ($border-width-thickness-num * 2); - -.icon-button { - @include set-color($color-text-dim); - @include trans-colors-border; - - display: flex; - align-items: center; - justify-content: center; - width: space($icon-button-size); - min-width: space($icon-button-size); - max-width: space($icon-button-size); - height: space($icon-button-size); - min-height: space($icon-button-size); - max-height: space($icon-button-size); - border-width: $border-width-thickness; - border-radius: space($icon-button-size * 0.5); - border-color: $color-transparent; - background-color: $color-transparent; - - &:focus, &:hover { - @include set-color($color-text); - background-color: $color-border; - } - - &:active { - @include set-color(color.scale($color-text, $lightness: 20%)); - background-color: $color-border-soft; - } - - &:disabled,&[disabled] { - @include set-color($color-text-dim); - opacity: 0.5; - } - - &:not([disabled]) { - @extend %nav-all; - cursor: pointer; - } - - svg { - width: space(32); - height: space(32); - } - - &--primary { - @include create-icon-button-variation($color-primary); - } - - &--secondary { - @include create-icon-button-variation($color-secondary); - } - - &--tertiary { - @include create-icon-button-variation($color-text); - } - - &--success { - @include create-icon-button-variation($color-success); - } - - &--error { - @include create-icon-button-variation($color-error); - } - - &--warning { - @include create-icon-button-variation($color-warning); - } -} diff --git a/assets/scss/styles/components/InputConfig.scss b/assets/scss/styles/components/InputConfig.scss deleted file mode 100644 index c01374e..0000000 --- a/assets/scss/styles/components/InputConfig.scss +++ /dev/null @@ -1,349 +0,0 @@ -@use 'sass:math'; - -// Probably will need to adjust for other langs... -$mapping-min-width: 80 * 8; -$visual-max-width: $base-modal-max-width - $mapping-min-width - $scrollbar-width; - -.input-config { - padding: 0; -} - -.input-config__horizontal-split { - display: flex; - position: relative; - flex-direction: row; - height: 100%; -} - -.input-config__mappings { - display: block; - flex: 1 1 auto; - min-width: space($mapping-min-width); - height: 100%; -} - -.input-config__mappings-scroll { - display: block; - width: 100%; - max-height: 100%; - overflow-y: auto; -} - -.input-config__mappings-wrapper { - padding: space(8); -} - -.input-config__visual-wrapper { - display: block; - flex: 1 1 100%; - width: auto; - max-width: space($visual-max-width); - height: auto; - max-height: space(math.div($visual-max-width, 4) * 3); - margin: auto 0; -} - -.input-config__visual-aspect { - position: relative; - width: 100%; - margin: auto 0; - padding-bottom: 75%; - background-color: $color-bg-shadow; -} - -.input-config__visual { - display: flex; - position: absolute; - top: space(16); - right: space(16); - bottom: space(16); - left: space(16); - flex-direction: column; - border-radius: space(108); - background-color: $color-white-a5; -} - -.input-config__visual-half { - display: flex; - position: relative; - flex: 1 1 100%; - flex-direction: row; - padding: 6%; - - &--bottom { - align-items: flex-end; - justify-content: space-between; - } -} - -.input-config__visual-quarter-left { - display: flex; - flex: 1 1 50%; - align-items: flex-start; - justify-content: flex-start; - width: auto; -} - -.input-config__visual-quarter-right { - display: flex; - flex: 1 1 100%; - align-items: flex-start; - justify-content: flex-end; -} - -.input-config__visual-stick-wrapper { - display: flex; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - align-items: center; - justify-content: center; -} - -.input-viz { - @include trans-colors-opa; - display: flex; - position: relative; - align-items: center; - justify-content: center; - - > svg:not(.input-viz__dpad-arrow) { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - } - - &__mappings div { - @extend %prompt-font-sm; - } -} - -$all-inputs: A, - B, - Z, - START, - DPAD_UP, - DPAD_DOWN, - DPAD_LEFT, - DPAD_RIGHT, - L, - R, - C_UP, - C_DOWN, - C_LEFT, - C_RIGHT, - X_AXIS_NEG, - X_AXIS_POS, - Y_AXIS_NEG, - Y_AXIS_POS; - -// Show default state while no inputs are active -[cur-input="NONE"] .input-viz[visual-input] { - opacity: 1; -} - -@each $inp in $all-inputs { - .input-viz[visual-input~="#{$inp}"] { - opacity: 0.25; - - [cur-input="#{$inp}"] & { - opacity: 1.0; - } - } -} - -@mixin set-sizes($sz) { - width: space($sz); - height: space($sz); - - > svg { - width: space($sz); - height: space($sz); - } -} - -.input-viz__button { - @include set-color($color-text); - - &--sm { - @include set-sizes(64); - } - - &--md { - @include set-sizes(76); - } - - &--lg { - @include set-sizes(84); - } - - &--C { - @include set-svgs-color($color-warning); - } - - &--A { - @include set-svgs-color($color-a); - margin-top: auto; - } - - &--B { - @include set-svgs-color($color-success); - } - - &--Start { - @include set-svgs-color($color-error); - } -} - -.input-viz__Z { - @include set-svgs-color($color-warning); - @include set-sizes(136); -} - -$dpad-size: 192; - -.input-viz.input-viz__dpad { - @include set-svgs-color($color-text); - @include set-sizes($dpad-size); - position: relative; -} - -$stick-size: 200; - -.input-config__visual-stick { - display: flex; - position: relative; - align-items: center; - justify-content: center; - width: space($stick-size); - height: space($stick-size); - border-radius: space(math.div($stick-size, 2)); - background-color: $color-white-a5; -} - -.input-viz__dpad-split, -.input-viz__stick-split { - @include inset-block(0); - display: flex; - width: 100%; - height: 100%; - - &--vertical { - flex-direction: column; - align-items: center; - justify-content: space-between; - } - - &--horizontal { - flex-direction: row; - align-items: center; - justify-content: space-between; - } - - > div { - display: flex; - flex: 1 1 100%; - flex-direction: row; - align-items: center; - justify-content: center; - } -} - -.input-viz__dpad-split > div { - width: space(math.div($dpad-size, 3)); - height: space(math.div($dpad-size, 3)); -} - -.input-viz__stick-split > div { - width: space(math.div($stick-size, 3)); - height: space(math.div($stick-size, 3)); -} - -.input-viz__dpad-arrow { - $edge-dist: space(4); - position: absolute; - width: space(60); - height: space(60); - - &--up { - top: $edge-dist; - margin: 0 auto; - } - - &--down { - bottom: $edge-dist; - margin: 0 auto; - transform: rotate(180deg); - } - - &--left { - left: $edge-dist; - margin: auto 0; - transform: rotate(-90deg); - } - - &--right { - right: $edge-dist; - margin: auto 0; - transform: rotate(90deg); - } -} - -.input-viz__R { - @include set-svgs-color($color-white); - @include set-sizes(96); -} - -.input-viz__L { - @include set-svgs-color($color-secondary); - @include set-sizes(136); -} - -.input-config__c-buttons { - position: relative; - width: space(76 + 76 + 56); - height: space(76 + 56); - - &-lr, &-du { - display: flex; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - } - - &-lr { - flex-direction: row; - align-items: flex-start; - justify-content: space-between; - } - - &-du { - flex-direction: column-reverse; - align-items: center; - justify-content: space-between; - } - - .input-viz { - &[visual-input="C_UP"] { - margin-top: space(-32); - } - } -} - -.input-config__main-buttons { - display: flex; - position: relative; - flex-direction: row; - justify-content: space-between; - width: space(268); - // WORKAROUND FIX: prevents RMLui assert error - min-width: 1dp; - height: space(128); - margin-right: space(10); -} diff --git a/assets/scss/styles/components/Launcher.scss b/assets/scss/styles/components/Launcher.scss deleted file mode 100644 index 89e4fce..0000000 --- a/assets/scss/styles/components/Launcher.scss +++ /dev/null @@ -1,108 +0,0 @@ - -// TODO: Affect all elements with launcher fade-in -// @keyframes fade-launcher-in { -// 0% { -// opacity: 0; -// } -// to { -// opacity: 1; -// } -// } - -.launcher { - display: block; - position: relative; - flex-direction: row; - justify-content: space-between; - width: 100%; - height: 100%; - background-color: $color-background-1; -} - -.launcher__vertical-split { - display: flex; - position: absolute; - top: 0; - right: 50%; - bottom: 0; - left: 0; - flex-direction: column; - align-items: flex-start; - justify-content: space-between; - - &--right { - right: 0; - left: 50%; - align-items: flex-end; - } -} - - -@keyframes slide-mm-bg-over { - 0% { - transform: translateX(space(100)); - } - - 100% { - transform: translateX(space(0)); - } -} - -.launcher__background-wrapper { - display: flex; - position: absolute; - top: -55vw; - right: -100%; - bottom: -50vw; - left: -70vw; - align-items: center; - justify-content: flex-start; - transform: translateX(space(0)); - animation: 25s cubic-out 1 slide-mm-bg-over; -} - -@keyframes fade-mm-in { - 0% { - opacity: 0; - } - - 100% { - opacity: 0.1; - } -} - -.launcher__background-mm { - position: absolute; - top: 0; - bottom: 0; - left: 0; - width: auto; - height: 100%; - animation: 2.5s cubic-in-out 1 fade-mm-in; - opacity: 0.1; -} - -.launcher__title-quadrant { - flex: 1 1 auto; - width: auto; - height: auto; - padding-top: space(96); - padding-left: space(96); - - &--right { - padding-right: space(96); - padding-left: 0; - } -} - -.launcher__content-quadrant { - display: flex; - position: relative; - flex: 1 1 100%; - flex-direction: column; - align-items: flex-start; - justify-content: flex-end; - width: 100%; - height: auto; - padding: space(32); -} diff --git a/assets/scss/styles/components/MenuListItem.scss b/assets/scss/styles/components/MenuListItem.scss deleted file mode 100644 index 657f0ef..0000000 --- a/assets/scss/styles/components/MenuListItem.scss +++ /dev/null @@ -1,71 +0,0 @@ - -/* - Example layout: - - - Variants: - .menu-list-item--right (align to right side) - - Optional: - -
Coming Soon™
- -*/ - -.menu-list-item { - @include set-color($color-text-dim); - display: flex; - flex-direction: row; - align-items: center; - width: 100%; - height: auto; - padding: space(16); - border-radius: $border-radius-sm; - background-color: rgba(0, 0, 0, 0); - cursor: pointer; - - &--right { - flex-direction: row-reverse; - align-content: flex-end; - - .menu-list-item__bullet { - margin-left: space(12); - opacity: 1; - } - - &.menu-list-item:focus:not(:disabled, [disabled]), - &.menu-list-item:hover:not(:disabled, [disabled]) { - decorator: $primary-rl-fade; - } - } - - &:focus:not(:disabled, [disabled]), - &:hover:not(:disabled, [disabled]) { - @include set-color($color-primary); - decorator: $primary-lr-fade; - - .menu-list-item__bullet { - opacity: 1; - } - } - - &:not(:disabled, [disabled]) { - @extend %nav-all; - } - - &:disabled, &[disabled] { - opacity: 0.5; - tab-index: none; - cursor: default; - } - -} - -.menu-list-item__label { - @extend %label-lg; -} - -.menu-list-item__bullet { - margin-right: space(12); - opacity: 0; -} diff --git a/assets/scss/styles/components/Prompt.scss b/assets/scss/styles/components/Prompt.scss deleted file mode 100644 index d7b4a1d..0000000 --- a/assets/scss/styles/components/Prompt.scss +++ /dev/null @@ -1,70 +0,0 @@ -@use 'sass:math'; - -$prompt-space: 24; - -.prompt { - &__overlay { - background-color: $color-bg-overlay; - pointer-events: auto; - } - - &__overlay, - &__content-wrapper { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - } - - &__content-wrapper { - display: flex; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - align-items: center; - justify-content: center; - } - - &__content { - display: flex; - position: relative; - flex: 1 1 100%; - flex-direction: column; - width: 100%; - max-width: space(700); - height: auto; - margin: auto; - border-width: $border-width-thickness; - border-radius: $border-radius-modal; - border-color: $color-border; - background: $color-modal-overlay; - - h3, p { - margin: space($prompt-space); - } - - p { - margin-top: 0; - } - } - - &__controls { - display: flex; - flex-direction: row; - justify-content: center; - padding: space($prompt-space) space(math.div($prompt-space, 2)); - border-top-width: $border-width-thickness; - border-top-color: $color-border-soft; - - .button { - min-width: space(math.div(700, 3)); - margin: 0 space(math.div($prompt-space, 2)); - text-align: center; - nav-up: none; - nav-down: none; - } - } -} diff --git a/assets/scss/styles/components/SubtitleTitle.scss b/assets/scss/styles/components/SubtitleTitle.scss deleted file mode 100644 index c04def6..0000000 --- a/assets/scss/styles/components/SubtitleTitle.scss +++ /dev/null @@ -1,71 +0,0 @@ -/* - Example layout: - - - - Variants: - .subtitle-title--right (align to right side) - - Optional: - -
Coming Soon™
- -*/ - -.subtitle-title { - display: block; - position: relative; - flex-direction: column; - align-content: flex-start; - align-items: flex-start; - width: auto; - height: auto; - padding: 0; - background-color: rgba(0, 0, 0, 0); - color: $color-text-dim; - text-align: left; - cursor: pointer; - - &--right { - align-content: flex-end; - } - - &--right, &--right > * { - text-align: right; - } - - &[selected] { - color: $color-text; - cursor: default; - } - - &:focus:not(:disabled, [disabled]), - &:hover:not(:disabled, [disabled], [selected]) { - color: $color-primary; - } - - &:not(:disabled, [disabled]) { - @extend %nav-all; - } - - &:disabled, &[disabled] { - opacity: 0.5; - cursor: default; - tab-index: none; - } - - h3 { - margin-bottom: space(6); - } - - h1 { - margin-top: space(6); - } - - &__disclaimer { - @extend %label-sm; - - margin-top: space(16); - } -} diff --git a/assets/scss/styles/components/Tabs.scss b/assets/scss/styles/components/Tabs.scss deleted file mode 100644 index 53f09dc..0000000 --- a/assets/scss/styles/components/Tabs.scss +++ /dev/null @@ -1,62 +0,0 @@ -/* - Example: - -
Graphics
-
-
-*/ - -.tabs tabs { - display: flex; - flex-direction: row; - align-items: center; - justify-content: flex-start; -} - -.tab { - @extend %nav-all; - @extend %header-3; - display: block; - position: relative; - margin: 0; - padding: space(20) space(24); - opacity: 0.9; - background-color: rgba(0,0,0,0); - color: $color-text-inactive; - - &:selected { - color: $color-text; - - .tab__indicator { - background-color: $color-border-solid; - } - - &:hover { - cursor: default; - } - } - - .rmlui-window:not([mouse-active]) &:focus { - transition: none; - animation: $focus-anim-border; - - &:selected .tab__indicator { - animation: $focus-anim-bg; - } - } - - &:focus, &:hover { - opacity: 1; - color: $color-text; - cursor: pointer; - } -} - -.tab__indicator { - position: absolute; - right: 0; - bottom: 2dp; - left: 0; - height: 2dp; - background-color: rgba(0, 0, 0, 0); -} diff --git a/assets/scss/styles/components/Toggle.scss b/assets/scss/styles/components/Toggle.scss deleted file mode 100644 index 065f92d..0000000 --- a/assets/scss/styles/components/Toggle.scss +++ /dev/null @@ -1,97 +0,0 @@ -@use "sass:math"; - -$toggle-width: 162; -$toggle-height: 72; - -$toggle-floater-width: 80; -$toggle-floater-height: 64; -$toggle-floater-margin: 4; -$toggle-checked-left-offset: $toggle-width - $toggle-floater-margin - $toggle-floater-width; - -.toggle { - @extend %nav-all; - @include trans-colors-opa; - display: flex; - position: relative; - flex-direction: row; - align-items: center; - justify-content: space-between; - width: space($toggle-width); - height: space($toggle-height); - border-radius: space(math.div($toggle-height, 2)); - opacity: 0.9; - background: $color-transparent; - cursor: pointer; - - &:hover, &:focus-visible, &:focus { - opacity: 1; - background-color: $color-secondary-a30; - } - - &:active { - opacity: 1; - background-color: $color-secondary-a5; - } - - .toggle__border { - @include inner-border-block($color-secondary-l); - border-radius: space(math.div($toggle-height, 2)); - } - - .toggle__floater { - position: absolute; - top: 50%; - left: space($toggle-floater-margin); - width: space($toggle-floater-width); - height: space($toggle-floater-height); - transform: translateY(-50%); - border-radius: space(math.div($toggle-floater-height, 2)); - background: $color-secondary-d; - } - - &--checked { - .toggle__floater { - left: space($toggle-checked-left-offset); - } - - .toggle__icon { - &.toggle__icon--left { - opacity: 0.9; - color: $color-secondary-l; - } - - &.toggle__icon--right { - opacity: 1.0; - color: $color-text; - } - } - } -} - -.toggle__icons { - display: flex; - position: absolute; - top: 50%; - right: space(16); - left: space(16); - align-items: center; - justify-content: space-between; - height: space(56); - transform: translateY(-50%); -} - -.toggle__icon { - @extend %prompt-font-lg; - @include trans-colors; - display: flex; - align-items: center; - justify-content: center; - width: space(56); - height: space(56); - color: $color-text; - - &--right { - opacity: 1; - color: $color-secondary-l; - } -} diff --git a/assets/scss/styles/components/_components.scss b/assets/scss/styles/components/_components.scss deleted file mode 100644 index b4a936e..0000000 --- a/assets/scss/styles/components/_components.scss +++ /dev/null @@ -1,16 +0,0 @@ -@import "./CenteredPage"; -@import "./ControlOption"; -@import "./Tabs"; -@import "./Config"; -@import "./ConfigGroup"; -@import "./ConfigOption"; -@import "./ConfigDescription"; -@import "./InputConfig"; -@import "./Button"; -@import "./IconButton"; -@import "./Launcher"; -@import "./MenuListItem"; -@import "./SubtitleTitle"; -@import "./Toggle"; -@import "./BottomLeft"; -@import "./Prompt"; diff --git a/assets/scss/styles/functions/_spacing.scss b/assets/scss/styles/functions/_spacing.scss deleted file mode 100644 index 437a7ac..0000000 --- a/assets/scss/styles/functions/_spacing.scss +++ /dev/null @@ -1,4 +0,0 @@ - -@function space($amt) { - @return #{$amt}dp; -} diff --git a/assets/scss/styles/global.scss b/assets/scss/styles/global.scss deleted file mode 100644 index 2e98925..0000000 --- a/assets/scss/styles/global.scss +++ /dev/null @@ -1,132 +0,0 @@ -@import "./base"; -@import "./globals/old"; -@import "./globals/scrollbars"; -@import "./components/components"; -@import "./pages/pages"; - -$font-size: 20dp; - -body -{ - @extend %body; - box-sizing: border-box; - color: $color-text; - font-family: $font-stack; -} - -.rmlui-window { - opacity: 1; - - &--hidden { - opacity: 0; - } - - &:not([mouse-active]) { - pointer-events: none; - } -} - -.nav-vert { - @extend %nav-vert; -} - -.nav-horiz { - @extend %nav-horiz; -} - -.nav-dir { - @extend %nav-dir; -} - -.nav-foc { - @extend %nav-foc; -} - -.nav-all { - @extend %nav-all; -} - -*, *:before, *:after { - box-sizing: border-box; -} - -h1 { - @extend %header-1; -} - -h2 { - @extend %header-2; -} - -h3 { - @extend %header-3; -} - -.label-lg { - @extend %label-lg; -} - -.label-md { - @extend %label-md; -} - -.label-sm { - @extend %label-sm; -} - -.prompt-font { - @extend %prompt-font; -} - -.prompt-font-sm { - @extend %prompt-font-sm; -} - -button { - background-color: $color-primary-d; -} - -@keyframes blue-pulse { - 0% { - color: $color-secondary; - } - - 50% { - color: $color-secondary-l; - } - - 100% { - color: $color-secondary; - } -} - -@keyframes blue-pulse-with-border { - 0% { - border-color: $color-secondary; - color: $color-secondary; - } - - 50% { - border-color: $color-secondary-l; - color: $color-secondary-l; - } - - 100% { - border-color: $color-secondary; - color: $color-secondary; - } -} - -@keyframes blue-pulse-background { - 0% { - background-color: $color-secondary; - } - - 50% { - background-color: $color-secondary-l; - } - - 100% { - background-color: $color-secondary; - } -} diff --git a/assets/scss/styles/globals/_old.scss b/assets/scss/styles/globals/_old.scss deleted file mode 100644 index 20351d9..0000000 --- a/assets/scss/styles/globals/_old.scss +++ /dev/null @@ -1,212 +0,0 @@ -/* stylelint-disable color-no-hex */ -/* stylelint-disable selector-max-id */ - -* { - box-sizing:border-box; -} - -hr { - display:block; - padding:1.5dp; - background: $color-background-1; -} - -body { - color: #fff; - font-family: chiaro; - font-size: 20dp; - font-style: normal; - font-weight: normal -} - -/* div { - focus:none; - tab-index:none; -} */ - -div#window { - position: relative; - box-sizing: border-box; - width: 100%; - height: 100%; - border-color: $color-border; - background-color: $color-background-2; -} - -div#content { - z-index: 2; - width: auto; - height: 100%; - overflow: hidden auto; - text-align: center -} - -p { - text-align: left; -} - -input.submit { - margin-left: 0 -} - - -input.text, -input.password { - box-sizing: border-box; - height: 31dp; - padding: 11dp 10dp 0; - text-align: left; - cursor: text -} - -textarea { - padding: 14dp 12dp 10dp; - text-align: left; - cursor: text -} - -input.text, -input.password, -select, -textarea { - /* color: #333; */ - - /* font-size: 13dp */ - height: auto; -} - -table input.text { - box-sizing: border-box; - width: 100%; - height: auto; - - /* height: 18dp; */ - margin: 0; - border-width: $border-width-thickness; - border-color: #000; - background-color: #fff; - font-size: 15dp; - - /* padding: 0 5dp; */ - line-height: 1; - decorator: none; - - /* vertical-align: center; */ -} - -select { - // display: inline-block; - // /* width: 175dp; */ - // /* height: 37dp; */ - // /* height: auto; */ - // text-align: left; - // box-sizing:border-box; - // /* padding: 4dp; */ - // vertical-align: center; - // padding: 4dp; - // border-radius: 5dp; - // background-color: rgb(120, 120, 120); - // width: 100%; -} -// select { -// @extend %body; -// display: flex; -// align-items: center; -// justify-content: flex-start; -// box-sizing: border-box; -// padding: space(16); -// flex: 1 1 100%; -// // width: auto; -// height: space(1000); -// border-radius: $border-radius-lg; -// background-color: $color-white-a20; -// } - -// select selectvalue { -// height: auto; -// /* padding: 4dp; */ -// /* margin-right: 30dp; */ -// /* height: 25dp; */ -// /* padding: 4dp; */ -// /* decorator: image(selectvalue) */ -// } - -// select:hover selectvalue { -// /* margin-right: 30dp; */ -// /* height: 25dp; */ -// /* padding: 4dp; */ -// background-color: rgb(150, 150, 150); -// /* decorator: image(selectvalue) */ -// } - -// select selectarrow { -// /* width: 30dp; */ -// /* height: 37dp; */ -// /* decorator: image(selectarrow) */ -// /* background-color: black; */ -// /* appearance: none; */ -// } - -// select:hover selectarrow { -// /* decorator: image(selectarrow-hover) */ -// } - -// select:active selectarrow, -// select selectarrow:checked { -// /* decorator: image(selectarrow-active) */ -// } - -// select selectbox { -// /* margin-left: 1dp; */ -// /* margin-top: -7dp; */ -// /* margin-bottom: -10dp; */ -// /* width: 162dp; */ -// /* padding: 1dp 4dp 4dp 4dp */ -// } - -// select selectbox, -// tbody { -// background-color: rgb(120,120,120); -// /* decorator: tiled-box(selectbox-tl, selectbox-t, selectbox-tr, selectbox-l, selectbox-c, auto, selectbox-bl, selectbox-b, selectbox-br) */ -// } - -// select selectbox option { -// width: auto; -// background-color: rgb(120, 120, 120) -// } - -// select selectbox option:nth-child(even), -// tr:nth-child(even) { -// background-color: rgb(100, 100, 100) -// } - -// select selectbox option:checked { -// font-weight:bold; -// color:rgb(255,255,255); -// } - -// select selectbox option:hover { -// background: rgb(150,150,150) -// } - -input.radio { - flex: 0; - width:0dp; - nav-up:auto; - nav-right:auto; - nav-down:auto; - nav-left:auto; - tab-index:auto; - focus:auto; -} - -input.checkbox { - width: space(20); - height: space(20); - nav-up:auto; - nav-right:auto; - nav-down:auto; - nav-left:auto; - tab-index:auto; - focus:auto; -} diff --git a/assets/scss/styles/globals/_scrollbars.scss b/assets/scss/styles/globals/_scrollbars.scss deleted file mode 100644 index 375c164..0000000 --- a/assets/scss/styles/globals/_scrollbars.scss +++ /dev/null @@ -1,52 +0,0 @@ -@use 'sass:math'; - -$scrollbar-width: 12; - -@mixin _set-scroll-size($size-key) { - #{$size-key}: space($scrollbar-width); - - slidertrack { - #{$size-key}: space($scrollbar-width); - } - - sliderbar { - #{$size-key}: space($scrollbar-width); - } -} - -scrollbarvertical,scrollbarhorizontal { - margin: 0; - border: 0; - - slidertrack { - background: $color-primary-l; - opacity: 0.05; - } - - sliderbar { - border-radius: space(math.div($scrollbar-width, 2) - 1); - background: $color-primary-l; - opacity: 0.1; - - &:hover:not(:active) { - opacity: 0.2; - } - - &:active { - opacity: 0.3; - } - } - - sliderarrowdec, sliderarrowinc { - width: 0; - height: 0; - } -} - -scrollbarvertical { - @include _set-scroll-size(width); -} - -scrollbarhorizontal { - @include _set-scroll-size(height); -} diff --git a/assets/scss/styles/mixins/_helpers.scss b/assets/scss/styles/mixins/_helpers.scss deleted file mode 100644 index 9759630..0000000 --- a/assets/scss/styles/mixins/_helpers.scss +++ /dev/null @@ -1,39 +0,0 @@ - -%nav-vert { - nav-up: auto; - nav-down: auto; -} - -%nav-horiz { - nav-right: auto; - nav-left: auto; -} - -%nav-dir { - @extend %nav-vert; - @extend %nav-horiz; -} - -%nav-foc { - focus: auto; - tab-index: auto; -} - -%nav-all { - @extend %nav-dir; - @extend %nav-foc; -} - -@mixin set-svgs-color($col) { - svg { - image-color: $col; - } -} - -/* -@include set-color(COLOR); -*/ -@mixin set-color($col) { - @include set-svgs-color($col); - color: $col; -} diff --git a/assets/scss/styles/mixins/_transitions.scss b/assets/scss/styles/mixins/_transitions.scss deleted file mode 100644 index 06c8677..0000000 --- a/assets/scss/styles/mixins/_transitions.scss +++ /dev/null @@ -1,36 +0,0 @@ - -/* -@include trans-colors; -*/ -@mixin trans-colors { - transition: color $transition-quick, background-color $transition-quick; -} - -/* -@include trans-colors-opa; -*/ -@mixin trans-colors-opa { - transition: color $transition-quick, background-color $transition-quick, opacity $transition-quick; -} - -/* -@include trans-colors-svg; -*/ -@mixin trans-colors-svg { - transition: color $transition-quick, background-color $transition-quick, opacity $transition-quick; - - svg { - transition: image-color $transition-quick, background-color $transition-quick; - } -} - -/* -@include trans-colors-border; -*/ -@mixin trans-colors-border { - transition: color $transition-quick, background-color $transition-quick, opacity $transition-quick, border-color $transition-quick; - - svg { - transition: image-color $transition-quick, background-color $transition-quick; - } -} diff --git a/assets/scss/styles/mixins/_typography.scss b/assets/scss/styles/mixins/_typography.scss deleted file mode 100644 index a638b1a..0000000 --- a/assets/scss/styles/mixins/_typography.scss +++ /dev/null @@ -1,83 +0,0 @@ - -$font-stack: chiaro; - -@mixin set-font-sizing($sz, $spacing) { - // font-family: $font-stack; - $sz-add: $sz + 4; - font-size: space($sz-add); - letter-spacing: space($sz-add * $spacing); - line-height: space($sz-add); -} - -%header-1 { - @include set-font-sizing(64, 0.07); - font-style: normal; - font-weight: 700; -} - -%header-2 { - @include set-font-sizing(48, 0.07); - font-style: normal; - font-weight: 700; -} - -%header-3 { - @include set-font-sizing(32, 0.07); - font-style: normal; - font-weight: 700; -} - -%label-lg { - @include set-font-sizing(32, 0.11); - font-style: normal; - font-weight: 700; -} - -%label-md { - @include set-font-sizing(24, 0.11); - font-style: normal; - font-weight: 700; -} - -%label-sm { - @include set-font-sizing(16, 0.14); - font-style: normal; - font-weight: 700; - text-transform: uppercase; -} - -%label-xs { - @include set-font-sizing(14, 0.14); - font-style: normal; - font-weight: 400; -} - -%body { - @include set-font-sizing(16, 0.0); - font-style: normal; - font-weight: 400; -} - -%prompt-font-lg { - font-family: promptfont; - font-size: space(56); - font-style: normal; - font-weight: 400; - line-height: space(56); -} - -%prompt-font { - font-family: promptfont; - font-size: space(40); - font-style: normal; - font-weight: 400; - line-height: space(40); -} - -%prompt-font-sm { - font-family: promptfont; - font-size: space(32); - font-style: normal; - font-weight: 400; - line-height: space(32); -} diff --git a/assets/scss/styles/pages/_pages.scss b/assets/scss/styles/pages/_pages.scss deleted file mode 100644 index 16e2e18..0000000 --- a/assets/scss/styles/pages/_pages.scss +++ /dev/null @@ -1 +0,0 @@ -@import "./config/config"; diff --git a/assets/scss/styles/pages/config/_config.scss b/assets/scss/styles/pages/config/_config.scss deleted file mode 100644 index c4e69bc..0000000 --- a/assets/scss/styles/pages/config/_config.scss +++ /dev/null @@ -1 +0,0 @@ -@import "./debug"; diff --git a/assets/scss/styles/pages/config/debug.scss b/assets/scss/styles/pages/config/debug.scss deleted file mode 100644 index 997bfc0..0000000 --- a/assets/scss/styles/pages/config/debug.scss +++ /dev/null @@ -1,178 +0,0 @@ - -.config-debug { - display: block; - position: relative; - width: 100%; - max-height: 100%; - padding: space(8); -} - -.config-debug__scroll { - display: block; - position: relative; - width: 100%; - max-height: 100%; - overflow-y: auto; -} - -.config-debug-option { - @include set-color($color-text-dim); - @include trans-colors-svg; - @include border-bottom($color-border-soft); - display: block; - position: relative; - flex-direction: column; - width: 100%; - height: auto; - padding: space(12) space(4); - background-color: rgba(0, 0, 0, 0); - - &:focus:not(:disabled, [disabled]), - &:focus-visible:not(:disabled, [disabled]), - &:hover:not(:disabled, [disabled]) { - @include set-color($color-text); - background-color: $color-bg-overlay; - } - - &:disabled, &[disabled] { - opacity: 0.5; - } - - .icon-button { - margin-left: space(8); - } -} - -.config-debug__option-split { - display: flex; - flex-direction: row; - align-items: center; - justify-content: space-between; -} - -.config-debug-option__label { - @extend %label-md; - display: flex; - flex-direction: row; - align-items: center; - justify-content: flex-start; - padding: space(4) space(16) space(12); - width: auto; - height: auto; - white-space: nowrap; -} - -.config-debug__option-controls { - display: block; - position: relative; - flex: 1 1 auto; - height: auto; - width: auto; - max-width: space(800); - padding: 0 space(12); -} - -.config-debug__option-trigger { - flex: 1 1 auto; -} - -.config-debug__select-wrapper { - display: flex; - position: relative; - flex-direction: row; - align-items: center; - justify-content: flex-start; - flex: 1 1 100%; - width: auto; - max-width: space(800); - height: auto; - padding: space(4); - - .config-debug__select-label { - @extend %label-sm; - padding-right: space(16); - flex: auto; - width: space(196); - - > div { - display: inline; - width: auto; - height: auto; - } - } - - input { - @extend %body; - @extend %nav-all; - display: block; - position: relative; - box-sizing: border-box; - padding: 0; - flex: 1 1 100%; - width: auto; - height: space(20); - margin: auto 0; - } - - select { - @extend %body; - @extend %nav-all; - @include trans-colors-border; - @include border($color-white-a50); - display: block; - position: relative; - box-sizing: border-box; - padding: 0; - flex: 1 1 100%; - width: auto; - height: space(48); - border-radius: $border-radius-md; - background-color: $color-white-a5; - cursor: pointer; - - align-items: center; - justify-content: flex-start; - padding: space(14); - - &:hover, &:focus { - @include border($color-white-a80); - background-color: $color-white-a20; - } - - selectvalue { - display: inline; - margin: auto 0; - height: auto; - } - - selectbox { - @include border($color-white-a80); - background-color: $color-background-3; - padding: space(4) 0; - margin-top: space(2); - border-radius: $border-radius-md; - - option { - @extend %nav-all; - @include trans-colors; - padding: space(8) space(12); - background-color: $color-transparent; - color: $color-text-dim; - font-weight: 400; - - &:hover, &:focus { - background-color: $color-white-a20; - } - - &:hover:not(:checked) { - cursor: pointer; - } - - &:checked { - color: $color-white; - background-color: $color-white-a5; - } - } - } - } -} diff --git a/assets/scss/styles/vars/_animations.scss b/assets/scss/styles/vars/_animations.scss deleted file mode 100644 index 9315f4d..0000000 --- a/assets/scss/styles/vars/_animations.scss +++ /dev/null @@ -1,4 +0,0 @@ - -$focus-anim: blue-pulse-with-border 0.75s infinite; -$focus-anim-border: blue-pulse 0.75s infinite; -$focus-anim-bg: blue-pulse-background 0.75s infinite; diff --git a/assets/scss/styles/vars/_borders.scss b/assets/scss/styles/vars/_borders.scss deleted file mode 100644 index 5088285..0000000 --- a/assets/scss/styles/vars/_borders.scss +++ /dev/null @@ -1,42 +0,0 @@ - -$border-radius-sm: 8dp; -$border-radius-md: 12dp; -// modals/pages -$border-radius-lg: 16dp; - -$border-radius-modal: $border-radius-lg; - -$border-width-thickness-num: 1.1; -// $border-width-thickness-num: 1.5; -$border-width-thickness: space($border-width-thickness-num); - -@mixin border($col: $color-border) { - border-width: $border-width-thickness; - border-color: $col; -} - -@mixin border-top($col: $color-border) { - border-top-width: $border-width-thickness; - border-top-color: $col; -} - -@mixin border-bottom($col: $color-border) { - border-bottom-width: $border-width-thickness; - border-bottom-color: $col; -} - -@mixin inset-block($inset-amt) { - position: absolute; - top: $inset-amt; - right: $inset-amt; - bottom: $inset-amt; - left: $inset-amt; -} - -// add this to a child of the container that needs a border. -// parent must have `position: relative` -@mixin inner-border-block($col: $color-border) { - @include inset-block($border-width-thickness); - @include border($col); - display: block; -} diff --git a/assets/scss/styles/vars/_colors.scss b/assets/scss/styles/vars/_colors.scss deleted file mode 100644 index 0b5cfd7..0000000 --- a/assets/scss/styles/vars/_colors.scss +++ /dev/null @@ -1,71 +0,0 @@ -/* stylelint-disable color-no-hex, color-hex-length */ - -$color-background-1: #08070D; -$color-background-2: #121018; -$color-background-3: #191622; -$color-bg-overlay: rgba(190, 184, 219, 0.1000); -$color-modal-overlay: rgba(8, 7, 13, 0.9000); -$color-bg-shadow: rgba(0, 0, 0, 0.3500); -$color-bg-shadow-2: rgba(8, 7, 13, 0.7200); -$color-text: #F2F2F2; -$color-text-dim: #CCCCCC; -$color-text-inactive: rgba(255, 255, 255, 0.6000); -$color-primary: #B97DF2; -$color-primary-l: #DABAF7; -$color-primary-d: #7A2AC6; -$color-primary-a5: rgba(185, 125, 242, 0.0500); -$color-primary-a20: rgba(185, 125, 242, 0.2000); -$color-primary-a30: rgba(185, 125, 242, 0.3000); -$color-primary-a50: rgba(185, 125, 242, 0.5000); -$color-primary-a80: rgba(185, 125, 242, 0.8000); -$color-secondary: #17D6E8; -$color-secondary-l: #A2EFF6; -$color-secondary-d: #25A1AD; -$color-secondary-a5: rgba(23, 214, 232, 0.0500); -$color-secondary-a20: rgba(23, 214, 232, 0.2000); -$color-secondary-a30: rgba(23, 214, 232, 0.3000); -$color-secondary-a50: rgba(23, 214, 232, 0.5000); -$color-secondary-a80: rgba(23, 214, 232, 0.8000); -$color-warning: #E9CD35; -$color-warning-l: #F9E57C; -$color-warning-d: #C5AA16; -$color-warning-a5: rgba(233, 205, 53, 0.0500); -$color-warning-a20: rgba(233, 205, 53, 0.2000); -$color-warning-a30: rgba(233, 205, 53, 0.3000); -$color-warning-a50: rgba(233, 205, 53, 0.5000); -$color-warning-a80: rgba(233, 205, 53, 0.8000); -$color-error: #F86039; -$color-error-l: #FE8667; -$color-error-d: #B23919; -$color-error-a5: rgba(248, 96, 57, 0.0500); -$color-error-a20: rgba(248, 96, 57, 0.2000); -$color-error-a30: rgba(248, 96, 57, 0.3000); -$color-error-a50: rgba(248, 96, 57, 0.5000); -$color-error-a80: rgba(248, 96, 57, 0.8000); -$color-success: #45D043; -$color-success-l: #AAEAA9; -$color-success-d: #2CA72A; -$color-success-a5: rgba(69, 208, 67, 0.0500); -$color-success-a20: rgba(69, 208, 67, 0.2000); -$color-success-a30: rgba(69, 208, 67, 0.3000); -$color-success-a50: rgba(69, 208, 67, 0.5000); -$color-success-a80: rgba(69, 208, 67, 0.8000); -$color-border: rgba(255, 255, 255, 0.2000); -$color-border-soft: rgba(255, 255, 255, 0.1000); -$color-border-hard: rgba(255, 255, 255, 0.3000); -$color-border-solid: rgba(255, 255, 255, 0.6000); -$color-transparent: rgba(0, 0, 0, 0.0000); -$color-a: #3333FF; -$color-a-l: #B2B2FF; -$color-a-d: #2020AC; -$color-a-a5: rgba(51, 51, 255, 0.0500); -$color-a-a20: rgba(51, 51, 255, 0.2000); -$color-a-a30: rgba(51, 51, 255, 0.3000); -$color-a-a50: rgba(51, 51, 255, 0.5000); -$color-a-a80: rgba(51, 51, 255, 0.8000); -$color-white: #FFFFFF; -$color-white-a5: rgba(255, 255, 255, 0.0500); -$color-white-a20: rgba(255, 255, 255, 0.2000); -$color-white-a30: rgba(255, 255, 255, 0.3000); -$color-white-a50: rgba(255, 255, 255, 0.5000); -$color-white-a80: rgba(255, 255, 255, 0.8000); diff --git a/assets/scss/styles/vars/_gradients.scss b/assets/scss/styles/vars/_gradients.scss deleted file mode 100644 index 1cdebd5..0000000 --- a/assets/scss/styles/vars/_gradients.scss +++ /dev/null @@ -1,7 +0,0 @@ - -// Not supported yet, need to use decorator: gradient atm -// $primary-lr-fade: linear-gradient(90deg, rgba($color-primary-l, 0.08) 0%, rgba($color-primary-l, 0.00) 100%); -// $primary-rl-fade: linear-gradient(90deg, rgba($color-primary-l, 0.00) 0%, rgba($color-primary-l, 0.08) 100%); - -$primary-lr-fade: horizontal-gradient(#{$color-primary-d}14 #{$color-primary-l}00); -$primary-rl-fade: horizontal-gradient(#{$color-primary-d}00 #{$color-primary-l}14); diff --git a/assets/scss/styles/vars/_spacing.scss b/assets/scss/styles/vars/_spacing.scss deleted file mode 100644 index b61c8e1..0000000 --- a/assets/scss/styles/vars/_spacing.scss +++ /dev/null @@ -1,7 +0,0 @@ -@use 'sass:math'; - -// $page-margin: 32; -$page-margin: 64; -$base-height: 1080; -$base-modal-height: $base-height - ($page-margin * 2); -$base-modal-max-width: math.div($base-modal-height * 16, 9); diff --git a/assets/scss/styles/vars/_transitions.scss b/assets/scss/styles/vars/_transitions.scss deleted file mode 100644 index 0586d88..0000000 --- a/assets/scss/styles/vars/_transitions.scss +++ /dev/null @@ -1,3 +0,0 @@ -// see: lib/RmlUi/Source/Core/PropertyParserAnimation.cpp -$transition-quick: 0.05s linear-in-out; -// $transition-quick: 0.033s linear-in-out; diff --git a/include/recomp_input.h b/include/recomp_input.h deleted file mode 100644 index 8599628..0000000 --- a/include/recomp_input.h +++ /dev/null @@ -1,202 +0,0 @@ -#ifndef __RECOMP_INPUT_H__ -#define __RECOMP_INPUT_H__ - -#include -#include -#include -#include -#include -#include -#include - -#include "ultramodern/input.hpp" - -#include "json/json.hpp" - -namespace recomp { - // x-macros to build input enums and arrays. - // First parameter is the enum name, second parameter is the bit field for the input (or 0 if there is no associated one), third is the readable name. - // TODO refactor this to allow projects to rename these, or get rid of the readable name and leave that up to individual projects to map. - #define DEFINE_N64_BUTTON_INPUTS() \ - DEFINE_INPUT(A, 0x8000, "Action") \ - DEFINE_INPUT(B, 0x4000, "Attack/Cancel") \ - DEFINE_INPUT(Z, 0x2000, "Target") \ - DEFINE_INPUT(START, 0x1000, "Start") \ - DEFINE_INPUT(L, 0x0020, "Toggle map") \ - DEFINE_INPUT(R, 0x0010, "Shield") \ - DEFINE_INPUT(C_UP, 0x0008, "Look/Fairy") \ - DEFINE_INPUT(C_LEFT, 0x0002, "Item 1") \ - DEFINE_INPUT(C_DOWN, 0x0004, "Item 2") \ - DEFINE_INPUT(C_RIGHT, 0x0001, "Item 3") \ - DEFINE_INPUT(DPAD_UP, 0x0800, "Special Item 1") \ - DEFINE_INPUT(DPAD_RIGHT, 0x0100, "Special Item 2") \ - DEFINE_INPUT(DPAD_DOWN, 0x0400, "Special Item 3") \ - DEFINE_INPUT(DPAD_LEFT, 0x0200, "Special Item 4") - - #define DEFINE_N64_AXIS_INPUTS() \ - DEFINE_INPUT(Y_AXIS_POS, 0, "Up") \ - DEFINE_INPUT(Y_AXIS_NEG, 0, "Down") \ - DEFINE_INPUT(X_AXIS_NEG, 0, "Left") \ - DEFINE_INPUT(X_AXIS_POS, 0, "Right") \ - - #define DEFINE_RECOMP_UI_INPUTS() \ - DEFINE_INPUT(TOGGLE_MENU, 0, "Toggle Menu") \ - DEFINE_INPUT(ACCEPT_MENU, 0, "Accept (Menu)") \ - DEFINE_INPUT(APPLY_MENU, 0, "Apply (Menu)") - - #define DEFINE_ALL_INPUTS() \ - DEFINE_N64_BUTTON_INPUTS() \ - DEFINE_N64_AXIS_INPUTS() \ - DEFINE_RECOMP_UI_INPUTS() - - // Enum containing every recomp input. - #define DEFINE_INPUT(name, value, readable) name, - enum class GameInput { - DEFINE_ALL_INPUTS() - - COUNT, - N64_BUTTON_START = A, - N64_BUTTON_COUNT = C_RIGHT - N64_BUTTON_START + 1, - N64_AXIS_START = X_AXIS_NEG, - N64_AXIS_COUNT = Y_AXIS_POS - N64_AXIS_START + 1, - }; - #undef DEFINE_INPUT - - struct InputField { - uint32_t input_type; - int32_t input_id; - std::string to_string() const; - auto operator<=>(const InputField& rhs) const = default; - }; - - void poll_inputs(); - float get_input_analog(const InputField& field); - float get_input_analog(const std::span fields); - bool get_input_digital(const InputField& field); - bool get_input_digital(const std::span fields); - void get_gyro_deltas(float* x, float* y); - void get_mouse_deltas(float* x, float* y); - void get_right_analog(float* x, float* y); - - enum class InputDevice { - Controller, - Keyboard, - COUNT - }; - - void start_scanning_input(InputDevice device); - void stop_scanning_input(); - void finish_scanning_input(InputField scanned_field); - void cancel_scanning_input(); - void config_menu_set_cont_or_kb(bool cont_interacted); - InputField get_scanned_input(); - int get_scanned_input_index(); - - struct DefaultN64Mappings { - std::vector a; - std::vector b; - std::vector l; - std::vector r; - std::vector z; - std::vector start; - - std::vector c_left; - std::vector c_right; - std::vector c_up; - std::vector c_down; - - std::vector dpad_left; - std::vector dpad_right; - std::vector dpad_up; - std::vector dpad_down; - - std::vector analog_left; - std::vector analog_right; - std::vector analog_up; - std::vector analog_down; - - std::vector toggle_menu; - std::vector accept_menu; - std::vector apply_menu; - }; - - inline const std::vector& get_default_mapping_for_input(const DefaultN64Mappings& defaults, const GameInput input) { - static const std::vector empty_input_field{}; - switch (input) { - case GameInput::A: return defaults.a; - case GameInput::B: return defaults.b; - case GameInput::L: return defaults.l; - case GameInput::R: return defaults.r; - case GameInput::Z: return defaults.z; - case GameInput::START: return defaults.start; - case GameInput::C_LEFT: return defaults.c_left; - case GameInput::C_RIGHT: return defaults.c_right; - case GameInput::C_UP: return defaults.c_up; - case GameInput::C_DOWN: return defaults.c_down; - case GameInput::DPAD_LEFT: return defaults.dpad_left; - case GameInput::DPAD_RIGHT: return defaults.dpad_right; - case GameInput::DPAD_UP: return defaults.dpad_up; - case GameInput::DPAD_DOWN: return defaults.dpad_down; - case GameInput::X_AXIS_NEG: return defaults.analog_left; - case GameInput::X_AXIS_POS: return defaults.analog_right; - case GameInput::Y_AXIS_POS: return defaults.analog_up; - case GameInput::Y_AXIS_NEG: return defaults.analog_down; - case GameInput::TOGGLE_MENU: return defaults.toggle_menu; - case GameInput::ACCEPT_MENU: return defaults.accept_menu; - case GameInput::APPLY_MENU: return defaults.apply_menu; - default: return empty_input_field; - } - } - - extern const DefaultN64Mappings default_n64_keyboard_mappings; - extern const DefaultN64Mappings default_n64_controller_mappings; - - constexpr size_t bindings_per_input = 2; - - size_t get_num_inputs(); - const std::string& get_input_name(GameInput input); - const std::string& get_input_enum_name(GameInput input); - GameInput get_input_from_enum_name(const std::string_view name); - InputField& get_input_binding(GameInput input, size_t binding_index, InputDevice device); - void set_input_binding(GameInput input, size_t binding_index, InputDevice device, InputField value); - - bool get_n64_input(int controller_num, uint16_t* buttons_out, float* x_out, float* y_out); - void set_rumble(int controller_num, bool); - void update_rumble(); - void handle_events(); - - ultramodern::input::connected_device_info_t get_connected_device_info(int controller_num); - - // Rumble strength ranges from 0 to 100. - int get_rumble_strength(); - void set_rumble_strength(int strength); - - // Gyro and mouse sensitivities range from 0 to 100. - int get_gyro_sensitivity(); - int get_mouse_sensitivity(); - int get_joystick_deadzone(); - void set_gyro_sensitivity(int strength); - void set_mouse_sensitivity(int strength); - void set_joystick_deadzone(int strength); - void apply_joystick_deadzone(float x_in, float y_in, float* x_out, float* y_out); - void set_right_analog_suppressed(bool suppressed); - - enum class BackgroundInputMode { - On, - Off, - OptionCount - }; - - NLOHMANN_JSON_SERIALIZE_ENUM(recomp::BackgroundInputMode, { - {recomp::BackgroundInputMode::On, "On"}, - {recomp::BackgroundInputMode::Off, "Off"} - }); - - BackgroundInputMode get_background_input_mode(); - void set_background_input_mode(BackgroundInputMode mode); - - bool game_input_disabled(); - bool all_input_disabled(); -} - -#endif diff --git a/include/recomp_ui.h b/include/recomp_ui.h deleted file mode 100644 index f609a52..0000000 --- a/include/recomp_ui.h +++ /dev/null @@ -1,146 +0,0 @@ -#ifndef __RECOMP_UI__ -#define __RECOMP_UI__ - -#include -#include -#include -#include - -// TODO move this file into src/ui - -#include "SDL.h" -#include "RmlUi/Core.h" - -#include "../src/ui/util/hsv.h" -#include "../src/ui/util/bem.h" - -#include "../src/ui/core/ui_context.h" - -namespace Rml { - class ElementDocument; - class EventListenerInstancer; - class Context; - class Event; -} - -namespace recompui { - class UiEventListenerInstancer; - - // TODO remove this once the UI has been ported over to the new system. - class MenuController { - public: - virtual ~MenuController() {} - virtual void load_document() = 0; - virtual void register_events(UiEventListenerInstancer& listener) = 0; - virtual void make_bindings(Rml::Context* context) = 0; - }; - - std::unique_ptr create_launcher_menu(); - std::unique_ptr create_config_menu(); - - using event_handler_t = void(const std::string& param, Rml::Event&); - - void queue_event(const SDL_Event& event); - bool try_deque_event(SDL_Event& out); - - std::unique_ptr make_event_listener_instancer(); - void register_event(UiEventListenerInstancer& listener, const std::string& name, event_handler_t* handler); - - void show_context(ContextId context, std::string_view param); - void hide_context(ContextId context); - void hide_all_contexts(); - bool is_context_shown(ContextId context); - bool is_context_capturing_input(); - bool is_context_capturing_mouse(); - bool is_any_context_shown(); - ContextId try_close_current_context(); - - ContextId get_launcher_context_id(); - ContextId get_config_context_id(); - ContextId get_config_sub_menu_context_id(); - - enum class ConfigTab { - General, - Controls, - Graphics, - Sound, - Mods, - Debug, - }; - - void set_config_tab(ConfigTab tab); - int config_tab_to_index(ConfigTab tab); - Rml::ElementTabSet* get_config_tabset(); - Rml::Element* get_mod_tab(); - void set_config_tabset_mod_nav(); - void focus_mod_configure_button(); - - enum class ButtonVariant { - Primary, - Secondary, - Tertiary, - Success, - Error, - Warning, - NumVariants, - }; - - void init_styling(const std::filesystem::path& rcss_file); - void init_prompt_context(); - void open_choice_prompt( - const std::string& header_text, - const std::string& content_text, - const std::string& confirm_label_text, - const std::string& cancel_label_text, - std::function confirm_action, - std::function cancel_action, - ButtonVariant confirm_variant = ButtonVariant::Success, - ButtonVariant cancel_variant = ButtonVariant::Error, - bool focus_on_cancel = true, - const std::string& return_element_id = "" - ); - void open_info_prompt( - const std::string& header_text, - const std::string& content_text, - const std::string& okay_label_text, - std::function okay_action, - ButtonVariant okay_variant = ButtonVariant::Error, - const std::string& return_element_id = "" - ); - void open_notification( - const std::string& header_text, - const std::string& content_text, - const std::string& return_element_id = "" - ); - void close_prompt(); - bool is_prompt_open(); - void update_mod_list(bool scan_mods = true); - void process_game_started(); - - void apply_color_hack(); - void get_window_size(int& width, int& height); - void set_cursor_visible(bool visible); - void update_supported_options(); - void toggle_fullscreen(); - - bool get_cont_active(void); - void set_cont_active(bool active); - void activate_mouse(); - - void message_box(const char* msg); - - void set_render_hooks(); - - Rml::ElementPtr create_custom_element(Rml::Element* parent, std::string tag); - Rml::ElementDocument* load_document(const std::filesystem::path& path); - Rml::ElementDocument* create_empty_document(); - Rml::Element* get_child_by_tag(Rml::Element* parent, const std::string& tag); - - void queue_image_from_bytes_rgba32(const std::string &src, const std::vector &bytes, uint32_t width, uint32_t height); - void queue_image_from_bytes_file(const std::string &src, const std::vector &bytes); - void release_image(const std::string &src); - - void drop_files(const std::list &file_list); -} - -#endif diff --git a/include/zelda_config.h b/include/zelda_config.h index 59b41f0..d15da18 100644 --- a/include/zelda_config.h +++ b/include/zelda_config.h @@ -2,92 +2,71 @@ #define __ZELDA_CONFIG_H__ #include -#include -#include "ultramodern/config.hpp" -#include "recomp_input.h" +#include namespace zelda64 { - constexpr std::u8string_view program_id = u8"Zelda64Recompiled"; - constexpr std::string_view program_name = "Zelda 64: Recompiled"; + inline const std::u8string program_id = u8"Zelda64Recompiled"; + inline const std::string program_name = "Zelda 64: Recompiled"; + + namespace configkeys { + namespace general { + inline const std::string debug_mode = "debug_mode"; + inline const std::string targeting_mode = "targeting_mode"; + inline const std::string autosave_mode = "autosave_mode"; + inline const std::string camera_invert_mode = "camera_invert_mode"; + inline const std::string analog_cam_mode = "analog_cam_mode"; + inline const std::string analog_camera_invert_mode = "analog_camera_invert_mode"; + } + + namespace sound { + inline const std::string bgm_volume = "bgm_volume"; + inline const std::string low_health_beeps = "low_health_beeps"; + } + + namespace graphics { + } + } // TODO: Move loading configs to the runtime once we have a way to allow per-project customization. - void load_config(); - void save_config(); - - void reset_input_bindings(); - void reset_cont_input_bindings(); - void reset_kb_input_bindings(); - void reset_single_input_binding(recomp::InputDevice device, recomp::GameInput input); - - std::filesystem::path get_app_folder_path(); + void init_config(); bool get_debug_mode_enabled(); - void set_debug_mode_enabled(bool enabled); - + enum class AutosaveMode { - On, Off, - OptionCount + On, }; - NLOHMANN_JSON_SERIALIZE_ENUM(zelda64::AutosaveMode, { - {zelda64::AutosaveMode::On, "On"}, - {zelda64::AutosaveMode::Off, "Off"} - }); + AutosaveMode get_autosave_mode(); enum class TargetingMode { Switch, - Hold, - OptionCount + Hold }; - - NLOHMANN_JSON_SERIALIZE_ENUM(zelda64::TargetingMode, { - {zelda64::TargetingMode::Switch, "Switch"}, - {zelda64::TargetingMode::Hold, "Hold"} - }); - + TargetingMode get_targeting_mode(); - void set_targeting_mode(TargetingMode mode); enum class CameraInvertMode { InvertNone, InvertX, InvertY, - InvertBoth, - OptionCount + InvertBoth }; - - NLOHMANN_JSON_SERIALIZE_ENUM(zelda64::CameraInvertMode, { - {zelda64::CameraInvertMode::InvertNone, "InvertNone"}, - {zelda64::CameraInvertMode::InvertX, "InvertX"}, - {zelda64::CameraInvertMode::InvertY, "InvertY"}, - {zelda64::CameraInvertMode::InvertBoth, "InvertBoth"} - }); - + CameraInvertMode get_camera_invert_mode(); - void set_camera_invert_mode(CameraInvertMode mode); - CameraInvertMode get_analog_camera_invert_mode(); - void set_analog_camera_invert_mode(CameraInvertMode mode); enum class AnalogCamMode { - On, Off, - OptionCount + On, }; - - NLOHMANN_JSON_SERIALIZE_ENUM(zelda64::AnalogCamMode, { - {zelda64::AnalogCamMode::On, "On"}, - {zelda64::AnalogCamMode::Off, "Off"} - }); - - AutosaveMode get_autosave_mode(); - void set_autosave_mode(AutosaveMode mode); - + AnalogCamMode get_analog_cam_mode(); - void set_analog_cam_mode(AnalogCamMode mode); - void open_quit_game_prompt(); + enum class LowHealthBeepsMode { + Off, + On, + }; }; #endif diff --git a/include/zelda_debug.h b/include/zelda_debug.h index 4c0743e..26552f4 100644 --- a/include/zelda_debug.h +++ b/include/zelda_debug.h @@ -5,6 +5,11 @@ #include namespace zelda64 { + namespace debug { + inline const std::string tab_id = "debug"; + inline const std::string tab_name = "Debug"; + } + struct SceneWarps { int index; std::string name; diff --git a/include/zelda_launcher.h b/include/zelda_launcher.h new file mode 100644 index 0000000..f88f7ee --- /dev/null +++ b/include/zelda_launcher.h @@ -0,0 +1,11 @@ +#ifndef __ZELDA_LAUNCHER_H__ +#define __ZELDA_LAUNCHER_H__ + +#include "recompui/recompui.h" + +namespace zelda64 { + void launcher_animation_setup(recompui::LauncherMenu *menu); + void launcher_animation_update(recompui::LauncherMenu *menu); +} + +#endif diff --git a/include/zelda_render.h b/include/zelda_render.h deleted file mode 100644 index 15397f9..0000000 --- a/include/zelda_render.h +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef __ZELDA_RENDER_H__ -#define __ZELDA_RENDER_H__ - -#include -#include - -#include "common/rt64_user_configuration.h" -#include "ultramodern/renderer_context.hpp" -#include "librecomp/mods.hpp" - -namespace RT64 { - struct Application; -} - -namespace zelda64 { - namespace renderer { - inline const std::string special_option_texture_pack_enabled = "_recomp_texture_pack_enabled"; - - class RT64Context final : public ultramodern::renderer::RendererContext { - public: - ~RT64Context() override; - RT64Context(uint8_t *rdram, ultramodern::renderer::WindowHandle window_handle, bool developer_mode); - - bool valid() override { return static_cast(app); } - - bool update_config(const ultramodern::renderer::GraphicsConfig &old_config, const ultramodern::renderer::GraphicsConfig &new_config) override; - - void enable_instant_present() override; - void send_dl(const OSTask *task) override; - void update_screen() override; - void shutdown() override; - uint32_t get_display_framerate() const override; - float get_resolution_scale() const override; - - private: - std::unique_ptr app; - std::unordered_set enabled_texture_packs; - std::unordered_set secondary_disabled_texture_packs; - - void check_texture_pack_actions(); - }; - - std::unique_ptr create_render_context(uint8_t *rdram, ultramodern::renderer::WindowHandle window_handle, bool developer_mode); - - RT64::UserConfiguration::Antialiasing RT64MaxMSAA(); - bool RT64SamplePositionsSupported(); - bool RT64HighPrecisionFBEnabled(); - - void trigger_texture_pack_update(); - void enable_texture_pack(const recomp::mods::ModContext& context, const recomp::mods::ModHandle& mod); - void disable_texture_pack(const recomp::mods::ModHandle& mod); - void secondary_enable_texture_pack(const std::string& mod_id); - void secondary_disable_texture_pack(const std::string& mod_id); - - // Texture pack enable option. Must be an enum with two options. - // The first option is treated as disabled and the second option is treated as enabled. - bool is_texture_pack_enable_config_option(const recomp::mods::ConfigOption& option, bool show_errors); - } -} - -#endif diff --git a/include/zelda_sound.h b/include/zelda_sound.h index 33bec01..07f0aaf 100644 --- a/include/zelda_sound.h +++ b/include/zelda_sound.h @@ -3,11 +3,7 @@ namespace zelda64 { void reset_sound_settings(); - void set_main_volume(int volume); - int get_main_volume(); - void set_bgm_volume(int volume); int get_bgm_volume(); - void set_low_health_beeps_enabled(bool enabled); bool get_low_health_beeps_enabled(); } diff --git a/lib/FindFreetype.cmake b/lib/FindFreetype.cmake deleted file mode 100644 index 1e50bcd..0000000 --- a/lib/FindFreetype.cmake +++ /dev/null @@ -1,9 +0,0 @@ -set(FREETYPE_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/lib/freetype-windows-binaries/include) -set(FREETYPE_LIBRARIES "${CMAKE_SOURCE_DIR}/lib/freetype-windows-binaries/release static/vs2015-2022/win64/freetype.lib") -add_library(Freetype::Freetype STATIC IMPORTED) -set_target_properties(Freetype::Freetype PROPERTIES - IMPORTED_LOCATION ${FREETYPE_LIBRARIES} -) -target_include_directories(Freetype::Freetype INTERFACE - ${FREETYPE_INCLUDE_DIRS} -) diff --git a/lib/GamepadMotionHelpers/.gitignore b/lib/GamepadMotionHelpers/.gitignore deleted file mode 100644 index 259148f..0000000 --- a/lib/GamepadMotionHelpers/.gitignore +++ /dev/null @@ -1,32 +0,0 @@ -# Prerequisites -*.d - -# Compiled Object files -*.slo -*.lo -*.o -*.obj - -# Precompiled Headers -*.gch -*.pch - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Fortran module files -*.mod -*.smod - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app diff --git a/lib/GamepadMotionHelpers/CMakeLists.txt b/lib/GamepadMotionHelpers/CMakeLists.txt deleted file mode 100644 index 27f7748..0000000 --- a/lib/GamepadMotionHelpers/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -cmake_minimum_required(VERSION 3.8) - -project(GamepadMotionHelpers LANGUAGES CXX) - -add_library(${PROJECT_NAME} INTERFACE) -add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) -target_include_directories(${PROJECT_NAME} - INTERFACE - $ - $) - \ No newline at end of file diff --git a/lib/GamepadMotionHelpers/GamepadMotion.hpp b/lib/GamepadMotionHelpers/GamepadMotion.hpp deleted file mode 100644 index 02497ac..0000000 --- a/lib/GamepadMotionHelpers/GamepadMotion.hpp +++ /dev/null @@ -1,1312 +0,0 @@ -// Copyright (c) 2020-2023 Julian "Jibb" Smart -// Released under the MIT license. See https://github.com/JibbSmart/GamepadMotionHelpers/blob/main/LICENSE for more info -// Version 9 - -#pragma once - -#define _USE_MATH_DEFINES -#include -#include // std::min, std::max and std::clamp - -// You don't need to look at these. These will just be used internally by the GamepadMotion class declared below. -// You can ignore anything in namespace GamepadMotionHelpers. -class GamepadMotionSettings; -class GamepadMotion; - -namespace GamepadMotionHelpers -{ - struct GyroCalibration - { - float X; - float Y; - float Z; - float AccelMagnitude; - int NumSamples; - }; - - struct Quat - { - float w; - float x; - float y; - float z; - - Quat(); - Quat(float inW, float inX, float inY, float inZ); - void Set(float inW, float inX, float inY, float inZ); - Quat& operator*=(const Quat& rhs); - friend Quat operator*(Quat lhs, const Quat& rhs); - void Normalize(); - Quat Normalized() const; - void Invert(); - Quat Inverse() const; - }; - - struct Vec - { - float x; - float y; - float z; - - Vec(); - Vec(float inValue); - Vec(float inX, float inY, float inZ); - void Set(float inX, float inY, float inZ); - float Length() const; - float LengthSquared() const; - void Normalize(); - Vec Normalized() const; - float Dot(const Vec& other) const; - Vec Cross(const Vec& other) const; - Vec Min(const Vec& other) const; - Vec Max(const Vec& other) const; - Vec Abs() const; - Vec Lerp(const Vec& other, float factor) const; - Vec Lerp(const Vec& other, const Vec& factor) const; - Vec& operator+=(const Vec& rhs); - friend Vec operator+(Vec lhs, const Vec& rhs); - Vec& operator-=(const Vec& rhs); - friend Vec operator-(Vec lhs, const Vec& rhs); - Vec& operator*=(const float rhs); - friend Vec operator*(Vec lhs, const float rhs); - Vec& operator/=(const float rhs); - friend Vec operator/(Vec lhs, const float rhs); - Vec& operator*=(const Quat& rhs); - friend Vec operator*(Vec lhs, const Quat& rhs); - Vec operator-() const; - }; - - struct SensorMinMaxWindow - { - Vec MinGyro; - Vec MaxGyro; - Vec MeanGyro; - Vec MinAccel; - Vec MaxAccel; - Vec MeanAccel; - Vec StartAccel; - int NumSamples = 0; - float TimeSampled = 0.f; - - SensorMinMaxWindow(); - void Reset(float remainder); - void AddSample(const Vec& inGyro, const Vec& inAccel, float deltaTime); - Vec GetMidGyro(); - }; - - struct AutoCalibration - { - SensorMinMaxWindow MinMaxWindow; - Vec SmoothedAngularVelocityGyro; - Vec SmoothedAngularVelocityAccel; - Vec SmoothedPreviousAccel; - Vec PreviousAccel; - - AutoCalibration(); - void Reset(); - bool AddSampleStillness(const Vec& inGyro, const Vec& inAccel, float deltaTime, bool doSensorFusion); - void NoSampleStillness(); - bool AddSampleSensorFusion(const Vec& inGyro, const Vec& inAccel, float deltaTime); - void NoSampleSensorFusion(); - void SetCalibrationData(GyroCalibration* calibrationData); - void SetSettings(GamepadMotionSettings* settings); - - float Confidence = 0.f; - bool IsSteady() { return bIsSteady; } - - private: - Vec MinDeltaGyro = Vec(1.f); - Vec MinDeltaAccel = Vec(0.25f); - float RecalibrateThreshold = 1.f; - float SensorFusionSkippedTime = 0.f; - float TimeSteadySensorFusion = 0.f; - float TimeSteadyStillness = 0.f; - bool bIsSteady = false; - - GyroCalibration* CalibrationData; - GamepadMotionSettings* Settings; - }; - - struct Motion - { - Quat Quaternion; - Vec Accel; - Vec Grav; - - Vec SmoothAccel = Vec(); - float Shakiness = 0.f; - const float ShortSteadinessHalfTime = 0.25f; - const float LongSteadinessHalfTime = 1.f; - - Motion(); - void Reset(); - void Update(float inGyroX, float inGyroY, float inGyroZ, float inAccelX, float inAccelY, float inAccelZ, float gravityLength, float deltaTime); - void SetSettings(GamepadMotionSettings* settings); - - private: - GamepadMotionSettings* Settings; - }; - - enum CalibrationMode - { - Manual = 0, - Stillness = 1, - SensorFusion = 2, - }; - - // https://stackoverflow.com/a/1448478/1130520 - inline CalibrationMode operator|(CalibrationMode a, CalibrationMode b) - { - return static_cast(static_cast(a) | static_cast(b)); - } - - inline CalibrationMode operator&(CalibrationMode a, CalibrationMode b) - { - return static_cast(static_cast(a) & static_cast(b)); - } - - inline CalibrationMode operator~(CalibrationMode a) - { - return static_cast(~static_cast(a)); - } - - // https://stackoverflow.com/a/23152590/1130520 - inline CalibrationMode& operator|=(CalibrationMode& a, CalibrationMode b) - { - return (CalibrationMode&)((int&)(a) |= static_cast(b)); - } - - inline CalibrationMode& operator&=(CalibrationMode& a, CalibrationMode b) - { - return (CalibrationMode&)((int&)(a) &= static_cast(b)); - } -} - -// Note that I'm using a Y-up coordinate system. This is to follow the convention set by the motion sensors in -// PlayStation controllers, which was what I was using when writing in this. But for the record, Z-up is -// better for most games (XY ground-plane in 3D games simplifies using 2D vectors in navigation, for example). - -// Gyro units should be degrees per second. Accelerometer should be g-force (approx. 9.8 m/s^2 = 1 g). If you're using -// radians per second, meters per second squared, etc, conversion should be simple. - -class GamepadMotionSettings -{ -public: - int MinStillnessSamples = 10; - float MinStillnessCollectionTime = 0.5f; - float MinStillnessCorrectionTime = 2.f; - float MaxStillnessError = 2.f; - float StillnessSampleDeteriorationRate = 0.2f; - float StillnessErrorClimbRate = 0.1f; - float StillnessErrorDropOnRecalibrate = 0.1f; - float StillnessCalibrationEaseInTime = 3.f; - float StillnessCalibrationHalfTime = 0.1f; - float StillnessConfidenceRate = 1.f; - - float StillnessGyroDelta = -1.f; - float StillnessAccelDelta = -1.f; - - float SensorFusionCalibrationSmoothingStrength = 2.f; - float SensorFusionAngularAccelerationThreshold = 20.f; - float SensorFusionCalibrationEaseInTime = 3.f; - float SensorFusionCalibrationHalfTime = 0.1f; - float SensorFusionConfidenceRate = 1.f; - - float GravityCorrectionShakinessMaxThreshold = 0.4f; - float GravityCorrectionShakinessMinThreshold = 0.01f; - - float GravityCorrectionStillSpeed = 1.f; - float GravityCorrectionShakySpeed = 0.1f; - - float GravityCorrectionGyroFactor = 0.1f; - float GravityCorrectionGyroMinThreshold = 0.05f; - float GravityCorrectionGyroMaxThreshold = 0.25f; - - float GravityCorrectionMinimumSpeed = 0.01f; -}; - -class GamepadMotion -{ -public: - GamepadMotion(); - - void Reset(); - - void ProcessMotion(float gyroX, float gyroY, float gyroZ, - float accelX, float accelY, float accelZ, float deltaTime); - - // reading the current state - void GetCalibratedGyro(float& x, float& y, float& z); - void GetGravity(float& x, float& y, float& z); - void GetProcessedAcceleration(float& x, float& y, float& z); - void GetOrientation(float& w, float& x, float& y, float& z); - void GetPlayerSpaceGyro(float& x, float& y, const float yawRelaxFactor = 1.41f); - static void CalculatePlayerSpaceGyro(float& x, float& y, const float gyroX, const float gyroY, const float gyroZ, const float gravX, const float gravY, const float gravZ, const float yawRelaxFactor = 1.41f); - void GetWorldSpaceGyro(float& x, float& y, const float sideReductionThreshold = 0.125f); - static void CalculateWorldSpaceGyro(float& x, float& y, const float gyroX, const float gyroY, const float gyroZ, const float gravX, const float gravY, const float gravZ, const float sideReductionThreshold = 0.125f); - - // gyro calibration functions - void StartContinuousCalibration(); - void PauseContinuousCalibration(); - void ResetContinuousCalibration(); - void GetCalibrationOffset(float& xOffset, float& yOffset, float& zOffset); - void SetCalibrationOffset(float xOffset, float yOffset, float zOffset, int weight); - float GetAutoCalibrationConfidence(); - void SetAutoCalibrationConfidence(float newConfidence); - bool GetAutoCalibrationIsSteady(); - - GamepadMotionHelpers::CalibrationMode GetCalibrationMode(); - void SetCalibrationMode(GamepadMotionHelpers::CalibrationMode calibrationMode); - - void ResetMotion(); - - GamepadMotionSettings Settings; - -private: - GamepadMotionHelpers::Vec Gyro; - GamepadMotionHelpers::Vec RawAccel; - GamepadMotionHelpers::Motion Motion; - GamepadMotionHelpers::GyroCalibration GyroCalibration; - GamepadMotionHelpers::AutoCalibration AutoCalibration; - GamepadMotionHelpers::CalibrationMode CurrentCalibrationMode; - - bool IsCalibrating; - void PushSensorSamples(float gyroX, float gyroY, float gyroZ, float accelMagnitude); - void GetCalibratedSensor(float& gyroOffsetX, float& gyroOffsetY, float& gyroOffsetZ, float& accelMagnitude); -}; - -///////////// Everything below here are just implementation details ///////////// - -namespace GamepadMotionHelpers -{ - inline Quat::Quat() - { - w = 1.0f; - x = 0.0f; - y = 0.0f; - z = 0.0f; - } - - inline Quat::Quat(float inW, float inX, float inY, float inZ) - { - w = inW; - x = inX; - y = inY; - z = inZ; - } - - inline static Quat AngleAxis(float inAngle, float inX, float inY, float inZ) - { - const float sinHalfAngle = sinf(inAngle * 0.5f); - Vec inAxis = Vec(inX, inY, inZ); - inAxis.Normalize(); - inAxis *= sinHalfAngle; - Quat result = Quat(cosf(inAngle * 0.5f), inAxis.x, inAxis.y, inAxis.z); - return result; - } - - inline void Quat::Set(float inW, float inX, float inY, float inZ) - { - w = inW; - x = inX; - y = inY; - z = inZ; - } - - inline Quat& Quat::operator*=(const Quat& rhs) - { - Set(w * rhs.w - x * rhs.x - y * rhs.y - z * rhs.z, - w * rhs.x + x * rhs.w + y * rhs.z - z * rhs.y, - w * rhs.y - x * rhs.z + y * rhs.w + z * rhs.x, - w * rhs.z + x * rhs.y - y * rhs.x + z * rhs.w); - return *this; - } - - inline Quat operator*(Quat lhs, const Quat& rhs) - { - lhs *= rhs; - return lhs; - } - - inline void Quat::Normalize() - { - const float length = sqrtf(w * w + x * x + y * y + z * z); - const float fixFactor = 1.0f / length; - - w *= fixFactor; - x *= fixFactor; - y *= fixFactor; - z *= fixFactor; - - return; - } - - inline Quat Quat::Normalized() const - { - Quat result = *this; - result.Normalize(); - return result; - } - - inline void Quat::Invert() - { - x = -x; - y = -y; - z = -z; - return; - } - - inline Quat Quat::Inverse() const - { - Quat result = *this; - result.Invert(); - return result; - } - - inline Vec::Vec() - { - x = 0.0f; - y = 0.0f; - z = 0.0f; - } - - inline Vec::Vec(float inValue) - { - x = inValue; - y = inValue; - z = inValue; - } - - inline Vec::Vec(float inX, float inY, float inZ) - { - x = inX; - y = inY; - z = inZ; - } - - inline void Vec::Set(float inX, float inY, float inZ) - { - x = inX; - y = inY; - z = inZ; - } - - inline float Vec::Length() const - { - return sqrtf(x * x + y * y + z * z); - } - - inline float Vec::LengthSquared() const - { - return x * x + y * y + z * z; - } - - inline void Vec::Normalize() - { - const float length = Length(); - if (length == 0.0) - { - return; - } - const float fixFactor = 1.0f / length; - - x *= fixFactor; - y *= fixFactor; - z *= fixFactor; - return; - } - - inline Vec Vec::Normalized() const - { - Vec result = *this; - result.Normalize(); - return result; - } - - inline Vec& Vec::operator+=(const Vec& rhs) - { - Set(x + rhs.x, y + rhs.y, z + rhs.z); - return *this; - } - - inline Vec operator+(Vec lhs, const Vec& rhs) - { - lhs += rhs; - return lhs; - } - - inline Vec& Vec::operator-=(const Vec& rhs) - { - Set(x - rhs.x, y - rhs.y, z - rhs.z); - return *this; - } - - inline Vec operator-(Vec lhs, const Vec& rhs) - { - lhs -= rhs; - return lhs; - } - - inline Vec& Vec::operator*=(const float rhs) - { - Set(x * rhs, y * rhs, z * rhs); - return *this; - } - - inline Vec operator*(Vec lhs, const float rhs) - { - lhs *= rhs; - return lhs; - } - - inline Vec& Vec::operator/=(const float rhs) - { - Set(x / rhs, y / rhs, z / rhs); - return *this; - } - - inline Vec operator/(Vec lhs, const float rhs) - { - lhs /= rhs; - return lhs; - } - - inline Vec& Vec::operator*=(const Quat& rhs) - { - Quat temp = rhs * Quat(0.0f, x, y, z) * rhs.Inverse(); - Set(temp.x, temp.y, temp.z); - return *this; - } - - inline Vec operator*(Vec lhs, const Quat& rhs) - { - lhs *= rhs; - return lhs; - } - - inline Vec Vec::operator-() const - { - Vec result = Vec(-x, -y, -z); - return result; - } - - inline float Vec::Dot(const Vec& other) const - { - return x * other.x + y * other.y + z * other.z; - } - - inline Vec Vec::Cross(const Vec& other) const - { - return Vec(y * other.z - z * other.y, - z * other.x - x * other.z, - x * other.y - y * other.x); - } - - inline Vec Vec::Min(const Vec& other) const - { - return Vec(x < other.x ? x : other.x, - y < other.y ? y : other.y, - z < other.z ? z : other.z); - } - - inline Vec Vec::Max(const Vec& other) const - { - return Vec(x > other.x ? x : other.x, - y > other.y ? y : other.y, - z > other.z ? z : other.z); - } - - inline Vec Vec::Abs() const - { - return Vec(x > 0 ? x : -x, - y > 0 ? y : -y, - z > 0 ? z : -z); - } - - inline Vec Vec::Lerp(const Vec& other, float factor) const - { - return *this + (other - *this) * factor; - } - - inline Vec Vec::Lerp(const Vec& other, const Vec& factor) const - { - return Vec(this->x + (other.x - this->x) * factor.x, - this->y + (other.y - this->y) * factor.y, - this->z + (other.z - this->z) * factor.z); - } - - inline Motion::Motion() - { - Reset(); - } - - inline void Motion::Reset() - { - Quaternion.Set(1.f, 0.f, 0.f, 0.f); - Accel.Set(0.f, 0.f, 0.f); - Grav.Set(0.f, 0.f, 0.f); - SmoothAccel.Set(0.f, 0.f, 0.f); - Shakiness = 0.f; - } - - /// - /// The gyro inputs should be calibrated degrees per second but have no other processing. Acceleration is in G units (1 = approx. 9.8m/s^2) - /// - inline void Motion::Update(float inGyroX, float inGyroY, float inGyroZ, float inAccelX, float inAccelY, float inAccelZ, float gravityLength, float deltaTime) - { - if (!Settings) - { - return; - } - - // get settings - const float gravityCorrectionShakinessMinThreshold = Settings->GravityCorrectionShakinessMinThreshold; - const float gravityCorrectionShakinessMaxThreshold = Settings->GravityCorrectionShakinessMaxThreshold; - const float gravityCorrectionStillSpeed = Settings->GravityCorrectionStillSpeed; - const float gravityCorrectionShakySpeed = Settings->GravityCorrectionShakySpeed; - const float gravityCorrectionGyroFactor = Settings->GravityCorrectionGyroFactor; - const float gravityCorrectionGyroMinThreshold = Settings->GravityCorrectionGyroMinThreshold; - const float gravityCorrectionGyroMaxThreshold = Settings->GravityCorrectionGyroMaxThreshold; - const float gravityCorrectionMinimumSpeed = Settings->GravityCorrectionMinimumSpeed; - - const Vec axis = Vec(inGyroX, inGyroY, inGyroZ); - const Vec accel = Vec(inAccelX, inAccelY, inAccelZ); - const float angleSpeed = axis.Length() * (float)M_PI / 180.0f; - const float angle = angleSpeed * deltaTime; - - // rotate - Quat rotation = AngleAxis(angle, axis.x, axis.y, axis.z); - Quaternion *= rotation; // do it this way because it's a local rotation, not global - - //printf("Quat: %.4f %.4f %.4f %.4f\n", - // Quaternion.w, Quaternion.x, Quaternion.y, Quaternion.z); - float accelMagnitude = accel.Length(); - if (accelMagnitude > 0.0f) - { - const Vec accelNorm = accel / accelMagnitude; - // account for rotation when tracking smoothed acceleration - SmoothAccel *= rotation.Inverse(); - //printf("Absolute Accel: %.4f %.4f %.4f\n", - // absoluteAccel.x, absoluteAccel.y, absoluteAccel.z); - const float smoothFactor = ShortSteadinessHalfTime <= 0.f ? 0.f : exp2f(-deltaTime / ShortSteadinessHalfTime); - Shakiness *= smoothFactor; - Shakiness = std::max(Shakiness, (accel - SmoothAccel).Length()); - SmoothAccel = accel.Lerp(SmoothAccel, smoothFactor); - - //printf("Shakiness: %.4f\n", Shakiness); - - // update grav by rotation - Grav *= rotation.Inverse(); - // we want to close the gap between grav and raw acceleration. What's the difference - const Vec gravToAccel = (accelNorm * -gravityLength) - Grav; - const Vec gravToAccelDir = gravToAccel.Normalized(); - // adjustment rate - float gravCorrectionSpeed; - if (gravityCorrectionShakinessMinThreshold < gravityCorrectionShakinessMaxThreshold) - { - gravCorrectionSpeed = gravityCorrectionStillSpeed + (gravityCorrectionShakySpeed - gravityCorrectionStillSpeed) * std::clamp((Shakiness - gravityCorrectionShakinessMinThreshold) / (gravityCorrectionShakinessMaxThreshold - gravityCorrectionShakinessMinThreshold), 0.f, 1.f); - } - else - { - gravCorrectionSpeed = Shakiness < gravityCorrectionShakinessMaxThreshold ? gravityCorrectionStillSpeed : gravityCorrectionShakySpeed; - } - // we also limit it to be no faster than a given proportion of the gyro rate, or the minimum gravity correction speed - const float gyroGravCorrectionLimit = std::max(angleSpeed * gravityCorrectionGyroFactor, gravityCorrectionMinimumSpeed); - if (gravCorrectionSpeed > gyroGravCorrectionLimit) - { - float closeEnoughFactor; - if (gravityCorrectionGyroMinThreshold < gravityCorrectionGyroMaxThreshold) - { - closeEnoughFactor = std::clamp((gravToAccel.Length() - gravityCorrectionGyroMinThreshold) / (gravityCorrectionGyroMaxThreshold - gravityCorrectionGyroMinThreshold), 0.f, 1.f); - } - else - { - closeEnoughFactor = gravToAccel.Length() < gravityCorrectionGyroMaxThreshold ? 0.f : 1.f; - } - gravCorrectionSpeed = gyroGravCorrectionLimit + (gravCorrectionSpeed - gyroGravCorrectionLimit) * closeEnoughFactor; - } - const Vec gravToAccelDelta = gravToAccelDir * gravCorrectionSpeed * deltaTime; - if (gravToAccelDelta.LengthSquared() < gravToAccel.LengthSquared()) - { - Grav += gravToAccelDelta; - } - else - { - Grav = accelNorm * -gravityLength; - } - - const Vec gravityDirection = Grav.Normalized() * Quaternion.Inverse(); // absolute gravity direction - const float errorAngle = acosf(std::clamp(Vec(0.0f, -1.0f, 0.0f).Dot(gravityDirection), -1.f, 1.f)); - const Vec flattened = Vec(0.0f, -1.0f, 0.0f).Cross(gravityDirection); - Quat correctionQuat = AngleAxis(errorAngle, flattened.x, flattened.y, flattened.z); - Quaternion = Quaternion * correctionQuat; - - Accel = accel + Grav; - } - else - { - Grav *= rotation.Inverse(); - Accel = Grav; - } - Quaternion.Normalize(); - } - - inline void Motion::SetSettings(GamepadMotionSettings* settings) - { - Settings = settings; - } - - inline SensorMinMaxWindow::SensorMinMaxWindow() - { - Reset(0.f); - } - - inline void SensorMinMaxWindow::Reset(float remainder) - { - NumSamples = 0; - TimeSampled = remainder; - } - - inline void SensorMinMaxWindow::AddSample(const Vec& inGyro, const Vec& inAccel, float deltaTime) - { - if (NumSamples == 0) - { - MaxGyro = inGyro; - MinGyro = inGyro; - MeanGyro = inGyro; - MaxAccel = inAccel; - MinAccel = inAccel; - MeanAccel = inAccel; - StartAccel = inAccel; - NumSamples = 1; - TimeSampled += deltaTime; - return; - } - - MaxGyro = MaxGyro.Max(inGyro); - MinGyro = MinGyro.Min(inGyro); - MaxAccel = MaxAccel.Max(inAccel); - MinAccel = MinAccel.Min(inAccel); - - NumSamples++; - TimeSampled += deltaTime; - - Vec delta = inGyro - MeanGyro; - MeanGyro += delta * (1.f / NumSamples); - delta = inAccel - MeanAccel; - MeanAccel += delta * (1.f / NumSamples); - } - - inline Vec SensorMinMaxWindow::GetMidGyro() - { - return MeanGyro; - } - - inline AutoCalibration::AutoCalibration() - { - CalibrationData = nullptr; - Reset(); - } - - inline void AutoCalibration::Reset() - { - MinMaxWindow.Reset(0.f); - Confidence = 0.f; - bIsSteady = false; - MinDeltaGyro = Vec(1.f); - MinDeltaAccel = Vec(0.25f); - RecalibrateThreshold = 1.f; - SensorFusionSkippedTime = 0.f; - TimeSteadySensorFusion = 0.f; - TimeSteadyStillness = 0.f; - } - - inline bool AutoCalibration::AddSampleStillness(const Vec& inGyro, const Vec& inAccel, float deltaTime, bool doSensorFusion) - { - if (inGyro.x == 0.f && inGyro.y == 0.f && inGyro.z == 0.f && - inAccel.x == 0.f && inAccel.y == 0.f && inAccel.z == 0.f) - { - // zeroes are almost certainly not valid inputs - return false; - } - - if (!Settings) - { - return false; - } - - if (!CalibrationData) - { - return false; - } - - // get settings - const int minStillnessSamples = Settings->MinStillnessSamples; - const float minStillnessCollectionTime = Settings->MinStillnessCollectionTime; - const float minStillnessCorrectionTime = Settings->MinStillnessCorrectionTime; - const float maxStillnessError = Settings->MaxStillnessError; - const float stillnessSampleDeteriorationRate = Settings->StillnessSampleDeteriorationRate; - const float stillnessErrorClimbRate = Settings->StillnessErrorClimbRate; - const float stillnessErrorDropOnRecalibrate = Settings->StillnessErrorDropOnRecalibrate; - const float stillnessCalibrationEaseInTime = Settings->StillnessCalibrationEaseInTime; - const float stillnessCalibrationHalfTime = Settings->StillnessCalibrationHalfTime * Confidence; - const float stillnessConfidenceRate = Settings->StillnessConfidenceRate; - const float stillnessGyroDelta = Settings->StillnessGyroDelta; - const float stillnessAccelDelta = Settings->StillnessAccelDelta; - - MinMaxWindow.AddSample(inGyro, inAccel, deltaTime); - // get deltas - const Vec gyroDelta = MinMaxWindow.MaxGyro - MinMaxWindow.MinGyro; - const Vec accelDelta = MinMaxWindow.MaxAccel - MinMaxWindow.MinAccel; - - bool calibrated = false; - bool isSteady = false; - const Vec climbThisTick = Vec(stillnessSampleDeteriorationRate * deltaTime); - if (stillnessGyroDelta < 0.f) - { - if (Confidence < 1.f) - { - MinDeltaGyro += climbThisTick; - } - } - else - { - MinDeltaGyro = Vec(stillnessGyroDelta); - } - if (stillnessAccelDelta < 0.f) - { - if (Confidence < 1.f) - { - MinDeltaAccel += climbThisTick; - } - } - else - { - MinDeltaAccel = Vec(stillnessAccelDelta); - } - - //printf("Deltas: %.4f %.4f %.4f; %.4f %.4f %.4f\n", - // gyroDelta.x, gyroDelta.y, gyroDelta.z, - // accelDelta.x, accelDelta.y, accelDelta.z); - - if (MinMaxWindow.NumSamples >= minStillnessSamples && MinMaxWindow.TimeSampled >= minStillnessCollectionTime) - { - MinDeltaGyro = MinDeltaGyro.Min(gyroDelta); - MinDeltaAccel = MinDeltaAccel.Min(accelDelta); - } - else - { - RecalibrateThreshold = std::min(RecalibrateThreshold + stillnessErrorClimbRate * deltaTime, maxStillnessError); - return false; - } - - // check that all inputs are below appropriate thresholds to be considered "still" - if (gyroDelta.x <= MinDeltaGyro.x * RecalibrateThreshold && - gyroDelta.y <= MinDeltaGyro.y * RecalibrateThreshold && - gyroDelta.z <= MinDeltaGyro.z * RecalibrateThreshold && - accelDelta.x <= MinDeltaAccel.x * RecalibrateThreshold && - accelDelta.y <= MinDeltaAccel.y * RecalibrateThreshold && - accelDelta.z <= MinDeltaAccel.z * RecalibrateThreshold) - { - if (MinMaxWindow.NumSamples >= minStillnessSamples && MinMaxWindow.TimeSampled >= minStillnessCorrectionTime) - { - /*if (TimeSteadyStillness == 0.f) - { - printf("Still!\n"); - }/**/ - - TimeSteadyStillness = std::min(TimeSteadyStillness + deltaTime, stillnessCalibrationEaseInTime); - const float calibrationEaseIn = stillnessCalibrationEaseInTime <= 0.f ? 1.f : TimeSteadyStillness / stillnessCalibrationEaseInTime; - - const Vec calibratedGyro = MinMaxWindow.GetMidGyro(); - - const Vec oldGyroBias = Vec(CalibrationData->X, CalibrationData->Y, CalibrationData->Z) / std::max((float)CalibrationData->NumSamples, 1.f); - const float stillnessLerpFactor = stillnessCalibrationHalfTime <= 0.f ? 0.f : exp2f(-calibrationEaseIn * deltaTime / stillnessCalibrationHalfTime); - Vec newGyroBias = calibratedGyro.Lerp(oldGyroBias, stillnessLerpFactor); - Confidence = std::min(Confidence + deltaTime * stillnessConfidenceRate, 1.f); - isSteady = true; - - if (doSensorFusion) - { - const Vec previousNormal = MinMaxWindow.StartAccel.Normalized(); - const Vec thisNormal = inAccel.Normalized(); - Vec angularVelocity = thisNormal.Cross(previousNormal); - const float crossLength = angularVelocity.Length(); - if (crossLength > 0.f) - { - const float thisDotPrev = std::clamp(thisNormal.Dot(previousNormal), -1.f, 1.f); - const float angleChange = acosf(thisDotPrev) * 180.0f / (float)M_PI; - const float anglePerSecond = angleChange / MinMaxWindow.TimeSampled; - angularVelocity *= anglePerSecond / crossLength; - } - - Vec axisCalibrationStrength = thisNormal.Abs(); - Vec sensorFusionBias = (calibratedGyro - angularVelocity).Lerp(oldGyroBias, stillnessLerpFactor); - if (axisCalibrationStrength.x <= 0.7f) - { - newGyroBias.x = sensorFusionBias.x; - } - if (axisCalibrationStrength.y <= 0.7f) - { - newGyroBias.y = sensorFusionBias.y; - } - if (axisCalibrationStrength.z <= 0.7f) - { - newGyroBias.z = sensorFusionBias.z; - } - } - - CalibrationData->X = newGyroBias.x; - CalibrationData->Y = newGyroBias.y; - CalibrationData->Z = newGyroBias.z; - - CalibrationData->AccelMagnitude = MinMaxWindow.MeanAccel.Length(); - CalibrationData->NumSamples = 1; - - calibrated = true; - } - else - { - RecalibrateThreshold = std::min(RecalibrateThreshold + stillnessErrorClimbRate * deltaTime, maxStillnessError); - } - } - else if (TimeSteadyStillness > 0.f) - { - //printf("Moved!\n"); - RecalibrateThreshold -= stillnessErrorDropOnRecalibrate; - if (RecalibrateThreshold < 1.f) RecalibrateThreshold = 1.f; - - TimeSteadyStillness = 0.f; - MinMaxWindow.Reset(0.f); - } - else - { - RecalibrateThreshold = std::min(RecalibrateThreshold + stillnessErrorClimbRate * deltaTime, maxStillnessError); - MinMaxWindow.Reset(0.f); - } - - bIsSteady = isSteady; - return calibrated; - } - - inline void AutoCalibration::NoSampleStillness() - { - MinMaxWindow.Reset(0.f); - } - - inline bool AutoCalibration::AddSampleSensorFusion(const Vec& inGyro, const Vec& inAccel, float deltaTime) - { - if (deltaTime <= 0.f) - { - return false; - } - - if (inGyro.x == 0.f && inGyro.y == 0.f && inGyro.z == 0.f && - inAccel.x == 0.f && inAccel.y == 0.f && inAccel.z == 0.f) - { - // all zeroes are almost certainly not valid inputs - TimeSteadySensorFusion = 0.f; - SensorFusionSkippedTime = 0.f; - PreviousAccel = inAccel; - SmoothedPreviousAccel = inAccel; - SmoothedAngularVelocityGyro = GamepadMotionHelpers::Vec(); - SmoothedAngularVelocityAccel = GamepadMotionHelpers::Vec(); - return false; - } - - if (PreviousAccel.x == 0.f && PreviousAccel.y == 0.f && PreviousAccel.z == 0.f) - { - TimeSteadySensorFusion = 0.f; - SensorFusionSkippedTime = 0.f; - PreviousAccel = inAccel; - SmoothedPreviousAccel = inAccel; - SmoothedAngularVelocityGyro = GamepadMotionHelpers::Vec(); - SmoothedAngularVelocityAccel = GamepadMotionHelpers::Vec(); - return false; - } - - // in case the controller state hasn't updated between samples - if (inAccel.x == PreviousAccel.x && inAccel.y == PreviousAccel.y && inAccel.z == PreviousAccel.z) - { - SensorFusionSkippedTime += deltaTime; - return false; - } - - if (!Settings) - { - return false; - } - - // get settings - const float sensorFusionCalibrationSmoothingStrength = Settings->SensorFusionCalibrationSmoothingStrength; - const float sensorFusionAngularAccelerationThreshold = Settings->SensorFusionAngularAccelerationThreshold; - const float sensorFusionCalibrationEaseInTime = Settings->SensorFusionCalibrationEaseInTime; - const float sensorFusionCalibrationHalfTime = Settings->SensorFusionCalibrationHalfTime * Confidence; - const float sensorFusionConfidenceRate = Settings->SensorFusionConfidenceRate; - - deltaTime += SensorFusionSkippedTime; - SensorFusionSkippedTime = 0.f; - bool calibrated = false; - bool isSteady = false; - - // framerate independent lerp smoothing: https://www.gamasutra.com/blogs/ScottLembcke/20180404/316046/Improved_Lerp_Smoothing.php - const float smoothingLerpFactor = exp2f(-sensorFusionCalibrationSmoothingStrength * deltaTime); - // velocity from smoothed accel matches better if we also smooth gyro - const Vec previousGyro = SmoothedAngularVelocityGyro; - SmoothedAngularVelocityGyro = inGyro.Lerp(SmoothedAngularVelocityGyro, smoothingLerpFactor); // smooth what remains - const float gyroAccelerationMag = (SmoothedAngularVelocityGyro - previousGyro).Length() / deltaTime; - // get angle between old and new accel - const Vec previousNormal = SmoothedPreviousAccel.Normalized(); - const Vec thisAccel = inAccel.Lerp(SmoothedPreviousAccel, smoothingLerpFactor); - const Vec thisNormal = thisAccel.Normalized(); - Vec angularVelocity = thisNormal.Cross(previousNormal); - const float crossLength = angularVelocity.Length(); - if (crossLength > 0.f) - { - const float thisDotPrev = std::clamp(thisNormal.Dot(previousNormal), -1.f, 1.f); - const float angleChange = acosf(thisDotPrev) * 180.0f / (float)M_PI; - const float anglePerSecond = angleChange / deltaTime; - angularVelocity *= anglePerSecond / crossLength; - } - SmoothedAngularVelocityAccel = angularVelocity; - - // apply corrections - if (gyroAccelerationMag > sensorFusionAngularAccelerationThreshold || CalibrationData == nullptr) - { - /*if (TimeSteadySensorFusion > 0.f) - { - printf("Shaken!\n"); - }/**/ - TimeSteadySensorFusion = 0.f; - //printf("No calibration due to acceleration of %.4f\n", gyroAccelerationMag); - } - else - { - /*if (TimeSteadySensorFusion == 0.f) - { - printf("Steady!\n"); - }/**/ - - TimeSteadySensorFusion = std::min(TimeSteadySensorFusion + deltaTime, sensorFusionCalibrationEaseInTime); - const float calibrationEaseIn = sensorFusionCalibrationEaseInTime <= 0.f ? 1.f : TimeSteadySensorFusion / sensorFusionCalibrationEaseInTime; - const Vec oldGyroBias = Vec(CalibrationData->X, CalibrationData->Y, CalibrationData->Z) / std::max((float)CalibrationData->NumSamples, 1.f); - // recalibrate over time proportional to the difference between the calculated bias and the current assumed bias - const float sensorFusionLerpFactor = sensorFusionCalibrationHalfTime <= 0.f ? 0.f : exp2f(-calibrationEaseIn * deltaTime / sensorFusionCalibrationHalfTime); - Vec newGyroBias = (SmoothedAngularVelocityGyro - SmoothedAngularVelocityAccel).Lerp(oldGyroBias, sensorFusionLerpFactor); - Confidence = std::min(Confidence + deltaTime * sensorFusionConfidenceRate, 1.f); - isSteady = true; - // don't change bias in axes that can't be affected by the gravity direction - Vec axisCalibrationStrength = thisNormal.Abs(); - if (axisCalibrationStrength.x > 0.7f) - { - axisCalibrationStrength.x = 1.f; - } - if (axisCalibrationStrength.y > 0.7f) - { - axisCalibrationStrength.y = 1.f; - } - if (axisCalibrationStrength.z > 0.7f) - { - axisCalibrationStrength.z = 1.f; - } - newGyroBias = newGyroBias.Lerp(oldGyroBias, axisCalibrationStrength.Min(Vec(1.f))); - - CalibrationData->X = newGyroBias.x; - CalibrationData->Y = newGyroBias.y; - CalibrationData->Z = newGyroBias.z; - - CalibrationData->AccelMagnitude = thisAccel.Length(); - - CalibrationData->NumSamples = 1; - - calibrated = true; - - //printf("Recalibrating at a strength of %.4f\n", calibrationEaseIn); - } - - SmoothedPreviousAccel = thisAccel; - PreviousAccel = inAccel; - - //printf("Gyro: %.4f, %.4f, %.4f | Accel: %.4f, %.4f, %.4f\n", - // SmoothedAngularVelocityGyro.x, SmoothedAngularVelocityGyro.y, SmoothedAngularVelocityGyro.z, - // SmoothedAngularVelocityAccel.x, SmoothedAngularVelocityAccel.y, SmoothedAngularVelocityAccel.z); - - bIsSteady = isSteady; - - return calibrated; - } - - inline void AutoCalibration::NoSampleSensorFusion() - { - TimeSteadySensorFusion = 0.f; - SensorFusionSkippedTime = 0.f; - PreviousAccel = GamepadMotionHelpers::Vec(); - SmoothedPreviousAccel = GamepadMotionHelpers::Vec(); - SmoothedAngularVelocityGyro = GamepadMotionHelpers::Vec(); - SmoothedAngularVelocityAccel = GamepadMotionHelpers::Vec(); - } - - inline void AutoCalibration::SetCalibrationData(GyroCalibration* calibrationData) - { - CalibrationData = calibrationData; - } - - inline void AutoCalibration::SetSettings(GamepadMotionSettings* settings) - { - Settings = settings; - } - -} // namespace GamepadMotionHelpers - -inline GamepadMotion::GamepadMotion() -{ - IsCalibrating = false; - CurrentCalibrationMode = GamepadMotionHelpers::CalibrationMode::Manual; - Reset(); - AutoCalibration.SetCalibrationData(&GyroCalibration); - AutoCalibration.SetSettings(&Settings); - Motion.SetSettings(&Settings); -} - -inline void GamepadMotion::Reset() -{ - GyroCalibration = {}; - Gyro = {}; - RawAccel = {}; - Settings = GamepadMotionSettings(); - Motion.Reset(); -} - -inline void GamepadMotion::ProcessMotion(float gyroX, float gyroY, float gyroZ, - float accelX, float accelY, float accelZ, float deltaTime) -{ - if (gyroX == 0.f && gyroY == 0.f && gyroZ == 0.f && - accelX == 0.f && accelY == 0.f && accelZ == 0.f) - { - // all zeroes are almost certainly not valid inputs - return; - } - - float accelMagnitude = sqrtf(accelX * accelX + accelY * accelY + accelZ * accelZ); - - if (IsCalibrating) - { - // manual calibration - PushSensorSamples(gyroX, gyroY, gyroZ, accelMagnitude); - AutoCalibration.NoSampleSensorFusion(); - AutoCalibration.NoSampleStillness(); - } - else if (CurrentCalibrationMode & GamepadMotionHelpers::CalibrationMode::Stillness) - { - AutoCalibration.AddSampleStillness(GamepadMotionHelpers::Vec(gyroX, gyroY, gyroZ), GamepadMotionHelpers::Vec(accelX, accelY, accelZ), deltaTime, CurrentCalibrationMode & GamepadMotionHelpers::CalibrationMode::SensorFusion); - AutoCalibration.NoSampleSensorFusion(); - } - else - { - AutoCalibration.NoSampleStillness(); - if (CurrentCalibrationMode & GamepadMotionHelpers::CalibrationMode::SensorFusion) - { - AutoCalibration.AddSampleSensorFusion(GamepadMotionHelpers::Vec(gyroX, gyroY, gyroZ), GamepadMotionHelpers::Vec(accelX, accelY, accelZ), deltaTime); - } - else - { - AutoCalibration.NoSampleSensorFusion(); - } - } - - float gyroOffsetX, gyroOffsetY, gyroOffsetZ; - GetCalibratedSensor(gyroOffsetX, gyroOffsetY, gyroOffsetZ, accelMagnitude); - - gyroX -= gyroOffsetX; - gyroY -= gyroOffsetY; - gyroZ -= gyroOffsetZ; - - Motion.Update(gyroX, gyroY, gyroZ, accelX, accelY, accelZ, accelMagnitude, deltaTime); - - Gyro.x = gyroX; - Gyro.y = gyroY; - Gyro.z = gyroZ; - RawAccel.x = accelX; - RawAccel.y = accelY; - RawAccel.z = accelZ; -} - -// reading the current state -inline void GamepadMotion::GetCalibratedGyro(float& x, float& y, float& z) -{ - x = Gyro.x; - y = Gyro.y; - z = Gyro.z; -} - -inline void GamepadMotion::GetGravity(float& x, float& y, float& z) -{ - x = Motion.Grav.x; - y = Motion.Grav.y; - z = Motion.Grav.z; -} - -inline void GamepadMotion::GetProcessedAcceleration(float& x, float& y, float& z) -{ - x = Motion.Accel.x; - y = Motion.Accel.y; - z = Motion.Accel.z; -} - -inline void GamepadMotion::GetOrientation(float& w, float& x, float& y, float& z) -{ - w = Motion.Quaternion.w; - x = Motion.Quaternion.x; - y = Motion.Quaternion.y; - z = Motion.Quaternion.z; -} - -inline void GamepadMotion::GetPlayerSpaceGyro(float& x, float& y, const float yawRelaxFactor) -{ - CalculatePlayerSpaceGyro(x, y, Gyro.x, Gyro.y, Gyro.z, Motion.Grav.x, Motion.Grav.y, Motion.Grav.z, yawRelaxFactor); -} - -inline void GamepadMotion::CalculatePlayerSpaceGyro(float& x, float& y, const float gyroX, const float gyroY, const float gyroZ, const float gravX, const float gravY, const float gravZ, const float yawRelaxFactor) -{ - // take gravity into account without taking on any error from gravity. Explained in depth at http://gyrowiki.jibbsmart.com/blog:player-space-gyro-and-alternatives-explained#toc7 - const float worldYaw = -(gravY * gyroY + gravZ * gyroZ); - const float worldYawSign = worldYaw < 0.f ? -1.f : 1.f; - y = worldYawSign * std::min(std::abs(worldYaw) * yawRelaxFactor, sqrtf(gyroY * gyroY + gyroZ * gyroZ)); - x = gyroX; -} - -inline void GamepadMotion::GetWorldSpaceGyro(float& x, float& y, const float sideReductionThreshold) -{ - CalculateWorldSpaceGyro(x, y, Gyro.x, Gyro.y, Gyro.z, Motion.Grav.x, Motion.Grav.y, Motion.Grav.z, sideReductionThreshold); -} - -inline void GamepadMotion::CalculateWorldSpaceGyro(float& x, float& y, const float gyroX, const float gyroY, const float gyroZ, const float gravX, const float gravY, const float gravZ, const float sideReductionThreshold) -{ - // use the gravity direction as the yaw axis, and derive an appropriate pitch axis. Explained in depth at http://gyrowiki.jibbsmart.com/blog:player-space-gyro-and-alternatives-explained#toc6 - const float worldYaw = -gravX * gyroX - gravY * gyroY - gravZ * gyroZ; - // project local pitch axis (X) onto gravity plane - const float gravDotPitchAxis = gravX; - GamepadMotionHelpers::Vec pitchAxis(1.f - gravX * gravDotPitchAxis, - -gravY * gravDotPitchAxis, - -gravZ * gravDotPitchAxis); - // normalize - const float pitchAxisLengthSquared = pitchAxis.LengthSquared(); - if (pitchAxisLengthSquared > 0.f) - { - const float pitchAxisLength = sqrtf(pitchAxisLengthSquared); - const float lengthReciprocal = 1.f / pitchAxisLength; - pitchAxis *= lengthReciprocal; - - const float flatness = std::abs(gravY); - const float upness = std::abs(gravZ); - const float sideReduction = sideReductionThreshold <= 0.f ? 1.f : std::clamp((std::max(flatness, upness) - sideReductionThreshold) / sideReductionThreshold, 0.f, 1.f); - - x = sideReduction * pitchAxis.Dot(GamepadMotionHelpers::Vec(gyroX, gyroY, gyroZ)); - } - else - { - x = 0.f; - } - - y = worldYaw; -} - -// gyro calibration functions -inline void GamepadMotion::StartContinuousCalibration() -{ - IsCalibrating = true; -} - -inline void GamepadMotion::PauseContinuousCalibration() -{ - IsCalibrating = false; -} - -inline void GamepadMotion::ResetContinuousCalibration() -{ - GyroCalibration = {}; - AutoCalibration.Reset(); -} - -inline void GamepadMotion::GetCalibrationOffset(float& xOffset, float& yOffset, float& zOffset) -{ - float accelMagnitude; - GetCalibratedSensor(xOffset, yOffset, zOffset, accelMagnitude); -} - -inline void GamepadMotion::SetCalibrationOffset(float xOffset, float yOffset, float zOffset, int weight) -{ - if (GyroCalibration.NumSamples > 1) - { - GyroCalibration.AccelMagnitude *= ((float)weight) / GyroCalibration.NumSamples; - } - else - { - GyroCalibration.AccelMagnitude = (float)weight; - } - - GyroCalibration.NumSamples = weight; - GyroCalibration.X = xOffset * weight; - GyroCalibration.Y = yOffset * weight; - GyroCalibration.Z = zOffset * weight; -} - -inline float GamepadMotion::GetAutoCalibrationConfidence() -{ - return AutoCalibration.Confidence; -} - -inline void GamepadMotion::SetAutoCalibrationConfidence(float newConfidence) -{ - AutoCalibration.Confidence = newConfidence; -} - -inline bool GamepadMotion::GetAutoCalibrationIsSteady() -{ - return AutoCalibration.IsSteady(); -} - -inline GamepadMotionHelpers::CalibrationMode GamepadMotion::GetCalibrationMode() -{ - return CurrentCalibrationMode; -} - -inline void GamepadMotion::SetCalibrationMode(GamepadMotionHelpers::CalibrationMode calibrationMode) -{ - CurrentCalibrationMode = calibrationMode; -} - -inline void GamepadMotion::ResetMotion() -{ - Motion.Reset(); -} - -// Private Methods - -inline void GamepadMotion::PushSensorSamples(float gyroX, float gyroY, float gyroZ, float accelMagnitude) -{ - // accumulate - GyroCalibration.NumSamples++; - GyroCalibration.X += gyroX; - GyroCalibration.Y += gyroY; - GyroCalibration.Z += gyroZ; - GyroCalibration.AccelMagnitude += accelMagnitude; -} - -inline void GamepadMotion::GetCalibratedSensor(float& gyroOffsetX, float& gyroOffsetY, float& gyroOffsetZ, float& accelMagnitude) -{ - if (GyroCalibration.NumSamples <= 0) - { - gyroOffsetX = 0.f; - gyroOffsetY = 0.f; - gyroOffsetZ = 0.f; - accelMagnitude = 1.f; - return; - } - - const float inverseSamples = 1.f / GyroCalibration.NumSamples; - gyroOffsetX = GyroCalibration.X * inverseSamples; - gyroOffsetY = GyroCalibration.Y * inverseSamples; - gyroOffsetZ = GyroCalibration.Z * inverseSamples; - accelMagnitude = GyroCalibration.AccelMagnitude * inverseSamples; -} diff --git a/lib/GamepadMotionHelpers/LICENSE b/lib/GamepadMotionHelpers/LICENSE deleted file mode 100644 index a46396a..0000000 --- a/lib/GamepadMotionHelpers/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020-2023 Julian "Jibb" Smart - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/lib/GamepadMotionHelpers/README.md b/lib/GamepadMotionHelpers/README.md deleted file mode 100644 index c5c1bc9..0000000 --- a/lib/GamepadMotionHelpers/README.md +++ /dev/null @@ -1,76 +0,0 @@ -# GamepadMotionHelpers -GamepadMotionHelpers is a lightweight header-only library for sensor fusion, gyro calibration, etc. BYO input library (eg [SDL2](https://github.com/libsdl-org/SDL)). - -## Units -Convert your gyro units into **degrees per second** and accelerometer units to **g-force** (1 g = 9.8 m/s^2). You don't have to use these units in your application, but convert to these units when writing to GamepadMotionHelpers and convert back when reading from it. Your input reader might prefer radians per second and metres per second squared, but the datasheets for every IMU I've seen talk about degrees per second and g-force. - -## Coordinate Space -This library uses a Y-up coordinate system. While Z-up is (only slightly) preferable for many games, PlayStation controllers use Y-up, and have set the standard for input libraries like [SDL2](https://github.com/libsdl-org/SDL) and [JSL](https://github.com/JibbSmart/JoyShockLibrary). These libraries convert inputs from other controller types to the same space used by PlayStation's DualShock 4 and DualSense, so that's what's used here. - -## Basic Use -Include the GamepadMotion.hpp file in your C++ project. That's it! Everything you need is in that file, and its only dependency is ``````. - -For each controller with gyro (and optionally accelerometer), create a ```GamepadMotion``` object. At regular intervals, whether when a new report comes in from the controller or when polling the controller's state, you should call ```ProcessMotion(...)```. This is when you tell your GamepadMotion object the latest gyro (in degrees per second) and accelerometer (in g-force) inputs. You'll also give it the time since the last update for this controller (in seconds). - -ProcessMotion takes these inputs, updates some internal values, and then you can use any of the following to read its current state: -- ```GetCalibratedGyro(float& x, float& y, float& z)``` - Get the controller's angular velocity in degrees per second. This is just the raw gyro you gave it minus the gyro's bias as determined by your calibration settings (more on that below). -- ```GetGravity(float& x, float& y, float& z)``` - Get the gravity direction in the controller's local space. When the controller is still on a flat surface it'll be approximately (0, -1, 0). The controller can't detect the gravity direction when it's in freefall or being shaken around, but it can make a pretty good guess if its gyro is correctly calibrated and then make further corrections when the controller is still again. -- ```GetProcessedAcceleration(float& x, float& y, float& z)``` - Get the controller's current acceleration in g-force with gravity removed. Raw accelerometer input includes gravity -- it is only (0, 0, 0) when the controller is in freefall. However, using the gravity direction as calculated for GetGravity, it can remove that component and detect how you're shaking the controller about. This function gives you that acceleration vector with the gravity removed. -- ```GetOrientation(float& w, float& x, float& y, float& z)``` - Get the controller's orientation. Gyro and accelerometer input are combined to give a good estimate of the controller's orientation. - -Additional helper functions are available for taking gravity into account and returning a "world space" or "player space" rotation in two axes. Bear in mind that the **X** and **Y** set by these functions is still around the controller's axes. This means **Y** is the *horizontal* part of the rotation, and **X** is the vertical part. To convert to a mouse-like input, you'll treat the **Y** as the horizontal or yaw input and **X** as the vertical or pitch input. This might be unintuitive, but since it's also true of the "local space" angular velocities obtained from GetCalibratedGyro, this makes it simple to let the user choose between *local space*, *world space*, and *player space* in your game or application by just swapping GetCalibratedGyro for these functions depending on that selection: -- ```GetWorldSpaceGyro(float& x, float& y, const float sideReductionThreshold = 0.125f)``` - Get the controller's angular velocity in *world space* as described on GyroWiki in the [player space article here](http://gyrowiki.jibbsmart.com/blog:player-space-gyro-and-alternatives-explained#toc6). Yaw input will be derived from motion around the gravity axis, and pitch input from an appropriate pitch axis calculated from the controller's orientation with respect to the gravity axis. Any errors in the calculated gravity axis (though likely very small) will be taken on by the calculated world space gyro rotation, making it slightly less robust than using calibrated gyro directly ("local space" gyro) or using *player space* gyro below. More info in the linked article. -- ```GetPlayerSpaceGyro(float& x, float& y, const float yawRelaxFactor = 1.41f)``` - Get the controller's angular velocity in *player space* as described on GyroWiki in the [player space article here](http://gyrowiki.jibbsmart.com/blog:player-space-gyro-and-alternatives-explained#toc7). Yaw input will be derived from motion approximately around the gravity axis, without any impact from errors in the gravity calculation. Pitch is just local pitch. It is robust, accommodates players who are used to both local space and world space gyro, while taking on most of the advantages of each. It is proven in popular games and is an ideal default for players using a standalone controller. For handheld (where the screen is part of the controller, such as mobile, Switch, or Steam Deck) local space (using the calibrated gyro input directly) may be preferable. More info in the linked article. - -If you want to plug in the gyro and gravity values yourself (perhaps you're using an externally calculated gravity), you can use ```CalculateWorldSpaceGyro``` and ```CalculatePlayerSpaceGyro``` instead. Make sure you use this GamepadMotionHelpers' coordinate space, units, and gravity is normalized, since those are all assumed for these functions. - -## Sensor Fusion -Combining multiple types of sensor like this to get a better picture of the controller's state is called "sensor fusion". Moment-to-moment changes in orientation are detected using the gyro, but that only gives local angular velocity and needs to be correctly calibrated. Errors can accumulate over time. The gravity vector as detected by the accelerometer is used to make corrections to the relevant components of the controller's orientation. - -But this cannot be used to correct the controller's orientation around the gravity vector (the **yaw** axis). If you're using the controller's absolute orientation for some reason, this "yaw drift" may need to be accounted for somehow. Some devices also have a magnetometer (compass) to counter yaw drift, but since popular game controllers don't have a magnetometer, I haven't tried it myself. In future, if I get such a device, I'd like to add the option for GamepadMotionHelpers to accept magnetometer input and account for it when calculating values for the above functions. - -## Gyro Calibration -Modern gyroscopes often need calibration. This is like how a [weighing scale](https://en.wikipedia.org/wiki/Weighing_scale) can need calibration to tell it what 'zero' is. Like a weighing scale, a correctly calibrated gyroscope will give an accurate reading. If you're using the gyro input as a mouse, which is the simplest application of a controller's gyro, you can find essential reading on [GyroWiki here](http://gyrowiki.jibbsmart.com/blog:good-gyro-controls-part-1:the-gyro-is-a-mouse). - -Calibration just means having the controller sit still and remembering the average reported angular velocity in each axis. This is the gyro's "bias". In GamepadMotionHelpers, I call our best guess at the controller's bias the "calibration offset". GamepadMotionHelpers has some options to help with calibrating: - -At any time, you can begin manually calibrating a controller by calling ```StartContinuousCalibration()```. This will start recording the average angular velocity and apply it immediately to any subsequent **GetGalibratedGyro(...)** call. At any time you can ```PauseContinuousCalibration()``` to no longer add current values to the average angular velocity being recorded. You can ```ResetContinousCalibration()``` to remove the recorded average before starting over with **StartContinuousCalibration** again. - -You can read the stored calibration values using ```GetCalibrationOffset(float& xOffset, float& yOffset, float& zOffset)```. You can manually set the calibration offset yourself with ```SetCalibrationOffset(float xOffset, float yOffset, float zOffset, int weight)```. This will override all stored values. The **weight** argument at the end determines how strongly these values should be considered over time if Continuous Calibration is still active (new values are still being added to the average). Each new sample has a weight of 1, so if you **SetCalibrationOffset** with a weight of 10, it'll have the weight of 10 samples when calculating the average. If you're not continuing to add samples (Continuous Calibration is not active), the weight will be meaningless. Setting this manually is unusual, so don't worry about it too much if that sounds complicated. - -Most games don't ask the user to calibrate the gyro themselves. They have built-in automatic calibration, which I like to call "auto-calibration". There's no such thing as a "good enough" auto-calibration solution -- at least not with only gyro and accelerometer. Every game that has an auto-calibration solution would be made better for more serious players with the option to manually calibrate their gyro, so I urge you to provide players the option to do the same in your game. Having said that, auto-calibration is a useful option for casual players, and you may choose to have it enabled in your game by default. - -So GamepadMotionHelpers provides some auto-calibration options. You can call ```SetCalibrationMode(CalibrationMode)``` on each GamepadMotion instance with the following options: -- ```CalibrationMode::Manual``` - No auto-calibration. This is the default. -- ```CalibrationMode::Stillness``` - Automatically try to detect when the controller is being held still and update the calibration offset accordingly. -- ```CalibrationMode::SensorFusion``` - Calculate an angular velocity from changes in the gravity direction as detected by the accelerometer. If these are steady enough, use them to make corrections to the calibration offset. This will only apply to relevant axes. - -Many players are already aware of the shortcomings of trying to automatically detect stillness to automatically calibrate the gyro. Whether on Switch, PlayStation, or using PlayStation controllers on PC, players have tried to track a slow or distant target only to have the aimer suddenly stop moving! The game or the platform has **misinterpreted their slow and steady input as the controller being held still**, and they've incorrectly recalibrated accordingly. Players *hate it* when this happens. - -**This is why it's important to let players manually calibrate their gyro** if they want to. - -Auto-calibration is used so widely in console games that it's speculated that game developers may not have the option to disable it on these platforms. If this is the case, GamepadMotionHelpers offers a big advantage over those platforms: you can disable it and enable it at any time. - -You, the game developer, can have your game tell if the player is tracking a distant or slow-moving target. You can tell if the player's aimer is moving towards a visible target or roughly following the movement of one. When it is, maybe disabling the auto-calibration (```SetCalibrationMode(CalibrationMode::Manual)```) could be the difference between good and bad auto-calibration. I don't know if the GamepadMotionHelpers auto-calibration functions are better or worse than their Switch and PlayStation counterparts generally, but by letting you take the game's context into account, you may be able to offer players a way better experience without them having to manually calibrate. - -But still give them the option to calibrate manually, please :) - -The **SensorFusion** calibration mode has shortcomings of its own. It's much harder to accidentally trick the game into incorrectly calibrating, but the angular velocity calculated from the accelerometer moment-to-moment is generally much less precise. Leaving the controller still, you'll notice the calibrated gyro moving slightly up and down over time. So while the **Stillness** mode is characterised by good behaviour occasionally punctuated by frustrating errors, the **SensorFusion** mode will tend to be more consistently not-quite-right without being terrible. - -Secondly, this library currently only combines accelerometer and gyro, so the **SensorFusion** auto-calibration cannot correct the gyro in all axes at the same time. The **SensorFusion**-only mode will be more useful in future when magnetometer input is supported, which can account for the axes that the accelerometer can't. - -Both auto-calibration modes can be combined by passing ```CalibrationMode::Stillness | CalibrationMode::SensorFusion``` to **SetCalibrationMode**. In this case, it'll use **Stillness** auto-calibration, but it'll adjust the calibration offset based on any angular velocity implied by changes in the accelerometer input. This tends to give better results than just using **Stillness** or **SensorFusion** on their own. - -If you aren't sure what to choose, I'd suggest using the combined ```CalibrationMode::Stillness | CalibrationMode::SensorFusion``` when auto calibration is enabled, but also allowing the player to manually calibrate. - -**TODO** This is a clunky way to let the user set up what is obviously the best solution. Maybe I should just call it "hybrid" or something and be done with it? - -Auto-calibration can also be used to communicate manual calibration to the player. ```GetAutoCalibrationIsSteady()``` will tell you whether GamepadMotionHelpers thinks the controller is currently being held steady (if auto-calibration is enabled). ```GetAutoCalibrationConfidence()``` will tell you how confident GamepadMotionHelpers is that it has a good calibration value from auto-calibration, from 0-1. Higher confidence means that new calibration changes will be applied more gradually. You can use these functions to detect when a controller needs to be calibrated, prompt the player to put their controller down, detect when they have put their controller down, and show progress for calibration (default 1 second once it starts). You can also override the confidence yourself (```SetAutoCalibrationConfidence()```), and resetting calibration will reset confidence to 0. How quickly confidence grows as well as other calibration settings can be customised in **GamepadMotionSettings**. - -## In the Wild -GamepadMotionHelpers is currently used in: -- [JoyShockMapper](https://github.com/Electronicks/JoyShockMapper) -- [JoyShockLibrary](https://github.com/JibbSmart/JoyShockLibrary) -- JoyShockOverlay - -If you know of any other games or applications using GamepadMotionHelpers, please let me know! \ No newline at end of file diff --git a/lib/N64ModernRuntime b/lib/N64ModernRuntime index df7e820..589bbf0 160000 --- a/lib/N64ModernRuntime +++ b/lib/N64ModernRuntime @@ -1 +1 @@ -Subproject commit df7e820d8c55e4fcb4616c210cbb2c01b25cd48c +Subproject commit 589bbf018a3e6d3646ddf7de1e7919f1b7e99bb1 diff --git a/lib/RecompFrontend b/lib/RecompFrontend new file mode 160000 index 0000000..b1a1477 --- /dev/null +++ b/lib/RecompFrontend @@ -0,0 +1 @@ +Subproject commit b1a1477c6556aeb7ed45defbfb5924f721efebc1 diff --git a/lib/RmlUi b/lib/RmlUi deleted file mode 160000 index 7a06f27..0000000 --- a/lib/RmlUi +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7a06f27db04fe5d13a5dacc19b2b4544673a4eca diff --git a/lib/concurrentqueue/blockingconcurrentqueue.h b/lib/concurrentqueue/blockingconcurrentqueue.h deleted file mode 100644 index 205a4db..0000000 --- a/lib/concurrentqueue/blockingconcurrentqueue.h +++ /dev/null @@ -1,582 +0,0 @@ -// Provides an efficient blocking version of moodycamel::ConcurrentQueue. -// ©2015-2020 Cameron Desrochers. Distributed under the terms of the simplified -// BSD license, available at the top of concurrentqueue.h. -// Also dual-licensed under the Boost Software License (see LICENSE.md) -// Uses Jeff Preshing's semaphore implementation (under the terms of its -// separate zlib license, see lightweightsemaphore.h). - -#pragma once - -#include "concurrentqueue.h" -#include "lightweightsemaphore.h" - -#include -#include -#include -#include -#include - -namespace moodycamel -{ -// This is a blocking version of the queue. It has an almost identical interface to -// the normal non-blocking version, with the addition of various wait_dequeue() methods -// and the removal of producer-specific dequeue methods. -template -class BlockingConcurrentQueue -{ -private: - typedef ::moodycamel::ConcurrentQueue ConcurrentQueue; - typedef ::moodycamel::LightweightSemaphore LightweightSemaphore; - -public: - typedef typename ConcurrentQueue::producer_token_t producer_token_t; - typedef typename ConcurrentQueue::consumer_token_t consumer_token_t; - - typedef typename ConcurrentQueue::index_t index_t; - typedef typename ConcurrentQueue::size_t size_t; - typedef typename std::make_signed::type ssize_t; - - static const size_t BLOCK_SIZE = ConcurrentQueue::BLOCK_SIZE; - static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = ConcurrentQueue::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD; - static const size_t EXPLICIT_INITIAL_INDEX_SIZE = ConcurrentQueue::EXPLICIT_INITIAL_INDEX_SIZE; - static const size_t IMPLICIT_INITIAL_INDEX_SIZE = ConcurrentQueue::IMPLICIT_INITIAL_INDEX_SIZE; - static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = ConcurrentQueue::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE; - static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = ConcurrentQueue::EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE; - static const size_t MAX_SUBQUEUE_SIZE = ConcurrentQueue::MAX_SUBQUEUE_SIZE; - -public: - // Creates a queue with at least `capacity` element slots; note that the - // actual number of elements that can be inserted without additional memory - // allocation depends on the number of producers and the block size (e.g. if - // the block size is equal to `capacity`, only a single block will be allocated - // up-front, which means only a single producer will be able to enqueue elements - // without an extra allocation -- blocks aren't shared between producers). - // This method is not thread safe -- it is up to the user to ensure that the - // queue is fully constructed before it starts being used by other threads (this - // includes making the memory effects of construction visible, possibly with a - // memory barrier). - explicit BlockingConcurrentQueue(size_t capacity = 6 * BLOCK_SIZE) - : inner(capacity), sema(create(0, (int)Traits::MAX_SEMA_SPINS), &BlockingConcurrentQueue::template destroy) - { - assert(reinterpret_cast((BlockingConcurrentQueue*)1) == &((BlockingConcurrentQueue*)1)->inner && "BlockingConcurrentQueue must have ConcurrentQueue as its first member"); - if (!sema) { - MOODYCAMEL_THROW(std::bad_alloc()); - } - } - - BlockingConcurrentQueue(size_t minCapacity, size_t maxExplicitProducers, size_t maxImplicitProducers) - : inner(minCapacity, maxExplicitProducers, maxImplicitProducers), sema(create(0, (int)Traits::MAX_SEMA_SPINS), &BlockingConcurrentQueue::template destroy) - { - assert(reinterpret_cast((BlockingConcurrentQueue*)1) == &((BlockingConcurrentQueue*)1)->inner && "BlockingConcurrentQueue must have ConcurrentQueue as its first member"); - if (!sema) { - MOODYCAMEL_THROW(std::bad_alloc()); - } - } - - // Disable copying and copy assignment - BlockingConcurrentQueue(BlockingConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; - BlockingConcurrentQueue& operator=(BlockingConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; - - // Moving is supported, but note that it is *not* a thread-safe operation. - // Nobody can use the queue while it's being moved, and the memory effects - // of that move must be propagated to other threads before they can use it. - // Note: When a queue is moved, its tokens are still valid but can only be - // used with the destination queue (i.e. semantically they are moved along - // with the queue itself). - BlockingConcurrentQueue(BlockingConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT - : inner(std::move(other.inner)), sema(std::move(other.sema)) - { } - - inline BlockingConcurrentQueue& operator=(BlockingConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT - { - return swap_internal(other); - } - - // Swaps this queue's state with the other's. Not thread-safe. - // Swapping two queues does not invalidate their tokens, however - // the tokens that were created for one queue must be used with - // only the swapped queue (i.e. the tokens are tied to the - // queue's movable state, not the object itself). - inline void swap(BlockingConcurrentQueue& other) MOODYCAMEL_NOEXCEPT - { - swap_internal(other); - } - -private: - BlockingConcurrentQueue& swap_internal(BlockingConcurrentQueue& other) - { - if (this == &other) { - return *this; - } - - inner.swap(other.inner); - sema.swap(other.sema); - return *this; - } - -public: - // Enqueues a single item (by copying it). - // Allocates memory if required. Only fails if memory allocation fails (or implicit - // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, - // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). - // Thread-safe. - inline bool enqueue(T const& item) - { - if ((details::likely)(inner.enqueue(item))) { - sema->signal(); - return true; - } - return false; - } - - // Enqueues a single item (by moving it, if possible). - // Allocates memory if required. Only fails if memory allocation fails (or implicit - // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, - // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). - // Thread-safe. - inline bool enqueue(T&& item) - { - if ((details::likely)(inner.enqueue(std::move(item)))) { - sema->signal(); - return true; - } - return false; - } - - // Enqueues a single item (by copying it) using an explicit producer token. - // Allocates memory if required. Only fails if memory allocation fails (or - // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). - // Thread-safe. - inline bool enqueue(producer_token_t const& token, T const& item) - { - if ((details::likely)(inner.enqueue(token, item))) { - sema->signal(); - return true; - } - return false; - } - - // Enqueues a single item (by moving it, if possible) using an explicit producer token. - // Allocates memory if required. Only fails if memory allocation fails (or - // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). - // Thread-safe. - inline bool enqueue(producer_token_t const& token, T&& item) - { - if ((details::likely)(inner.enqueue(token, std::move(item)))) { - sema->signal(); - return true; - } - return false; - } - - // Enqueues several items. - // Allocates memory if required. Only fails if memory allocation fails (or - // implicit production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE - // is 0, or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). - // Note: Use std::make_move_iterator if the elements should be moved instead of copied. - // Thread-safe. - template - inline bool enqueue_bulk(It itemFirst, size_t count) - { - if ((details::likely)(inner.enqueue_bulk(std::forward(itemFirst), count))) { - sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count); - return true; - } - return false; - } - - // Enqueues several items using an explicit producer token. - // Allocates memory if required. Only fails if memory allocation fails - // (or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). - // Note: Use std::make_move_iterator if the elements should be moved - // instead of copied. - // Thread-safe. - template - inline bool enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) - { - if ((details::likely)(inner.enqueue_bulk(token, std::forward(itemFirst), count))) { - sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count); - return true; - } - return false; - } - - // Enqueues a single item (by copying it). - // Does not allocate memory. Fails if not enough room to enqueue (or implicit - // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE - // is 0). - // Thread-safe. - inline bool try_enqueue(T const& item) - { - if (inner.try_enqueue(item)) { - sema->signal(); - return true; - } - return false; - } - - // Enqueues a single item (by moving it, if possible). - // Does not allocate memory (except for one-time implicit producer). - // Fails if not enough room to enqueue (or implicit production is - // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). - // Thread-safe. - inline bool try_enqueue(T&& item) - { - if (inner.try_enqueue(std::move(item))) { - sema->signal(); - return true; - } - return false; - } - - // Enqueues a single item (by copying it) using an explicit producer token. - // Does not allocate memory. Fails if not enough room to enqueue. - // Thread-safe. - inline bool try_enqueue(producer_token_t const& token, T const& item) - { - if (inner.try_enqueue(token, item)) { - sema->signal(); - return true; - } - return false; - } - - // Enqueues a single item (by moving it, if possible) using an explicit producer token. - // Does not allocate memory. Fails if not enough room to enqueue. - // Thread-safe. - inline bool try_enqueue(producer_token_t const& token, T&& item) - { - if (inner.try_enqueue(token, std::move(item))) { - sema->signal(); - return true; - } - return false; - } - - // Enqueues several items. - // Does not allocate memory (except for one-time implicit producer). - // Fails if not enough room to enqueue (or implicit production is - // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). - // Note: Use std::make_move_iterator if the elements should be moved - // instead of copied. - // Thread-safe. - template - inline bool try_enqueue_bulk(It itemFirst, size_t count) - { - if (inner.try_enqueue_bulk(std::forward(itemFirst), count)) { - sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count); - return true; - } - return false; - } - - // Enqueues several items using an explicit producer token. - // Does not allocate memory. Fails if not enough room to enqueue. - // Note: Use std::make_move_iterator if the elements should be moved - // instead of copied. - // Thread-safe. - template - inline bool try_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) - { - if (inner.try_enqueue_bulk(token, std::forward(itemFirst), count)) { - sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count); - return true; - } - return false; - } - - - // Attempts to dequeue from the queue. - // Returns false if all producer streams appeared empty at the time they - // were checked (so, the queue is likely but not guaranteed to be empty). - // Never allocates. Thread-safe. - template - inline bool try_dequeue(U& item) - { - if (sema->tryWait()) { - while (!inner.try_dequeue(item)) { - continue; - } - return true; - } - return false; - } - - // Attempts to dequeue from the queue using an explicit consumer token. - // Returns false if all producer streams appeared empty at the time they - // were checked (so, the queue is likely but not guaranteed to be empty). - // Never allocates. Thread-safe. - template - inline bool try_dequeue(consumer_token_t& token, U& item) - { - if (sema->tryWait()) { - while (!inner.try_dequeue(token, item)) { - continue; - } - return true; - } - return false; - } - - // Attempts to dequeue several elements from the queue. - // Returns the number of items actually dequeued. - // Returns 0 if all producer streams appeared empty at the time they - // were checked (so, the queue is likely but not guaranteed to be empty). - // Never allocates. Thread-safe. - template - inline size_t try_dequeue_bulk(It itemFirst, size_t max) - { - size_t count = 0; - max = (size_t)sema->tryWaitMany((LightweightSemaphore::ssize_t)(ssize_t)max); - while (count != max) { - count += inner.template try_dequeue_bulk(itemFirst, max - count); - } - return count; - } - - // Attempts to dequeue several elements from the queue using an explicit consumer token. - // Returns the number of items actually dequeued. - // Returns 0 if all producer streams appeared empty at the time they - // were checked (so, the queue is likely but not guaranteed to be empty). - // Never allocates. Thread-safe. - template - inline size_t try_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max) - { - size_t count = 0; - max = (size_t)sema->tryWaitMany((LightweightSemaphore::ssize_t)(ssize_t)max); - while (count != max) { - count += inner.template try_dequeue_bulk(token, itemFirst, max - count); - } - return count; - } - - - - // Blocks the current thread until there's something to dequeue, then - // dequeues it. - // Never allocates. Thread-safe. - template - inline void wait_dequeue(U& item) - { - while (!sema->wait()) { - continue; - } - while (!inner.try_dequeue(item)) { - continue; - } - } - - // Blocks the current thread until either there's something to dequeue - // or the timeout (specified in microseconds) expires. Returns false - // without setting `item` if the timeout expires, otherwise assigns - // to `item` and returns true. - // Using a negative timeout indicates an indefinite timeout, - // and is thus functionally equivalent to calling wait_dequeue. - // Never allocates. Thread-safe. - template - inline bool wait_dequeue_timed(U& item, std::int64_t timeout_usecs) - { - if (!sema->wait(timeout_usecs)) { - return false; - } - while (!inner.try_dequeue(item)) { - continue; - } - return true; - } - - // Blocks the current thread until either there's something to dequeue - // or the timeout expires. Returns false without setting `item` if the - // timeout expires, otherwise assigns to `item` and returns true. - // Never allocates. Thread-safe. - template - inline bool wait_dequeue_timed(U& item, std::chrono::duration const& timeout) - { - return wait_dequeue_timed(item, std::chrono::duration_cast(timeout).count()); - } - - // Blocks the current thread until there's something to dequeue, then - // dequeues it using an explicit consumer token. - // Never allocates. Thread-safe. - template - inline void wait_dequeue(consumer_token_t& token, U& item) - { - while (!sema->wait()) { - continue; - } - while (!inner.try_dequeue(token, item)) { - continue; - } - } - - // Blocks the current thread until either there's something to dequeue - // or the timeout (specified in microseconds) expires. Returns false - // without setting `item` if the timeout expires, otherwise assigns - // to `item` and returns true. - // Using a negative timeout indicates an indefinite timeout, - // and is thus functionally equivalent to calling wait_dequeue. - // Never allocates. Thread-safe. - template - inline bool wait_dequeue_timed(consumer_token_t& token, U& item, std::int64_t timeout_usecs) - { - if (!sema->wait(timeout_usecs)) { - return false; - } - while (!inner.try_dequeue(token, item)) { - continue; - } - return true; - } - - // Blocks the current thread until either there's something to dequeue - // or the timeout expires. Returns false without setting `item` if the - // timeout expires, otherwise assigns to `item` and returns true. - // Never allocates. Thread-safe. - template - inline bool wait_dequeue_timed(consumer_token_t& token, U& item, std::chrono::duration const& timeout) - { - return wait_dequeue_timed(token, item, std::chrono::duration_cast(timeout).count()); - } - - // Attempts to dequeue several elements from the queue. - // Returns the number of items actually dequeued, which will - // always be at least one (this method blocks until the queue - // is non-empty) and at most max. - // Never allocates. Thread-safe. - template - inline size_t wait_dequeue_bulk(It itemFirst, size_t max) - { - size_t count = 0; - max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max); - while (count != max) { - count += inner.template try_dequeue_bulk(itemFirst, max - count); - } - return count; - } - - // Attempts to dequeue several elements from the queue. - // Returns the number of items actually dequeued, which can - // be 0 if the timeout expires while waiting for elements, - // and at most max. - // Using a negative timeout indicates an indefinite timeout, - // and is thus functionally equivalent to calling wait_dequeue_bulk. - // Never allocates. Thread-safe. - template - inline size_t wait_dequeue_bulk_timed(It itemFirst, size_t max, std::int64_t timeout_usecs) - { - size_t count = 0; - max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max, timeout_usecs); - while (count != max) { - count += inner.template try_dequeue_bulk(itemFirst, max - count); - } - return count; - } - - // Attempts to dequeue several elements from the queue. - // Returns the number of items actually dequeued, which can - // be 0 if the timeout expires while waiting for elements, - // and at most max. - // Never allocates. Thread-safe. - template - inline size_t wait_dequeue_bulk_timed(It itemFirst, size_t max, std::chrono::duration const& timeout) - { - return wait_dequeue_bulk_timed(itemFirst, max, std::chrono::duration_cast(timeout).count()); - } - - // Attempts to dequeue several elements from the queue using an explicit consumer token. - // Returns the number of items actually dequeued, which will - // always be at least one (this method blocks until the queue - // is non-empty) and at most max. - // Never allocates. Thread-safe. - template - inline size_t wait_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max) - { - size_t count = 0; - max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max); - while (count != max) { - count += inner.template try_dequeue_bulk(token, itemFirst, max - count); - } - return count; - } - - // Attempts to dequeue several elements from the queue using an explicit consumer token. - // Returns the number of items actually dequeued, which can - // be 0 if the timeout expires while waiting for elements, - // and at most max. - // Using a negative timeout indicates an indefinite timeout, - // and is thus functionally equivalent to calling wait_dequeue_bulk. - // Never allocates. Thread-safe. - template - inline size_t wait_dequeue_bulk_timed(consumer_token_t& token, It itemFirst, size_t max, std::int64_t timeout_usecs) - { - size_t count = 0; - max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max, timeout_usecs); - while (count != max) { - count += inner.template try_dequeue_bulk(token, itemFirst, max - count); - } - return count; - } - - // Attempts to dequeue several elements from the queue using an explicit consumer token. - // Returns the number of items actually dequeued, which can - // be 0 if the timeout expires while waiting for elements, - // and at most max. - // Never allocates. Thread-safe. - template - inline size_t wait_dequeue_bulk_timed(consumer_token_t& token, It itemFirst, size_t max, std::chrono::duration const& timeout) - { - return wait_dequeue_bulk_timed(token, itemFirst, max, std::chrono::duration_cast(timeout).count()); - } - - - // Returns an estimate of the total number of elements currently in the queue. This - // estimate is only accurate if the queue has completely stabilized before it is called - // (i.e. all enqueue and dequeue operations have completed and their memory effects are - // visible on the calling thread, and no further operations start while this method is - // being called). - // Thread-safe. - inline size_t size_approx() const - { - return (size_t)sema->availableApprox(); - } - - - // Returns true if the underlying atomic variables used by - // the queue are lock-free (they should be on most platforms). - // Thread-safe. - static constexpr bool is_lock_free() - { - return ConcurrentQueue::is_lock_free(); - } - - -private: - template - static inline U* create(A1&& a1, A2&& a2) - { - void* p = (Traits::malloc)(sizeof(U)); - return p != nullptr ? new (p) U(std::forward(a1), std::forward(a2)) : nullptr; - } - - template - static inline void destroy(U* p) - { - if (p != nullptr) { - p->~U(); - } - (Traits::free)(p); - } - -private: - ConcurrentQueue inner; - std::unique_ptr sema; -}; - - -template -inline void swap(BlockingConcurrentQueue& a, BlockingConcurrentQueue& b) MOODYCAMEL_NOEXCEPT -{ - a.swap(b); -} - -} // end namespace moodycamel diff --git a/lib/concurrentqueue/concurrentqueue.h b/lib/concurrentqueue/concurrentqueue.h deleted file mode 100644 index bcb06a1..0000000 --- a/lib/concurrentqueue/concurrentqueue.h +++ /dev/null @@ -1,3747 +0,0 @@ -// Provides a C++11 implementation of a multi-producer, multi-consumer lock-free queue. -// An overview, including benchmark results, is provided here: -// http://moodycamel.com/blog/2014/a-fast-general-purpose-lock-free-queue-for-c++ -// The full design is also described in excruciating detail at: -// http://moodycamel.com/blog/2014/detailed-design-of-a-lock-free-queue - -// Simplified BSD license: -// Copyright (c) 2013-2020, Cameron Desrochers. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// -// - Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// - Redistributions in binary form must reproduce the above copyright notice, this list of -// conditions and the following disclaimer in the documentation and/or other materials -// provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT -// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// Also dual-licensed under the Boost Software License (see LICENSE.md) - -#pragma once - -#if defined(__GNUC__) && !defined(__INTEL_COMPILER) -// Disable -Wconversion warnings (spuriously triggered when Traits::size_t and -// Traits::index_t are set to < 32 bits, causing integer promotion, causing warnings -// upon assigning any computed values) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wconversion" - -#ifdef MCDBGQ_USE_RELACY -#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" -#endif -#endif - -#if defined(_MSC_VER) && (!defined(_HAS_CXX17) || !_HAS_CXX17) -// VS2019 with /W4 warns about constant conditional expressions but unless /std=c++17 or higher -// does not support `if constexpr`, so we have no choice but to simply disable the warning -#pragma warning(push) -#pragma warning(disable: 4127) // conditional expression is constant -#endif - -#if defined(__APPLE__) -#include "TargetConditionals.h" -#endif - -#ifdef MCDBGQ_USE_RELACY -#include "relacy/relacy_std.hpp" -#include "relacy_shims.h" -// We only use malloc/free anyway, and the delete macro messes up `= delete` method declarations. -// We'll override the default trait malloc ourselves without a macro. -#undef new -#undef delete -#undef malloc -#undef free -#else -#include // Requires C++11. Sorry VS2010. -#include -#endif -#include // for max_align_t -#include -#include -#include -#include -#include -#include -#include // for CHAR_BIT -#include -#include // partly for __WINPTHREADS_VERSION if on MinGW-w64 w/ POSIX threading -#include // used for thread exit synchronization - -// Platform-specific definitions of a numeric thread ID type and an invalid value -namespace moodycamel { namespace details { - template struct thread_id_converter { - typedef thread_id_t thread_id_numeric_size_t; - typedef thread_id_t thread_id_hash_t; - static thread_id_hash_t prehash(thread_id_t const& x) { return x; } - }; -} } -#if defined(MCDBGQ_USE_RELACY) -namespace moodycamel { namespace details { - typedef std::uint32_t thread_id_t; - static const thread_id_t invalid_thread_id = 0xFFFFFFFFU; - static const thread_id_t invalid_thread_id2 = 0xFFFFFFFEU; - static inline thread_id_t thread_id() { return rl::thread_index(); } -} } -#elif defined(_WIN32) || defined(__WINDOWS__) || defined(__WIN32__) -// No sense pulling in windows.h in a header, we'll manually declare the function -// we use and rely on backwards-compatibility for this not to break -extern "C" __declspec(dllimport) unsigned long __stdcall GetCurrentThreadId(void); -namespace moodycamel { namespace details { - static_assert(sizeof(unsigned long) == sizeof(std::uint32_t), "Expected size of unsigned long to be 32 bits on Windows"); - typedef std::uint32_t thread_id_t; - static const thread_id_t invalid_thread_id = 0; // See http://blogs.msdn.com/b/oldnewthing/archive/2004/02/23/78395.aspx - static const thread_id_t invalid_thread_id2 = 0xFFFFFFFFU; // Not technically guaranteed to be invalid, but is never used in practice. Note that all Win32 thread IDs are presently multiples of 4. - static inline thread_id_t thread_id() { return static_cast(::GetCurrentThreadId()); } -} } -#elif defined(__arm__) || defined(_M_ARM) || defined(__aarch64__) || (defined(__APPLE__) && TARGET_OS_IPHONE) || defined(MOODYCAMEL_NO_THREAD_LOCAL) -namespace moodycamel { namespace details { - static_assert(sizeof(std::thread::id) == 4 || sizeof(std::thread::id) == 8, "std::thread::id is expected to be either 4 or 8 bytes"); - - typedef std::thread::id thread_id_t; - static const thread_id_t invalid_thread_id; // Default ctor creates invalid ID - - // Note we don't define a invalid_thread_id2 since std::thread::id doesn't have one; it's - // only used if MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED is defined anyway, which it won't - // be. - static inline thread_id_t thread_id() { return std::this_thread::get_id(); } - - template struct thread_id_size { }; - template<> struct thread_id_size<4> { typedef std::uint32_t numeric_t; }; - template<> struct thread_id_size<8> { typedef std::uint64_t numeric_t; }; - - template<> struct thread_id_converter { - typedef thread_id_size::numeric_t thread_id_numeric_size_t; -#ifndef __APPLE__ - typedef std::size_t thread_id_hash_t; -#else - typedef thread_id_numeric_size_t thread_id_hash_t; -#endif - - static thread_id_hash_t prehash(thread_id_t const& x) - { -#ifndef __APPLE__ - return std::hash()(x); -#else - return *reinterpret_cast(&x); -#endif - } - }; -} } -#else -// Use a nice trick from this answer: http://stackoverflow.com/a/8438730/21475 -// In order to get a numeric thread ID in a platform-independent way, we use a thread-local -// static variable's address as a thread identifier :-) -#if defined(__GNUC__) || defined(__INTEL_COMPILER) -#define MOODYCAMEL_THREADLOCAL __thread -#elif defined(_MSC_VER) -#define MOODYCAMEL_THREADLOCAL __declspec(thread) -#else -// Assume C++11 compliant compiler -#define MOODYCAMEL_THREADLOCAL thread_local -#endif -namespace moodycamel { namespace details { - typedef std::uintptr_t thread_id_t; - static const thread_id_t invalid_thread_id = 0; // Address can't be nullptr - static const thread_id_t invalid_thread_id2 = 1; // Member accesses off a null pointer are also generally invalid. Plus it's not aligned. - inline thread_id_t thread_id() { static MOODYCAMEL_THREADLOCAL int x; return reinterpret_cast(&x); } -} } -#endif - -// Constexpr if -#ifndef MOODYCAMEL_CONSTEXPR_IF -#if (defined(_MSC_VER) && defined(_HAS_CXX17) && _HAS_CXX17) || __cplusplus > 201402L -#define MOODYCAMEL_CONSTEXPR_IF if constexpr -#define MOODYCAMEL_MAYBE_UNUSED [[maybe_unused]] -#else -#define MOODYCAMEL_CONSTEXPR_IF if -#define MOODYCAMEL_MAYBE_UNUSED -#endif -#endif - -// Exceptions -#ifndef MOODYCAMEL_EXCEPTIONS_ENABLED -#if (defined(_MSC_VER) && defined(_CPPUNWIND)) || (defined(__GNUC__) && defined(__EXCEPTIONS)) || (!defined(_MSC_VER) && !defined(__GNUC__)) -#define MOODYCAMEL_EXCEPTIONS_ENABLED -#endif -#endif -#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED -#define MOODYCAMEL_TRY try -#define MOODYCAMEL_CATCH(...) catch(__VA_ARGS__) -#define MOODYCAMEL_RETHROW throw -#define MOODYCAMEL_THROW(expr) throw (expr) -#else -#define MOODYCAMEL_TRY MOODYCAMEL_CONSTEXPR_IF (true) -#define MOODYCAMEL_CATCH(...) else MOODYCAMEL_CONSTEXPR_IF (false) -#define MOODYCAMEL_RETHROW -#define MOODYCAMEL_THROW(expr) -#endif - -#ifndef MOODYCAMEL_NOEXCEPT -#if !defined(MOODYCAMEL_EXCEPTIONS_ENABLED) -#define MOODYCAMEL_NOEXCEPT -#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) true -#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) true -#elif defined(_MSC_VER) && defined(_NOEXCEPT) && _MSC_VER < 1800 -// VS2012's std::is_nothrow_[move_]constructible is broken and returns true when it shouldn't :-( -// We have to assume *all* non-trivial constructors may throw on VS2012! -#define MOODYCAMEL_NOEXCEPT _NOEXCEPT -#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) (std::is_rvalue_reference::value && std::is_move_constructible::value ? std::is_trivially_move_constructible::value : std::is_trivially_copy_constructible::value) -#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) ((std::is_rvalue_reference::value && std::is_move_assignable::value ? std::is_trivially_move_assignable::value || std::is_nothrow_move_assignable::value : std::is_trivially_copy_assignable::value || std::is_nothrow_copy_assignable::value) && MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr)) -#elif defined(_MSC_VER) && defined(_NOEXCEPT) && _MSC_VER < 1900 -#define MOODYCAMEL_NOEXCEPT _NOEXCEPT -#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) (std::is_rvalue_reference::value && std::is_move_constructible::value ? std::is_trivially_move_constructible::value || std::is_nothrow_move_constructible::value : std::is_trivially_copy_constructible::value || std::is_nothrow_copy_constructible::value) -#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) ((std::is_rvalue_reference::value && std::is_move_assignable::value ? std::is_trivially_move_assignable::value || std::is_nothrow_move_assignable::value : std::is_trivially_copy_assignable::value || std::is_nothrow_copy_assignable::value) && MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr)) -#else -#define MOODYCAMEL_NOEXCEPT noexcept -#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) noexcept(expr) -#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) noexcept(expr) -#endif -#endif - -#ifndef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED -#ifdef MCDBGQ_USE_RELACY -#define MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED -#else -// VS2013 doesn't support `thread_local`, and MinGW-w64 w/ POSIX threading has a crippling bug: http://sourceforge.net/p/mingw-w64/bugs/445 -// g++ <=4.7 doesn't support thread_local either. -// Finally, iOS/ARM doesn't have support for it either, and g++/ARM allows it to compile but it's unconfirmed to actually work -#if (!defined(_MSC_VER) || _MSC_VER >= 1900) && (!defined(__MINGW32__) && !defined(__MINGW64__) || !defined(__WINPTHREADS_VERSION)) && (!defined(__GNUC__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) && (!defined(__APPLE__) || !TARGET_OS_IPHONE) && !defined(__arm__) && !defined(_M_ARM) && !defined(__aarch64__) -// Assume `thread_local` is fully supported in all other C++11 compilers/platforms -#define MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED // tentatively enabled for now; years ago several users report having problems with it on -#endif -#endif -#endif - -// VS2012 doesn't support deleted functions. -// In this case, we declare the function normally but don't define it. A link error will be generated if the function is called. -#ifndef MOODYCAMEL_DELETE_FUNCTION -#if defined(_MSC_VER) && _MSC_VER < 1800 -#define MOODYCAMEL_DELETE_FUNCTION -#else -#define MOODYCAMEL_DELETE_FUNCTION = delete -#endif -#endif - -namespace moodycamel { namespace details { -#ifndef MOODYCAMEL_ALIGNAS -// VS2013 doesn't support alignas or alignof, and align() requires a constant literal -#if defined(_MSC_VER) && _MSC_VER <= 1800 -#define MOODYCAMEL_ALIGNAS(alignment) __declspec(align(alignment)) -#define MOODYCAMEL_ALIGNOF(obj) __alignof(obj) -#define MOODYCAMEL_ALIGNED_TYPE_LIKE(T, obj) typename details::Vs2013Aligned::value, T>::type - template struct Vs2013Aligned { }; // default, unsupported alignment - template struct Vs2013Aligned<1, T> { typedef __declspec(align(1)) T type; }; - template struct Vs2013Aligned<2, T> { typedef __declspec(align(2)) T type; }; - template struct Vs2013Aligned<4, T> { typedef __declspec(align(4)) T type; }; - template struct Vs2013Aligned<8, T> { typedef __declspec(align(8)) T type; }; - template struct Vs2013Aligned<16, T> { typedef __declspec(align(16)) T type; }; - template struct Vs2013Aligned<32, T> { typedef __declspec(align(32)) T type; }; - template struct Vs2013Aligned<64, T> { typedef __declspec(align(64)) T type; }; - template struct Vs2013Aligned<128, T> { typedef __declspec(align(128)) T type; }; - template struct Vs2013Aligned<256, T> { typedef __declspec(align(256)) T type; }; -#else - template struct identity { typedef T type; }; -#define MOODYCAMEL_ALIGNAS(alignment) alignas(alignment) -#define MOODYCAMEL_ALIGNOF(obj) alignof(obj) -#define MOODYCAMEL_ALIGNED_TYPE_LIKE(T, obj) alignas(alignof(obj)) typename details::identity::type -#endif -#endif -} } - - -// TSAN can false report races in lock-free code. To enable TSAN to be used from projects that use this one, -// we can apply per-function compile-time suppression. -// See https://clang.llvm.org/docs/ThreadSanitizer.html#has-feature-thread-sanitizer -#define MOODYCAMEL_NO_TSAN -#if defined(__has_feature) - #if __has_feature(thread_sanitizer) - #undef MOODYCAMEL_NO_TSAN - #define MOODYCAMEL_NO_TSAN __attribute__((no_sanitize("thread"))) - #endif // TSAN -#endif // TSAN - -// Compiler-specific likely/unlikely hints -namespace moodycamel { namespace details { -#if defined(__GNUC__) - static inline bool (likely)(bool x) { return __builtin_expect((x), true); } - static inline bool (unlikely)(bool x) { return __builtin_expect((x), false); } -#else - static inline bool (likely)(bool x) { return x; } - static inline bool (unlikely)(bool x) { return x; } -#endif -} } - -#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG -#include "internal/concurrentqueue_internal_debug.h" -#endif - -namespace moodycamel { -namespace details { - template - struct const_numeric_max { - static_assert(std::is_integral::value, "const_numeric_max can only be used with integers"); - static const T value = std::numeric_limits::is_signed - ? (static_cast(1) << (sizeof(T) * CHAR_BIT - 1)) - static_cast(1) - : static_cast(-1); - }; - -#if defined(__GLIBCXX__) - typedef ::max_align_t std_max_align_t; // libstdc++ forgot to add it to std:: for a while -#else - typedef std::max_align_t std_max_align_t; // Others (e.g. MSVC) insist it can *only* be accessed via std:: -#endif - - // Some platforms have incorrectly set max_align_t to a type with <8 bytes alignment even while supporting - // 8-byte aligned scalar values (*cough* 32-bit iOS). Work around this with our own union. See issue #64. - typedef union { - std_max_align_t x; - long long y; - void* z; - } max_align_t; -} - -// Default traits for the ConcurrentQueue. To change some of the -// traits without re-implementing all of them, inherit from this -// struct and shadow the declarations you wish to be different; -// since the traits are used as a template type parameter, the -// shadowed declarations will be used where defined, and the defaults -// otherwise. -struct ConcurrentQueueDefaultTraits -{ - // General-purpose size type. std::size_t is strongly recommended. - typedef std::size_t size_t; - - // The type used for the enqueue and dequeue indices. Must be at least as - // large as size_t. Should be significantly larger than the number of elements - // you expect to hold at once, especially if you have a high turnover rate; - // for example, on 32-bit x86, if you expect to have over a hundred million - // elements or pump several million elements through your queue in a very - // short space of time, using a 32-bit type *may* trigger a race condition. - // A 64-bit int type is recommended in that case, and in practice will - // prevent a race condition no matter the usage of the queue. Note that - // whether the queue is lock-free with a 64-int type depends on the whether - // std::atomic is lock-free, which is platform-specific. - typedef std::size_t index_t; - - // Internally, all elements are enqueued and dequeued from multi-element - // blocks; this is the smallest controllable unit. If you expect few elements - // but many producers, a smaller block size should be favoured. For few producers - // and/or many elements, a larger block size is preferred. A sane default - // is provided. Must be a power of 2. - static const size_t BLOCK_SIZE = 32; - - // For explicit producers (i.e. when using a producer token), the block is - // checked for being empty by iterating through a list of flags, one per element. - // For large block sizes, this is too inefficient, and switching to an atomic - // counter-based approach is faster. The switch is made for block sizes strictly - // larger than this threshold. - static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = 32; - - // How many full blocks can be expected for a single explicit producer? This should - // reflect that number's maximum for optimal performance. Must be a power of 2. - static const size_t EXPLICIT_INITIAL_INDEX_SIZE = 32; - - // How many full blocks can be expected for a single implicit producer? This should - // reflect that number's maximum for optimal performance. Must be a power of 2. - static const size_t IMPLICIT_INITIAL_INDEX_SIZE = 32; - - // The initial size of the hash table mapping thread IDs to implicit producers. - // Note that the hash is resized every time it becomes half full. - // Must be a power of two, and either 0 or at least 1. If 0, implicit production - // (using the enqueue methods without an explicit producer token) is disabled. - static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = 32; - - // Controls the number of items that an explicit consumer (i.e. one with a token) - // must consume before it causes all consumers to rotate and move on to the next - // internal queue. - static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = 256; - - // The maximum number of elements (inclusive) that can be enqueued to a sub-queue. - // Enqueue operations that would cause this limit to be surpassed will fail. Note - // that this limit is enforced at the block level (for performance reasons), i.e. - // it's rounded up to the nearest block size. - static const size_t MAX_SUBQUEUE_SIZE = details::const_numeric_max::value; - - // The number of times to spin before sleeping when waiting on a semaphore. - // Recommended values are on the order of 1000-10000 unless the number of - // consumer threads exceeds the number of idle cores (in which case try 0-100). - // Only affects instances of the BlockingConcurrentQueue. - static const int MAX_SEMA_SPINS = 100; - - // Whether to recycle dynamically-allocated blocks into an internal free list or - // not. If false, only pre-allocated blocks (controlled by the constructor - // arguments) will be recycled, and all others will be `free`d back to the heap. - // Note that blocks consumed by explicit producers are only freed on destruction - // of the queue (not following destruction of the token) regardless of this trait. - static const bool RECYCLE_ALLOCATED_BLOCKS = false; - - -#ifndef MCDBGQ_USE_RELACY - // Memory allocation can be customized if needed. - // malloc should return nullptr on failure, and handle alignment like std::malloc. -#if defined(malloc) || defined(free) - // Gah, this is 2015, stop defining macros that break standard code already! - // Work around malloc/free being special macros: - static inline void* WORKAROUND_malloc(size_t size) { return malloc(size); } - static inline void WORKAROUND_free(void* ptr) { return free(ptr); } - static inline void* (malloc)(size_t size) { return WORKAROUND_malloc(size); } - static inline void (free)(void* ptr) { return WORKAROUND_free(ptr); } -#else - static inline void* malloc(size_t size) { return std::malloc(size); } - static inline void free(void* ptr) { return std::free(ptr); } -#endif -#else - // Debug versions when running under the Relacy race detector (ignore - // these in user code) - static inline void* malloc(size_t size) { return rl::rl_malloc(size, $); } - static inline void free(void* ptr) { return rl::rl_free(ptr, $); } -#endif -}; - - -// When producing or consuming many elements, the most efficient way is to: -// 1) Use one of the bulk-operation methods of the queue with a token -// 2) Failing that, use the bulk-operation methods without a token -// 3) Failing that, create a token and use that with the single-item methods -// 4) Failing that, use the single-parameter methods of the queue -// Having said that, don't create tokens willy-nilly -- ideally there should be -// a maximum of one token per thread (of each kind). -struct ProducerToken; -struct ConsumerToken; - -template class ConcurrentQueue; -template class BlockingConcurrentQueue; -class ConcurrentQueueTests; - - -namespace details -{ - struct ConcurrentQueueProducerTypelessBase - { - ConcurrentQueueProducerTypelessBase* next; - std::atomic inactive; - ProducerToken* token; - - ConcurrentQueueProducerTypelessBase() - : next(nullptr), inactive(false), token(nullptr) - { - } - }; - - template struct _hash_32_or_64 { - static inline std::uint32_t hash(std::uint32_t h) - { - // MurmurHash3 finalizer -- see https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp - // Since the thread ID is already unique, all we really want to do is propagate that - // uniqueness evenly across all the bits, so that we can use a subset of the bits while - // reducing collisions significantly - h ^= h >> 16; - h *= 0x85ebca6b; - h ^= h >> 13; - h *= 0xc2b2ae35; - return h ^ (h >> 16); - } - }; - template<> struct _hash_32_or_64<1> { - static inline std::uint64_t hash(std::uint64_t h) - { - h ^= h >> 33; - h *= 0xff51afd7ed558ccd; - h ^= h >> 33; - h *= 0xc4ceb9fe1a85ec53; - return h ^ (h >> 33); - } - }; - template struct hash_32_or_64 : public _hash_32_or_64<(size > 4)> { }; - - static inline size_t hash_thread_id(thread_id_t id) - { - static_assert(sizeof(thread_id_t) <= 8, "Expected a platform where thread IDs are at most 64-bit values"); - return static_cast(hash_32_or_64::thread_id_hash_t)>::hash( - thread_id_converter::prehash(id))); - } - - template - static inline bool circular_less_than(T a, T b) - { - static_assert(std::is_integral::value && !std::numeric_limits::is_signed, "circular_less_than is intended to be used only with unsigned integer types"); - return static_cast(a - b) > static_cast(static_cast(1) << (static_cast(sizeof(T) * CHAR_BIT - 1))); - // Note: extra parens around rhs of operator<< is MSVC bug: https://developercommunity2.visualstudio.com/t/C4554-triggers-when-both-lhs-and-rhs-is/10034931 - // silencing the bug requires #pragma warning(disable: 4554) around the calling code and has no effect when done here. - } - - template - static inline char* align_for(char* ptr) - { - const std::size_t alignment = std::alignment_of::value; - return ptr + (alignment - (reinterpret_cast(ptr) % alignment)) % alignment; - } - - template - static inline T ceil_to_pow_2(T x) - { - static_assert(std::is_integral::value && !std::numeric_limits::is_signed, "ceil_to_pow_2 is intended to be used only with unsigned integer types"); - - // Adapted from http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 - --x; - x |= x >> 1; - x |= x >> 2; - x |= x >> 4; - for (std::size_t i = 1; i < sizeof(T); i <<= 1) { - x |= x >> (i << 3); - } - ++x; - return x; - } - - template - static inline void swap_relaxed(std::atomic& left, std::atomic& right) - { - T temp = std::move(left.load(std::memory_order_relaxed)); - left.store(std::move(right.load(std::memory_order_relaxed)), std::memory_order_relaxed); - right.store(std::move(temp), std::memory_order_relaxed); - } - - template - static inline T const& nomove(T const& x) - { - return x; - } - - template - struct nomove_if - { - template - static inline T const& eval(T const& x) - { - return x; - } - }; - - template<> - struct nomove_if - { - template - static inline auto eval(U&& x) - -> decltype(std::forward(x)) - { - return std::forward(x); - } - }; - - template - static inline auto deref_noexcept(It& it) MOODYCAMEL_NOEXCEPT -> decltype(*it) - { - return *it; - } - -#if defined(__clang__) || !defined(__GNUC__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) - template struct is_trivially_destructible : std::is_trivially_destructible { }; -#else - template struct is_trivially_destructible : std::has_trivial_destructor { }; -#endif - -#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED -#ifdef MCDBGQ_USE_RELACY - typedef RelacyThreadExitListener ThreadExitListener; - typedef RelacyThreadExitNotifier ThreadExitNotifier; -#else - class ThreadExitNotifier; - - struct ThreadExitListener - { - typedef void (*callback_t)(void*); - callback_t callback; - void* userData; - - ThreadExitListener* next; // reserved for use by the ThreadExitNotifier - ThreadExitNotifier* chain; // reserved for use by the ThreadExitNotifier - }; - - class ThreadExitNotifier - { - public: - static void subscribe(ThreadExitListener* listener) - { - auto& tlsInst = instance(); - std::lock_guard guard(mutex()); - listener->next = tlsInst.tail; - listener->chain = &tlsInst; - tlsInst.tail = listener; - } - - static void unsubscribe(ThreadExitListener* listener) - { - std::lock_guard guard(mutex()); - if (!listener->chain) { - return; // race with ~ThreadExitNotifier - } - auto& tlsInst = *listener->chain; - listener->chain = nullptr; - ThreadExitListener** prev = &tlsInst.tail; - for (auto ptr = tlsInst.tail; ptr != nullptr; ptr = ptr->next) { - if (ptr == listener) { - *prev = ptr->next; - break; - } - prev = &ptr->next; - } - } - - private: - ThreadExitNotifier() : tail(nullptr) { } - ThreadExitNotifier(ThreadExitNotifier const&) MOODYCAMEL_DELETE_FUNCTION; - ThreadExitNotifier& operator=(ThreadExitNotifier const&) MOODYCAMEL_DELETE_FUNCTION; - - ~ThreadExitNotifier() - { - // This thread is about to exit, let everyone know! - assert(this == &instance() && "If this assert fails, you likely have a buggy compiler! Change the preprocessor conditions such that MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED is no longer defined."); - std::lock_guard guard(mutex()); - for (auto ptr = tail; ptr != nullptr; ptr = ptr->next) { - ptr->chain = nullptr; - ptr->callback(ptr->userData); - } - } - - // Thread-local - static inline ThreadExitNotifier& instance() - { - static thread_local ThreadExitNotifier notifier; - return notifier; - } - - static inline std::mutex& mutex() - { - // Must be static because the ThreadExitNotifier could be destroyed while unsubscribe is called - static std::mutex mutex; - return mutex; - } - - private: - ThreadExitListener* tail; - }; -#endif -#endif - - template struct static_is_lock_free_num { enum { value = 0 }; }; - template<> struct static_is_lock_free_num { enum { value = ATOMIC_CHAR_LOCK_FREE }; }; - template<> struct static_is_lock_free_num { enum { value = ATOMIC_SHORT_LOCK_FREE }; }; - template<> struct static_is_lock_free_num { enum { value = ATOMIC_INT_LOCK_FREE }; }; - template<> struct static_is_lock_free_num { enum { value = ATOMIC_LONG_LOCK_FREE }; }; - template<> struct static_is_lock_free_num { enum { value = ATOMIC_LLONG_LOCK_FREE }; }; - template struct static_is_lock_free : static_is_lock_free_num::type> { }; - template<> struct static_is_lock_free { enum { value = ATOMIC_BOOL_LOCK_FREE }; }; - template struct static_is_lock_free { enum { value = ATOMIC_POINTER_LOCK_FREE }; }; -} - - -struct ProducerToken -{ - template - explicit ProducerToken(ConcurrentQueue& queue); - - template - explicit ProducerToken(BlockingConcurrentQueue& queue); - - ProducerToken(ProducerToken&& other) MOODYCAMEL_NOEXCEPT - : producer(other.producer) - { - other.producer = nullptr; - if (producer != nullptr) { - producer->token = this; - } - } - - inline ProducerToken& operator=(ProducerToken&& other) MOODYCAMEL_NOEXCEPT - { - swap(other); - return *this; - } - - void swap(ProducerToken& other) MOODYCAMEL_NOEXCEPT - { - std::swap(producer, other.producer); - if (producer != nullptr) { - producer->token = this; - } - if (other.producer != nullptr) { - other.producer->token = &other; - } - } - - // A token is always valid unless: - // 1) Memory allocation failed during construction - // 2) It was moved via the move constructor - // (Note: assignment does a swap, leaving both potentially valid) - // 3) The associated queue was destroyed - // Note that if valid() returns true, that only indicates - // that the token is valid for use with a specific queue, - // but not which one; that's up to the user to track. - inline bool valid() const { return producer != nullptr; } - - ~ProducerToken() - { - if (producer != nullptr) { - producer->token = nullptr; - producer->inactive.store(true, std::memory_order_release); - } - } - - // Disable copying and assignment - ProducerToken(ProducerToken const&) MOODYCAMEL_DELETE_FUNCTION; - ProducerToken& operator=(ProducerToken const&) MOODYCAMEL_DELETE_FUNCTION; - -private: - template friend class ConcurrentQueue; - friend class ConcurrentQueueTests; - -protected: - details::ConcurrentQueueProducerTypelessBase* producer; -}; - - -struct ConsumerToken -{ - template - explicit ConsumerToken(ConcurrentQueue& q); - - template - explicit ConsumerToken(BlockingConcurrentQueue& q); - - ConsumerToken(ConsumerToken&& other) MOODYCAMEL_NOEXCEPT - : initialOffset(other.initialOffset), lastKnownGlobalOffset(other.lastKnownGlobalOffset), itemsConsumedFromCurrent(other.itemsConsumedFromCurrent), currentProducer(other.currentProducer), desiredProducer(other.desiredProducer) - { - } - - inline ConsumerToken& operator=(ConsumerToken&& other) MOODYCAMEL_NOEXCEPT - { - swap(other); - return *this; - } - - void swap(ConsumerToken& other) MOODYCAMEL_NOEXCEPT - { - std::swap(initialOffset, other.initialOffset); - std::swap(lastKnownGlobalOffset, other.lastKnownGlobalOffset); - std::swap(itemsConsumedFromCurrent, other.itemsConsumedFromCurrent); - std::swap(currentProducer, other.currentProducer); - std::swap(desiredProducer, other.desiredProducer); - } - - // Disable copying and assignment - ConsumerToken(ConsumerToken const&) MOODYCAMEL_DELETE_FUNCTION; - ConsumerToken& operator=(ConsumerToken const&) MOODYCAMEL_DELETE_FUNCTION; - -private: - template friend class ConcurrentQueue; - friend class ConcurrentQueueTests; - -private: // but shared with ConcurrentQueue - std::uint32_t initialOffset; - std::uint32_t lastKnownGlobalOffset; - std::uint32_t itemsConsumedFromCurrent; - details::ConcurrentQueueProducerTypelessBase* currentProducer; - details::ConcurrentQueueProducerTypelessBase* desiredProducer; -}; - -// Need to forward-declare this swap because it's in a namespace. -// See http://stackoverflow.com/questions/4492062/why-does-a-c-friend-class-need-a-forward-declaration-only-in-other-namespaces -template -inline void swap(typename ConcurrentQueue::ImplicitProducerKVP& a, typename ConcurrentQueue::ImplicitProducerKVP& b) MOODYCAMEL_NOEXCEPT; - - -template -class ConcurrentQueue -{ -public: - typedef ::moodycamel::ProducerToken producer_token_t; - typedef ::moodycamel::ConsumerToken consumer_token_t; - - typedef typename Traits::index_t index_t; - typedef typename Traits::size_t size_t; - - static const size_t BLOCK_SIZE = static_cast(Traits::BLOCK_SIZE); - static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = static_cast(Traits::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD); - static const size_t EXPLICIT_INITIAL_INDEX_SIZE = static_cast(Traits::EXPLICIT_INITIAL_INDEX_SIZE); - static const size_t IMPLICIT_INITIAL_INDEX_SIZE = static_cast(Traits::IMPLICIT_INITIAL_INDEX_SIZE); - static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = static_cast(Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE); - static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = static_cast(Traits::EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE); -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable: 4307) // + integral constant overflow (that's what the ternary expression is for!) -#pragma warning(disable: 4309) // static_cast: Truncation of constant value -#endif - static const size_t MAX_SUBQUEUE_SIZE = (details::const_numeric_max::value - static_cast(Traits::MAX_SUBQUEUE_SIZE) < BLOCK_SIZE) ? details::const_numeric_max::value : ((static_cast(Traits::MAX_SUBQUEUE_SIZE) + (BLOCK_SIZE - 1)) / BLOCK_SIZE * BLOCK_SIZE); -#ifdef _MSC_VER -#pragma warning(pop) -#endif - - static_assert(!std::numeric_limits::is_signed && std::is_integral::value, "Traits::size_t must be an unsigned integral type"); - static_assert(!std::numeric_limits::is_signed && std::is_integral::value, "Traits::index_t must be an unsigned integral type"); - static_assert(sizeof(index_t) >= sizeof(size_t), "Traits::index_t must be at least as wide as Traits::size_t"); - static_assert((BLOCK_SIZE > 1) && !(BLOCK_SIZE & (BLOCK_SIZE - 1)), "Traits::BLOCK_SIZE must be a power of 2 (and at least 2)"); - static_assert((EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD > 1) && !(EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD & (EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD - 1)), "Traits::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD must be a power of 2 (and greater than 1)"); - static_assert((EXPLICIT_INITIAL_INDEX_SIZE > 1) && !(EXPLICIT_INITIAL_INDEX_SIZE & (EXPLICIT_INITIAL_INDEX_SIZE - 1)), "Traits::EXPLICIT_INITIAL_INDEX_SIZE must be a power of 2 (and greater than 1)"); - static_assert((IMPLICIT_INITIAL_INDEX_SIZE > 1) && !(IMPLICIT_INITIAL_INDEX_SIZE & (IMPLICIT_INITIAL_INDEX_SIZE - 1)), "Traits::IMPLICIT_INITIAL_INDEX_SIZE must be a power of 2 (and greater than 1)"); - static_assert((INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) || !(INITIAL_IMPLICIT_PRODUCER_HASH_SIZE & (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE - 1)), "Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE must be a power of 2"); - static_assert(INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0 || INITIAL_IMPLICIT_PRODUCER_HASH_SIZE >= 1, "Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE must be at least 1 (or 0 to disable implicit enqueueing)"); - -public: - // Creates a queue with at least `capacity` element slots; note that the - // actual number of elements that can be inserted without additional memory - // allocation depends on the number of producers and the block size (e.g. if - // the block size is equal to `capacity`, only a single block will be allocated - // up-front, which means only a single producer will be able to enqueue elements - // without an extra allocation -- blocks aren't shared between producers). - // This method is not thread safe -- it is up to the user to ensure that the - // queue is fully constructed before it starts being used by other threads (this - // includes making the memory effects of construction visible, possibly with a - // memory barrier). - explicit ConcurrentQueue(size_t capacity = 32 * BLOCK_SIZE) - : producerListTail(nullptr), - producerCount(0), - initialBlockPoolIndex(0), - nextExplicitConsumerId(0), - globalExplicitConsumerOffset(0) - { - implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); - populate_initial_implicit_producer_hash(); - populate_initial_block_list(capacity / BLOCK_SIZE + ((capacity & (BLOCK_SIZE - 1)) == 0 ? 0 : 1)); - -#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG - // Track all the producers using a fully-resolved typed list for - // each kind; this makes it possible to debug them starting from - // the root queue object (otherwise wacky casts are needed that - // don't compile in the debugger's expression evaluator). - explicitProducers.store(nullptr, std::memory_order_relaxed); - implicitProducers.store(nullptr, std::memory_order_relaxed); -#endif - } - - // Computes the correct amount of pre-allocated blocks for you based - // on the minimum number of elements you want available at any given - // time, and the maximum concurrent number of each type of producer. - ConcurrentQueue(size_t minCapacity, size_t maxExplicitProducers, size_t maxImplicitProducers) - : producerListTail(nullptr), - producerCount(0), - initialBlockPoolIndex(0), - nextExplicitConsumerId(0), - globalExplicitConsumerOffset(0) - { - implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); - populate_initial_implicit_producer_hash(); - size_t blocks = (((minCapacity + BLOCK_SIZE - 1) / BLOCK_SIZE) - 1) * (maxExplicitProducers + 1) + 2 * (maxExplicitProducers + maxImplicitProducers); - populate_initial_block_list(blocks); - -#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG - explicitProducers.store(nullptr, std::memory_order_relaxed); - implicitProducers.store(nullptr, std::memory_order_relaxed); -#endif - } - - // Note: The queue should not be accessed concurrently while it's - // being deleted. It's up to the user to synchronize this. - // This method is not thread safe. - ~ConcurrentQueue() - { - // Destroy producers - auto ptr = producerListTail.load(std::memory_order_relaxed); - while (ptr != nullptr) { - auto next = ptr->next_prod(); - if (ptr->token != nullptr) { - ptr->token->producer = nullptr; - } - destroy(ptr); - ptr = next; - } - - // Destroy implicit producer hash tables - MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE != 0) { - auto hash = implicitProducerHash.load(std::memory_order_relaxed); - while (hash != nullptr) { - auto prev = hash->prev; - if (prev != nullptr) { // The last hash is part of this object and was not allocated dynamically - for (size_t i = 0; i != hash->capacity; ++i) { - hash->entries[i].~ImplicitProducerKVP(); - } - hash->~ImplicitProducerHash(); - (Traits::free)(hash); - } - hash = prev; - } - } - - // Destroy global free list - auto block = freeList.head_unsafe(); - while (block != nullptr) { - auto next = block->freeListNext.load(std::memory_order_relaxed); - if (block->dynamicallyAllocated) { - destroy(block); - } - block = next; - } - - // Destroy initial free list - destroy_array(initialBlockPool, initialBlockPoolSize); - } - - // Disable copying and copy assignment - ConcurrentQueue(ConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; - ConcurrentQueue& operator=(ConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; - - // Moving is supported, but note that it is *not* a thread-safe operation. - // Nobody can use the queue while it's being moved, and the memory effects - // of that move must be propagated to other threads before they can use it. - // Note: When a queue is moved, its tokens are still valid but can only be - // used with the destination queue (i.e. semantically they are moved along - // with the queue itself). - ConcurrentQueue(ConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT - : producerListTail(other.producerListTail.load(std::memory_order_relaxed)), - producerCount(other.producerCount.load(std::memory_order_relaxed)), - initialBlockPoolIndex(other.initialBlockPoolIndex.load(std::memory_order_relaxed)), - initialBlockPool(other.initialBlockPool), - initialBlockPoolSize(other.initialBlockPoolSize), - freeList(std::move(other.freeList)), - nextExplicitConsumerId(other.nextExplicitConsumerId.load(std::memory_order_relaxed)), - globalExplicitConsumerOffset(other.globalExplicitConsumerOffset.load(std::memory_order_relaxed)) - { - // Move the other one into this, and leave the other one as an empty queue - implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); - populate_initial_implicit_producer_hash(); - swap_implicit_producer_hashes(other); - - other.producerListTail.store(nullptr, std::memory_order_relaxed); - other.producerCount.store(0, std::memory_order_relaxed); - other.nextExplicitConsumerId.store(0, std::memory_order_relaxed); - other.globalExplicitConsumerOffset.store(0, std::memory_order_relaxed); - -#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG - explicitProducers.store(other.explicitProducers.load(std::memory_order_relaxed), std::memory_order_relaxed); - other.explicitProducers.store(nullptr, std::memory_order_relaxed); - implicitProducers.store(other.implicitProducers.load(std::memory_order_relaxed), std::memory_order_relaxed); - other.implicitProducers.store(nullptr, std::memory_order_relaxed); -#endif - - other.initialBlockPoolIndex.store(0, std::memory_order_relaxed); - other.initialBlockPoolSize = 0; - other.initialBlockPool = nullptr; - - reown_producers(); - } - - inline ConcurrentQueue& operator=(ConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT - { - return swap_internal(other); - } - - // Swaps this queue's state with the other's. Not thread-safe. - // Swapping two queues does not invalidate their tokens, however - // the tokens that were created for one queue must be used with - // only the swapped queue (i.e. the tokens are tied to the - // queue's movable state, not the object itself). - inline void swap(ConcurrentQueue& other) MOODYCAMEL_NOEXCEPT - { - swap_internal(other); - } - -private: - ConcurrentQueue& swap_internal(ConcurrentQueue& other) - { - if (this == &other) { - return *this; - } - - details::swap_relaxed(producerListTail, other.producerListTail); - details::swap_relaxed(producerCount, other.producerCount); - details::swap_relaxed(initialBlockPoolIndex, other.initialBlockPoolIndex); - std::swap(initialBlockPool, other.initialBlockPool); - std::swap(initialBlockPoolSize, other.initialBlockPoolSize); - freeList.swap(other.freeList); - details::swap_relaxed(nextExplicitConsumerId, other.nextExplicitConsumerId); - details::swap_relaxed(globalExplicitConsumerOffset, other.globalExplicitConsumerOffset); - - swap_implicit_producer_hashes(other); - - reown_producers(); - other.reown_producers(); - -#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG - details::swap_relaxed(explicitProducers, other.explicitProducers); - details::swap_relaxed(implicitProducers, other.implicitProducers); -#endif - - return *this; - } - -public: - // Enqueues a single item (by copying it). - // Allocates memory if required. Only fails if memory allocation fails (or implicit - // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, - // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). - // Thread-safe. - inline bool enqueue(T const& item) - { - MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; - else return inner_enqueue(item); - } - - // Enqueues a single item (by moving it, if possible). - // Allocates memory if required. Only fails if memory allocation fails (or implicit - // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, - // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). - // Thread-safe. - inline bool enqueue(T&& item) - { - MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; - else return inner_enqueue(std::move(item)); - } - - // Enqueues a single item (by copying it) using an explicit producer token. - // Allocates memory if required. Only fails if memory allocation fails (or - // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). - // Thread-safe. - inline bool enqueue(producer_token_t const& token, T const& item) - { - return inner_enqueue(token, item); - } - - // Enqueues a single item (by moving it, if possible) using an explicit producer token. - // Allocates memory if required. Only fails if memory allocation fails (or - // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). - // Thread-safe. - inline bool enqueue(producer_token_t const& token, T&& item) - { - return inner_enqueue(token, std::move(item)); - } - - // Enqueues several items. - // Allocates memory if required. Only fails if memory allocation fails (or - // implicit production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE - // is 0, or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). - // Note: Use std::make_move_iterator if the elements should be moved instead of copied. - // Thread-safe. - template - bool enqueue_bulk(It itemFirst, size_t count) - { - MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; - else return inner_enqueue_bulk(itemFirst, count); - } - - // Enqueues several items using an explicit producer token. - // Allocates memory if required. Only fails if memory allocation fails - // (or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). - // Note: Use std::make_move_iterator if the elements should be moved - // instead of copied. - // Thread-safe. - template - bool enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) - { - return inner_enqueue_bulk(token, itemFirst, count); - } - - // Enqueues a single item (by copying it). - // Does not allocate memory. Fails if not enough room to enqueue (or implicit - // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE - // is 0). - // Thread-safe. - inline bool try_enqueue(T const& item) - { - MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; - else return inner_enqueue(item); - } - - // Enqueues a single item (by moving it, if possible). - // Does not allocate memory (except for one-time implicit producer). - // Fails if not enough room to enqueue (or implicit production is - // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). - // Thread-safe. - inline bool try_enqueue(T&& item) - { - MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; - else return inner_enqueue(std::move(item)); - } - - // Enqueues a single item (by copying it) using an explicit producer token. - // Does not allocate memory. Fails if not enough room to enqueue. - // Thread-safe. - inline bool try_enqueue(producer_token_t const& token, T const& item) - { - return inner_enqueue(token, item); - } - - // Enqueues a single item (by moving it, if possible) using an explicit producer token. - // Does not allocate memory. Fails if not enough room to enqueue. - // Thread-safe. - inline bool try_enqueue(producer_token_t const& token, T&& item) - { - return inner_enqueue(token, std::move(item)); - } - - // Enqueues several items. - // Does not allocate memory (except for one-time implicit producer). - // Fails if not enough room to enqueue (or implicit production is - // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). - // Note: Use std::make_move_iterator if the elements should be moved - // instead of copied. - // Thread-safe. - template - bool try_enqueue_bulk(It itemFirst, size_t count) - { - MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; - else return inner_enqueue_bulk(itemFirst, count); - } - - // Enqueues several items using an explicit producer token. - // Does not allocate memory. Fails if not enough room to enqueue. - // Note: Use std::make_move_iterator if the elements should be moved - // instead of copied. - // Thread-safe. - template - bool try_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) - { - return inner_enqueue_bulk(token, itemFirst, count); - } - - - - // Attempts to dequeue from the queue. - // Returns false if all producer streams appeared empty at the time they - // were checked (so, the queue is likely but not guaranteed to be empty). - // Never allocates. Thread-safe. - template - bool try_dequeue(U& item) - { - // Instead of simply trying each producer in turn (which could cause needless contention on the first - // producer), we score them heuristically. - size_t nonEmptyCount = 0; - ProducerBase* best = nullptr; - size_t bestSize = 0; - for (auto ptr = producerListTail.load(std::memory_order_acquire); nonEmptyCount < 3 && ptr != nullptr; ptr = ptr->next_prod()) { - auto size = ptr->size_approx(); - if (size > 0) { - if (size > bestSize) { - bestSize = size; - best = ptr; - } - ++nonEmptyCount; - } - } - - // If there was at least one non-empty queue but it appears empty at the time - // we try to dequeue from it, we need to make sure every queue's been tried - if (nonEmptyCount > 0) { - if ((details::likely)(best->dequeue(item))) { - return true; - } - for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { - if (ptr != best && ptr->dequeue(item)) { - return true; - } - } - } - return false; - } - - // Attempts to dequeue from the queue. - // Returns false if all producer streams appeared empty at the time they - // were checked (so, the queue is likely but not guaranteed to be empty). - // This differs from the try_dequeue(item) method in that this one does - // not attempt to reduce contention by interleaving the order that producer - // streams are dequeued from. So, using this method can reduce overall throughput - // under contention, but will give more predictable results in single-threaded - // consumer scenarios. This is mostly only useful for internal unit tests. - // Never allocates. Thread-safe. - template - bool try_dequeue_non_interleaved(U& item) - { - for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { - if (ptr->dequeue(item)) { - return true; - } - } - return false; - } - - // Attempts to dequeue from the queue using an explicit consumer token. - // Returns false if all producer streams appeared empty at the time they - // were checked (so, the queue is likely but not guaranteed to be empty). - // Never allocates. Thread-safe. - template - bool try_dequeue(consumer_token_t& token, U& item) - { - // The idea is roughly as follows: - // Every 256 items from one producer, make everyone rotate (increase the global offset) -> this means the highest efficiency consumer dictates the rotation speed of everyone else, more or less - // If you see that the global offset has changed, you must reset your consumption counter and move to your designated place - // If there's no items where you're supposed to be, keep moving until you find a producer with some items - // If the global offset has not changed but you've run out of items to consume, move over from your current position until you find an producer with something in it - - if (token.desiredProducer == nullptr || token.lastKnownGlobalOffset != globalExplicitConsumerOffset.load(std::memory_order_relaxed)) { - if (!update_current_producer_after_rotation(token)) { - return false; - } - } - - // If there was at least one non-empty queue but it appears empty at the time - // we try to dequeue from it, we need to make sure every queue's been tried - if (static_cast(token.currentProducer)->dequeue(item)) { - if (++token.itemsConsumedFromCurrent == EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE) { - globalExplicitConsumerOffset.fetch_add(1, std::memory_order_relaxed); - } - return true; - } - - auto tail = producerListTail.load(std::memory_order_acquire); - auto ptr = static_cast(token.currentProducer)->next_prod(); - if (ptr == nullptr) { - ptr = tail; - } - while (ptr != static_cast(token.currentProducer)) { - if (ptr->dequeue(item)) { - token.currentProducer = ptr; - token.itemsConsumedFromCurrent = 1; - return true; - } - ptr = ptr->next_prod(); - if (ptr == nullptr) { - ptr = tail; - } - } - return false; - } - - // Attempts to dequeue several elements from the queue. - // Returns the number of items actually dequeued. - // Returns 0 if all producer streams appeared empty at the time they - // were checked (so, the queue is likely but not guaranteed to be empty). - // Never allocates. Thread-safe. - template - size_t try_dequeue_bulk(It itemFirst, size_t max) - { - size_t count = 0; - for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { - count += ptr->dequeue_bulk(itemFirst, max - count); - if (count == max) { - break; - } - } - return count; - } - - // Attempts to dequeue several elements from the queue using an explicit consumer token. - // Returns the number of items actually dequeued. - // Returns 0 if all producer streams appeared empty at the time they - // were checked (so, the queue is likely but not guaranteed to be empty). - // Never allocates. Thread-safe. - template - size_t try_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max) - { - if (token.desiredProducer == nullptr || token.lastKnownGlobalOffset != globalExplicitConsumerOffset.load(std::memory_order_relaxed)) { - if (!update_current_producer_after_rotation(token)) { - return 0; - } - } - - size_t count = static_cast(token.currentProducer)->dequeue_bulk(itemFirst, max); - if (count == max) { - if ((token.itemsConsumedFromCurrent += static_cast(max)) >= EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE) { - globalExplicitConsumerOffset.fetch_add(1, std::memory_order_relaxed); - } - return max; - } - token.itemsConsumedFromCurrent += static_cast(count); - max -= count; - - auto tail = producerListTail.load(std::memory_order_acquire); - auto ptr = static_cast(token.currentProducer)->next_prod(); - if (ptr == nullptr) { - ptr = tail; - } - while (ptr != static_cast(token.currentProducer)) { - auto dequeued = ptr->dequeue_bulk(itemFirst, max); - count += dequeued; - if (dequeued != 0) { - token.currentProducer = ptr; - token.itemsConsumedFromCurrent = static_cast(dequeued); - } - if (dequeued == max) { - break; - } - max -= dequeued; - ptr = ptr->next_prod(); - if (ptr == nullptr) { - ptr = tail; - } - } - return count; - } - - - - // Attempts to dequeue from a specific producer's inner queue. - // If you happen to know which producer you want to dequeue from, this - // is significantly faster than using the general-case try_dequeue methods. - // Returns false if the producer's queue appeared empty at the time it - // was checked (so, the queue is likely but not guaranteed to be empty). - // Never allocates. Thread-safe. - template - inline bool try_dequeue_from_producer(producer_token_t const& producer, U& item) - { - return static_cast(producer.producer)->dequeue(item); - } - - // Attempts to dequeue several elements from a specific producer's inner queue. - // Returns the number of items actually dequeued. - // If you happen to know which producer you want to dequeue from, this - // is significantly faster than using the general-case try_dequeue methods. - // Returns 0 if the producer's queue appeared empty at the time it - // was checked (so, the queue is likely but not guaranteed to be empty). - // Never allocates. Thread-safe. - template - inline size_t try_dequeue_bulk_from_producer(producer_token_t const& producer, It itemFirst, size_t max) - { - return static_cast(producer.producer)->dequeue_bulk(itemFirst, max); - } - - - // Returns an estimate of the total number of elements currently in the queue. This - // estimate is only accurate if the queue has completely stabilized before it is called - // (i.e. all enqueue and dequeue operations have completed and their memory effects are - // visible on the calling thread, and no further operations start while this method is - // being called). - // Thread-safe. - size_t size_approx() const - { - size_t size = 0; - for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { - size += ptr->size_approx(); - } - return size; - } - - - // Returns true if the underlying atomic variables used by - // the queue are lock-free (they should be on most platforms). - // Thread-safe. - static constexpr bool is_lock_free() - { - return - details::static_is_lock_free::value == 2 && - details::static_is_lock_free::value == 2 && - details::static_is_lock_free::value == 2 && - details::static_is_lock_free::value == 2 && - details::static_is_lock_free::value == 2 && - details::static_is_lock_free::thread_id_numeric_size_t>::value == 2; - } - - -private: - friend struct ProducerToken; - friend struct ConsumerToken; - struct ExplicitProducer; - friend struct ExplicitProducer; - struct ImplicitProducer; - friend struct ImplicitProducer; - friend class ConcurrentQueueTests; - - enum AllocationMode { CanAlloc, CannotAlloc }; - - - /////////////////////////////// - // Queue methods - /////////////////////////////// - - template - inline bool inner_enqueue(producer_token_t const& token, U&& element) - { - return static_cast(token.producer)->ConcurrentQueue::ExplicitProducer::template enqueue(std::forward(element)); - } - - template - inline bool inner_enqueue(U&& element) - { - auto producer = get_or_add_implicit_producer(); - return producer == nullptr ? false : producer->ConcurrentQueue::ImplicitProducer::template enqueue(std::forward(element)); - } - - template - inline bool inner_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) - { - return static_cast(token.producer)->ConcurrentQueue::ExplicitProducer::template enqueue_bulk(itemFirst, count); - } - - template - inline bool inner_enqueue_bulk(It itemFirst, size_t count) - { - auto producer = get_or_add_implicit_producer(); - return producer == nullptr ? false : producer->ConcurrentQueue::ImplicitProducer::template enqueue_bulk(itemFirst, count); - } - - inline bool update_current_producer_after_rotation(consumer_token_t& token) - { - // Ah, there's been a rotation, figure out where we should be! - auto tail = producerListTail.load(std::memory_order_acquire); - if (token.desiredProducer == nullptr && tail == nullptr) { - return false; - } - auto prodCount = producerCount.load(std::memory_order_relaxed); - auto globalOffset = globalExplicitConsumerOffset.load(std::memory_order_relaxed); - if ((details::unlikely)(token.desiredProducer == nullptr)) { - // Aha, first time we're dequeueing anything. - // Figure out our local position - // Note: offset is from start, not end, but we're traversing from end -- subtract from count first - std::uint32_t offset = prodCount - 1 - (token.initialOffset % prodCount); - token.desiredProducer = tail; - for (std::uint32_t i = 0; i != offset; ++i) { - token.desiredProducer = static_cast(token.desiredProducer)->next_prod(); - if (token.desiredProducer == nullptr) { - token.desiredProducer = tail; - } - } - } - - std::uint32_t delta = globalOffset - token.lastKnownGlobalOffset; - if (delta >= prodCount) { - delta = delta % prodCount; - } - for (std::uint32_t i = 0; i != delta; ++i) { - token.desiredProducer = static_cast(token.desiredProducer)->next_prod(); - if (token.desiredProducer == nullptr) { - token.desiredProducer = tail; - } - } - - token.lastKnownGlobalOffset = globalOffset; - token.currentProducer = token.desiredProducer; - token.itemsConsumedFromCurrent = 0; - return true; - } - - - /////////////////////////// - // Free list - /////////////////////////// - - template - struct FreeListNode - { - FreeListNode() : freeListRefs(0), freeListNext(nullptr) { } - - std::atomic freeListRefs; - std::atomic freeListNext; - }; - - // A simple CAS-based lock-free free list. Not the fastest thing in the world under heavy contention, but - // simple and correct (assuming nodes are never freed until after the free list is destroyed), and fairly - // speedy under low contention. - template // N must inherit FreeListNode or have the same fields (and initialization of them) - struct FreeList - { - FreeList() : freeListHead(nullptr) { } - FreeList(FreeList&& other) : freeListHead(other.freeListHead.load(std::memory_order_relaxed)) { other.freeListHead.store(nullptr, std::memory_order_relaxed); } - void swap(FreeList& other) { details::swap_relaxed(freeListHead, other.freeListHead); } - - FreeList(FreeList const&) MOODYCAMEL_DELETE_FUNCTION; - FreeList& operator=(FreeList const&) MOODYCAMEL_DELETE_FUNCTION; - - inline void add(N* node) - { -#ifdef MCDBGQ_NOLOCKFREE_FREELIST - debug::DebugLock lock(mutex); -#endif - // We know that the should-be-on-freelist bit is 0 at this point, so it's safe to - // set it using a fetch_add - if (node->freeListRefs.fetch_add(SHOULD_BE_ON_FREELIST, std::memory_order_acq_rel) == 0) { - // Oh look! We were the last ones referencing this node, and we know - // we want to add it to the free list, so let's do it! - add_knowing_refcount_is_zero(node); - } - } - - inline N* try_get() - { -#ifdef MCDBGQ_NOLOCKFREE_FREELIST - debug::DebugLock lock(mutex); -#endif - auto head = freeListHead.load(std::memory_order_acquire); - while (head != nullptr) { - auto prevHead = head; - auto refs = head->freeListRefs.load(std::memory_order_relaxed); - if ((refs & REFS_MASK) == 0 || !head->freeListRefs.compare_exchange_strong(refs, refs + 1, std::memory_order_acquire, std::memory_order_relaxed)) { - head = freeListHead.load(std::memory_order_acquire); - continue; - } - - // Good, reference count has been incremented (it wasn't at zero), which means we can read the - // next and not worry about it changing between now and the time we do the CAS - auto next = head->freeListNext.load(std::memory_order_relaxed); - if (freeListHead.compare_exchange_strong(head, next, std::memory_order_acquire, std::memory_order_relaxed)) { - // Yay, got the node. This means it was on the list, which means shouldBeOnFreeList must be false no - // matter the refcount (because nobody else knows it's been taken off yet, it can't have been put back on). - assert((head->freeListRefs.load(std::memory_order_relaxed) & SHOULD_BE_ON_FREELIST) == 0); - - // Decrease refcount twice, once for our ref, and once for the list's ref - head->freeListRefs.fetch_sub(2, std::memory_order_release); - return head; - } - - // OK, the head must have changed on us, but we still need to decrease the refcount we increased. - // Note that we don't need to release any memory effects, but we do need to ensure that the reference - // count decrement happens-after the CAS on the head. - refs = prevHead->freeListRefs.fetch_sub(1, std::memory_order_acq_rel); - if (refs == SHOULD_BE_ON_FREELIST + 1) { - add_knowing_refcount_is_zero(prevHead); - } - } - - return nullptr; - } - - // Useful for traversing the list when there's no contention (e.g. to destroy remaining nodes) - N* head_unsafe() const { return freeListHead.load(std::memory_order_relaxed); } - - private: - inline void add_knowing_refcount_is_zero(N* node) - { - // Since the refcount is zero, and nobody can increase it once it's zero (except us, and we run - // only one copy of this method per node at a time, i.e. the single thread case), then we know - // we can safely change the next pointer of the node; however, once the refcount is back above - // zero, then other threads could increase it (happens under heavy contention, when the refcount - // goes to zero in between a load and a refcount increment of a node in try_get, then back up to - // something non-zero, then the refcount increment is done by the other thread) -- so, if the CAS - // to add the node to the actual list fails, decrease the refcount and leave the add operation to - // the next thread who puts the refcount back at zero (which could be us, hence the loop). - auto head = freeListHead.load(std::memory_order_relaxed); - while (true) { - node->freeListNext.store(head, std::memory_order_relaxed); - node->freeListRefs.store(1, std::memory_order_release); - if (!freeListHead.compare_exchange_strong(head, node, std::memory_order_release, std::memory_order_relaxed)) { - // Hmm, the add failed, but we can only try again when the refcount goes back to zero - if (node->freeListRefs.fetch_add(SHOULD_BE_ON_FREELIST - 1, std::memory_order_release) == 1) { - continue; - } - } - return; - } - } - - private: - // Implemented like a stack, but where node order doesn't matter (nodes are inserted out of order under contention) - std::atomic freeListHead; - - static const std::uint32_t REFS_MASK = 0x7FFFFFFF; - static const std::uint32_t SHOULD_BE_ON_FREELIST = 0x80000000; - -#ifdef MCDBGQ_NOLOCKFREE_FREELIST - debug::DebugMutex mutex; -#endif - }; - - - /////////////////////////// - // Block - /////////////////////////// - - enum InnerQueueContext { implicit_context = 0, explicit_context = 1 }; - - struct Block - { - Block() - : next(nullptr), elementsCompletelyDequeued(0), freeListRefs(0), freeListNext(nullptr), dynamicallyAllocated(true) - { -#ifdef MCDBGQ_TRACKMEM - owner = nullptr; -#endif - } - - template - inline bool is_empty() const - { - MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { - // Check flags - for (size_t i = 0; i < BLOCK_SIZE; ++i) { - if (!emptyFlags[i].load(std::memory_order_relaxed)) { - return false; - } - } - - // Aha, empty; make sure we have all other memory effects that happened before the empty flags were set - std::atomic_thread_fence(std::memory_order_acquire); - return true; - } - else { - // Check counter - if (elementsCompletelyDequeued.load(std::memory_order_relaxed) == BLOCK_SIZE) { - std::atomic_thread_fence(std::memory_order_acquire); - return true; - } - assert(elementsCompletelyDequeued.load(std::memory_order_relaxed) <= BLOCK_SIZE); - return false; - } - } - - // Returns true if the block is now empty (does not apply in explicit context) - template - inline bool set_empty(MOODYCAMEL_MAYBE_UNUSED index_t i) - { - MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { - // Set flag - assert(!emptyFlags[BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1))].load(std::memory_order_relaxed)); - emptyFlags[BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1))].store(true, std::memory_order_release); - return false; - } - else { - // Increment counter - auto prevVal = elementsCompletelyDequeued.fetch_add(1, std::memory_order_release); - assert(prevVal < BLOCK_SIZE); - return prevVal == BLOCK_SIZE - 1; - } - } - - // Sets multiple contiguous item statuses to 'empty' (assumes no wrapping and count > 0). - // Returns true if the block is now empty (does not apply in explicit context). - template - inline bool set_many_empty(MOODYCAMEL_MAYBE_UNUSED index_t i, size_t count) - { - MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { - // Set flags - std::atomic_thread_fence(std::memory_order_release); - i = BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1)) - count + 1; - for (size_t j = 0; j != count; ++j) { - assert(!emptyFlags[i + j].load(std::memory_order_relaxed)); - emptyFlags[i + j].store(true, std::memory_order_relaxed); - } - return false; - } - else { - // Increment counter - auto prevVal = elementsCompletelyDequeued.fetch_add(count, std::memory_order_release); - assert(prevVal + count <= BLOCK_SIZE); - return prevVal + count == BLOCK_SIZE; - } - } - - template - inline void set_all_empty() - { - MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { - // Set all flags - for (size_t i = 0; i != BLOCK_SIZE; ++i) { - emptyFlags[i].store(true, std::memory_order_relaxed); - } - } - else { - // Reset counter - elementsCompletelyDequeued.store(BLOCK_SIZE, std::memory_order_relaxed); - } - } - - template - inline void reset_empty() - { - MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { - // Reset flags - for (size_t i = 0; i != BLOCK_SIZE; ++i) { - emptyFlags[i].store(false, std::memory_order_relaxed); - } - } - else { - // Reset counter - elementsCompletelyDequeued.store(0, std::memory_order_relaxed); - } - } - - inline T* operator[](index_t idx) MOODYCAMEL_NOEXCEPT { return static_cast(static_cast(elements)) + static_cast(idx & static_cast(BLOCK_SIZE - 1)); } - inline T const* operator[](index_t idx) const MOODYCAMEL_NOEXCEPT { return static_cast(static_cast(elements)) + static_cast(idx & static_cast(BLOCK_SIZE - 1)); } - - private: - static_assert(std::alignment_of::value <= sizeof(T), "The queue does not support types with an alignment greater than their size at this time"); - MOODYCAMEL_ALIGNED_TYPE_LIKE(char[sizeof(T) * BLOCK_SIZE], T) elements; - public: - Block* next; - std::atomic elementsCompletelyDequeued; - std::atomic emptyFlags[BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD ? BLOCK_SIZE : 1]; - public: - std::atomic freeListRefs; - std::atomic freeListNext; - bool dynamicallyAllocated; // Perhaps a better name for this would be 'isNotPartOfInitialBlockPool' - -#ifdef MCDBGQ_TRACKMEM - void* owner; -#endif - }; - static_assert(std::alignment_of::value >= std::alignment_of::value, "Internal error: Blocks must be at least as aligned as the type they are wrapping"); - - -#ifdef MCDBGQ_TRACKMEM -public: - struct MemStats; -private: -#endif - - /////////////////////////// - // Producer base - /////////////////////////// - - struct ProducerBase : public details::ConcurrentQueueProducerTypelessBase - { - ProducerBase(ConcurrentQueue* parent_, bool isExplicit_) : - tailIndex(0), - headIndex(0), - dequeueOptimisticCount(0), - dequeueOvercommit(0), - tailBlock(nullptr), - isExplicit(isExplicit_), - parent(parent_) - { - } - - virtual ~ProducerBase() { } - - template - inline bool dequeue(U& element) - { - if (isExplicit) { - return static_cast(this)->dequeue(element); - } - else { - return static_cast(this)->dequeue(element); - } - } - - template - inline size_t dequeue_bulk(It& itemFirst, size_t max) - { - if (isExplicit) { - return static_cast(this)->dequeue_bulk(itemFirst, max); - } - else { - return static_cast(this)->dequeue_bulk(itemFirst, max); - } - } - - inline ProducerBase* next_prod() const { return static_cast(next); } - - inline size_t size_approx() const - { - auto tail = tailIndex.load(std::memory_order_relaxed); - auto head = headIndex.load(std::memory_order_relaxed); - return details::circular_less_than(head, tail) ? static_cast(tail - head) : 0; - } - - inline index_t getTail() const { return tailIndex.load(std::memory_order_relaxed); } - protected: - std::atomic tailIndex; // Where to enqueue to next - std::atomic headIndex; // Where to dequeue from next - - std::atomic dequeueOptimisticCount; - std::atomic dequeueOvercommit; - - Block* tailBlock; - - public: - bool isExplicit; - ConcurrentQueue* parent; - - protected: -#ifdef MCDBGQ_TRACKMEM - friend struct MemStats; -#endif - }; - - - /////////////////////////// - // Explicit queue - /////////////////////////// - - struct ExplicitProducer : public ProducerBase - { - explicit ExplicitProducer(ConcurrentQueue* parent_) : - ProducerBase(parent_, true), - blockIndex(nullptr), - pr_blockIndexSlotsUsed(0), - pr_blockIndexSize(EXPLICIT_INITIAL_INDEX_SIZE >> 1), - pr_blockIndexFront(0), - pr_blockIndexEntries(nullptr), - pr_blockIndexRaw(nullptr) - { - size_t poolBasedIndexSize = details::ceil_to_pow_2(parent_->initialBlockPoolSize) >> 1; - if (poolBasedIndexSize > pr_blockIndexSize) { - pr_blockIndexSize = poolBasedIndexSize; - } - - new_block_index(0); // This creates an index with double the number of current entries, i.e. EXPLICIT_INITIAL_INDEX_SIZE - } - - ~ExplicitProducer() - { - // Destruct any elements not yet dequeued. - // Since we're in the destructor, we can assume all elements - // are either completely dequeued or completely not (no halfways). - if (this->tailBlock != nullptr) { // Note this means there must be a block index too - // First find the block that's partially dequeued, if any - Block* halfDequeuedBlock = nullptr; - if ((this->headIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)) != 0) { - // The head's not on a block boundary, meaning a block somewhere is partially dequeued - // (or the head block is the tail block and was fully dequeued, but the head/tail are still not on a boundary) - size_t i = (pr_blockIndexFront - pr_blockIndexSlotsUsed) & (pr_blockIndexSize - 1); - while (details::circular_less_than(pr_blockIndexEntries[i].base + BLOCK_SIZE, this->headIndex.load(std::memory_order_relaxed))) { - i = (i + 1) & (pr_blockIndexSize - 1); - } - assert(details::circular_less_than(pr_blockIndexEntries[i].base, this->headIndex.load(std::memory_order_relaxed))); - halfDequeuedBlock = pr_blockIndexEntries[i].block; - } - - // Start at the head block (note the first line in the loop gives us the head from the tail on the first iteration) - auto block = this->tailBlock; - do { - block = block->next; - if (block->ConcurrentQueue::Block::template is_empty()) { - continue; - } - - size_t i = 0; // Offset into block - if (block == halfDequeuedBlock) { - i = static_cast(this->headIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)); - } - - // Walk through all the items in the block; if this is the tail block, we need to stop when we reach the tail index - auto lastValidIndex = (this->tailIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)) == 0 ? BLOCK_SIZE : static_cast(this->tailIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)); - while (i != BLOCK_SIZE && (block != this->tailBlock || i != lastValidIndex)) { - (*block)[i++]->~T(); - } - } while (block != this->tailBlock); - } - - // Destroy all blocks that we own - if (this->tailBlock != nullptr) { - auto block = this->tailBlock; - do { - auto nextBlock = block->next; - this->parent->add_block_to_free_list(block); - block = nextBlock; - } while (block != this->tailBlock); - } - - // Destroy the block indices - auto header = static_cast(pr_blockIndexRaw); - while (header != nullptr) { - auto prev = static_cast(header->prev); - header->~BlockIndexHeader(); - (Traits::free)(header); - header = prev; - } - } - - template - inline bool enqueue(U&& element) - { - index_t currentTailIndex = this->tailIndex.load(std::memory_order_relaxed); - index_t newTailIndex = 1 + currentTailIndex; - if ((currentTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { - // We reached the end of a block, start a new one - auto startBlock = this->tailBlock; - auto originalBlockIndexSlotsUsed = pr_blockIndexSlotsUsed; - if (this->tailBlock != nullptr && this->tailBlock->next->ConcurrentQueue::Block::template is_empty()) { - // We can re-use the block ahead of us, it's empty! - this->tailBlock = this->tailBlock->next; - this->tailBlock->ConcurrentQueue::Block::template reset_empty(); - - // We'll put the block on the block index (guaranteed to be room since we're conceptually removing the - // last block from it first -- except instead of removing then adding, we can just overwrite). - // Note that there must be a valid block index here, since even if allocation failed in the ctor, - // it would have been re-attempted when adding the first block to the queue; since there is such - // a block, a block index must have been successfully allocated. - } - else { - // Whatever head value we see here is >= the last value we saw here (relatively), - // and <= its current value. Since we have the most recent tail, the head must be - // <= to it. - auto head = this->headIndex.load(std::memory_order_relaxed); - assert(!details::circular_less_than(currentTailIndex, head)); - if (!details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) - || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head))) { - // We can't enqueue in another block because there's not enough leeway -- the - // tail could surpass the head by the time the block fills up! (Or we'll exceed - // the size limit, if the second part of the condition was true.) - return false; - } - // We're going to need a new block; check that the block index has room - if (pr_blockIndexRaw == nullptr || pr_blockIndexSlotsUsed == pr_blockIndexSize) { - // Hmm, the circular block index is already full -- we'll need - // to allocate a new index. Note pr_blockIndexRaw can only be nullptr if - // the initial allocation failed in the constructor. - - MOODYCAMEL_CONSTEXPR_IF (allocMode == CannotAlloc) { - return false; - } - else if (!new_block_index(pr_blockIndexSlotsUsed)) { - return false; - } - } - - // Insert a new block in the circular linked list - auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); - if (newBlock == nullptr) { - return false; - } -#ifdef MCDBGQ_TRACKMEM - newBlock->owner = this; -#endif - newBlock->ConcurrentQueue::Block::template reset_empty(); - if (this->tailBlock == nullptr) { - newBlock->next = newBlock; - } - else { - newBlock->next = this->tailBlock->next; - this->tailBlock->next = newBlock; - } - this->tailBlock = newBlock; - ++pr_blockIndexSlotsUsed; - } - - MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(element)))) { - // The constructor may throw. We want the element not to appear in the queue in - // that case (without corrupting the queue): - MOODYCAMEL_TRY { - new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); - } - MOODYCAMEL_CATCH (...) { - // Revert change to the current block, but leave the new block available - // for next time - pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; - this->tailBlock = startBlock == nullptr ? this->tailBlock : startBlock; - MOODYCAMEL_RETHROW; - } - } - else { - (void)startBlock; - (void)originalBlockIndexSlotsUsed; - } - - // Add block to block index - auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; - entry.base = currentTailIndex; - entry.block = this->tailBlock; - blockIndex.load(std::memory_order_relaxed)->front.store(pr_blockIndexFront, std::memory_order_release); - pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); - - MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(element)))) { - this->tailIndex.store(newTailIndex, std::memory_order_release); - return true; - } - } - - // Enqueue - new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); - - this->tailIndex.store(newTailIndex, std::memory_order_release); - return true; - } - - template - bool dequeue(U& element) - { - auto tail = this->tailIndex.load(std::memory_order_relaxed); - auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); - if (details::circular_less_than(this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit, tail)) { - // Might be something to dequeue, let's give it a try - - // Note that this if is purely for performance purposes in the common case when the queue is - // empty and the values are eventually consistent -- we may enter here spuriously. - - // Note that whatever the values of overcommit and tail are, they are not going to change (unless we - // change them) and must be the same value at this point (inside the if) as when the if condition was - // evaluated. - - // We insert an acquire fence here to synchronize-with the release upon incrementing dequeueOvercommit below. - // This ensures that whatever the value we got loaded into overcommit, the load of dequeueOptisticCount in - // the fetch_add below will result in a value at least as recent as that (and therefore at least as large). - // Note that I believe a compiler (signal) fence here would be sufficient due to the nature of fetch_add (all - // read-modify-write operations are guaranteed to work on the latest value in the modification order), but - // unfortunately that can't be shown to be correct using only the C++11 standard. - // See http://stackoverflow.com/questions/18223161/what-are-the-c11-memory-ordering-guarantees-in-this-corner-case - std::atomic_thread_fence(std::memory_order_acquire); - - // Increment optimistic counter, then check if it went over the boundary - auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(1, std::memory_order_relaxed); - - // Note that since dequeueOvercommit must be <= dequeueOptimisticCount (because dequeueOvercommit is only ever - // incremented after dequeueOptimisticCount -- this is enforced in the `else` block below), and since we now - // have a version of dequeueOptimisticCount that is at least as recent as overcommit (due to the release upon - // incrementing dequeueOvercommit and the acquire above that synchronizes with it), overcommit <= myDequeueCount. - // However, we can't assert this since both dequeueOptimisticCount and dequeueOvercommit may (independently) - // overflow; in such a case, though, the logic still holds since the difference between the two is maintained. - - // Note that we reload tail here in case it changed; it will be the same value as before or greater, since - // this load is sequenced after (happens after) the earlier load above. This is supported by read-read - // coherency (as defined in the standard), explained here: http://en.cppreference.com/w/cpp/atomic/memory_order - tail = this->tailIndex.load(std::memory_order_acquire); - if ((details::likely)(details::circular_less_than(myDequeueCount - overcommit, tail))) { - // Guaranteed to be at least one element to dequeue! - - // Get the index. Note that since there's guaranteed to be at least one element, this - // will never exceed tail. We need to do an acquire-release fence here since it's possible - // that whatever condition got us to this point was for an earlier enqueued element (that - // we already see the memory effects for), but that by the time we increment somebody else - // has incremented it, and we need to see the memory effects for *that* element, which is - // in such a case is necessarily visible on the thread that incremented it in the first - // place with the more current condition (they must have acquired a tail that is at least - // as recent). - auto index = this->headIndex.fetch_add(1, std::memory_order_acq_rel); - - - // Determine which block the element is in - - auto localBlockIndex = blockIndex.load(std::memory_order_acquire); - auto localBlockIndexHead = localBlockIndex->front.load(std::memory_order_acquire); - - // We need to be careful here about subtracting and dividing because of index wrap-around. - // When an index wraps, we need to preserve the sign of the offset when dividing it by the - // block size (in order to get a correct signed block count offset in all cases): - auto headBase = localBlockIndex->entries[localBlockIndexHead].base; - auto blockBaseIndex = index & ~static_cast(BLOCK_SIZE - 1); - auto offset = static_cast(static_cast::type>(blockBaseIndex - headBase) / static_cast::type>(BLOCK_SIZE)); - auto block = localBlockIndex->entries[(localBlockIndexHead + offset) & (localBlockIndex->size - 1)].block; - - // Dequeue - auto& el = *((*block)[index]); - if (!MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, element = std::move(el))) { - // Make sure the element is still fully dequeued and destroyed even if the assignment - // throws - struct Guard { - Block* block; - index_t index; - - ~Guard() - { - (*block)[index]->~T(); - block->ConcurrentQueue::Block::template set_empty(index); - } - } guard = { block, index }; - - element = std::move(el); // NOLINT - } - else { - element = std::move(el); // NOLINT - el.~T(); // NOLINT - block->ConcurrentQueue::Block::template set_empty(index); - } - - return true; - } - else { - // Wasn't anything to dequeue after all; make the effective dequeue count eventually consistent - this->dequeueOvercommit.fetch_add(1, std::memory_order_release); // Release so that the fetch_add on dequeueOptimisticCount is guaranteed to happen before this write - } - } - - return false; - } - - template - bool MOODYCAMEL_NO_TSAN enqueue_bulk(It itemFirst, size_t count) - { - // First, we need to make sure we have enough room to enqueue all of the elements; - // this means pre-allocating blocks and putting them in the block index (but only if - // all the allocations succeeded). - index_t startTailIndex = this->tailIndex.load(std::memory_order_relaxed); - auto startBlock = this->tailBlock; - auto originalBlockIndexFront = pr_blockIndexFront; - auto originalBlockIndexSlotsUsed = pr_blockIndexSlotsUsed; - - Block* firstAllocatedBlock = nullptr; - - // Figure out how many blocks we'll need to allocate, and do so - size_t blockBaseDiff = ((startTailIndex + count - 1) & ~static_cast(BLOCK_SIZE - 1)) - ((startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1)); - index_t currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); - if (blockBaseDiff > 0) { - // Allocate as many blocks as possible from ahead - while (blockBaseDiff > 0 && this->tailBlock != nullptr && this->tailBlock->next != firstAllocatedBlock && this->tailBlock->next->ConcurrentQueue::Block::template is_empty()) { - blockBaseDiff -= static_cast(BLOCK_SIZE); - currentTailIndex += static_cast(BLOCK_SIZE); - - this->tailBlock = this->tailBlock->next; - firstAllocatedBlock = firstAllocatedBlock == nullptr ? this->tailBlock : firstAllocatedBlock; - - auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; - entry.base = currentTailIndex; - entry.block = this->tailBlock; - pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); - } - - // Now allocate as many blocks as necessary from the block pool - while (blockBaseDiff > 0) { - blockBaseDiff -= static_cast(BLOCK_SIZE); - currentTailIndex += static_cast(BLOCK_SIZE); - - auto head = this->headIndex.load(std::memory_order_relaxed); - assert(!details::circular_less_than(currentTailIndex, head)); - bool full = !details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head)); - if (pr_blockIndexRaw == nullptr || pr_blockIndexSlotsUsed == pr_blockIndexSize || full) { - MOODYCAMEL_CONSTEXPR_IF (allocMode == CannotAlloc) { - // Failed to allocate, undo changes (but keep injected blocks) - pr_blockIndexFront = originalBlockIndexFront; - pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; - this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; - return false; - } - else if (full || !new_block_index(originalBlockIndexSlotsUsed)) { - // Failed to allocate, undo changes (but keep injected blocks) - pr_blockIndexFront = originalBlockIndexFront; - pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; - this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; - return false; - } - - // pr_blockIndexFront is updated inside new_block_index, so we need to - // update our fallback value too (since we keep the new index even if we - // later fail) - originalBlockIndexFront = originalBlockIndexSlotsUsed; - } - - // Insert a new block in the circular linked list - auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); - if (newBlock == nullptr) { - pr_blockIndexFront = originalBlockIndexFront; - pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; - this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; - return false; - } - -#ifdef MCDBGQ_TRACKMEM - newBlock->owner = this; -#endif - newBlock->ConcurrentQueue::Block::template set_all_empty(); - if (this->tailBlock == nullptr) { - newBlock->next = newBlock; - } - else { - newBlock->next = this->tailBlock->next; - this->tailBlock->next = newBlock; - } - this->tailBlock = newBlock; - firstAllocatedBlock = firstAllocatedBlock == nullptr ? this->tailBlock : firstAllocatedBlock; - - ++pr_blockIndexSlotsUsed; - - auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; - entry.base = currentTailIndex; - entry.block = this->tailBlock; - pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); - } - - // Excellent, all allocations succeeded. Reset each block's emptiness before we fill them up, and - // publish the new block index front - auto block = firstAllocatedBlock; - while (true) { - block->ConcurrentQueue::Block::template reset_empty(); - if (block == this->tailBlock) { - break; - } - block = block->next; - } - - MOODYCAMEL_CONSTEXPR_IF (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (static_cast(nullptr)) T(details::deref_noexcept(itemFirst)))) { - blockIndex.load(std::memory_order_relaxed)->front.store((pr_blockIndexFront - 1) & (pr_blockIndexSize - 1), std::memory_order_release); - } - } - - // Enqueue, one block at a time - index_t newTailIndex = startTailIndex + static_cast(count); - currentTailIndex = startTailIndex; - auto endBlock = this->tailBlock; - this->tailBlock = startBlock; - assert((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr || count == 0); - if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0 && firstAllocatedBlock != nullptr) { - this->tailBlock = firstAllocatedBlock; - } - while (true) { - index_t stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); - if (details::circular_less_than(newTailIndex, stopIndex)) { - stopIndex = newTailIndex; - } - MOODYCAMEL_CONSTEXPR_IF (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (static_cast(nullptr)) T(details::deref_noexcept(itemFirst)))) { - while (currentTailIndex != stopIndex) { - new ((*this->tailBlock)[currentTailIndex++]) T(*itemFirst++); - } - } - else { - MOODYCAMEL_TRY { - while (currentTailIndex != stopIndex) { - // Must use copy constructor even if move constructor is available - // because we may have to revert if there's an exception. - // Sorry about the horrible templated next line, but it was the only way - // to disable moving *at compile time*, which is important because a type - // may only define a (noexcept) move constructor, and so calls to the - // cctor will not compile, even if they are in an if branch that will never - // be executed - new ((*this->tailBlock)[currentTailIndex]) T(details::nomove_if(nullptr)) T(details::deref_noexcept(itemFirst)))>::eval(*itemFirst)); - ++currentTailIndex; - ++itemFirst; - } - } - MOODYCAMEL_CATCH (...) { - // Oh dear, an exception's been thrown -- destroy the elements that - // were enqueued so far and revert the entire bulk operation (we'll keep - // any allocated blocks in our linked list for later, though). - auto constructedStopIndex = currentTailIndex; - auto lastBlockEnqueued = this->tailBlock; - - pr_blockIndexFront = originalBlockIndexFront; - pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; - this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; - - if (!details::is_trivially_destructible::value) { - auto block = startBlock; - if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { - block = firstAllocatedBlock; - } - currentTailIndex = startTailIndex; - while (true) { - stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); - if (details::circular_less_than(constructedStopIndex, stopIndex)) { - stopIndex = constructedStopIndex; - } - while (currentTailIndex != stopIndex) { - (*block)[currentTailIndex++]->~T(); - } - if (block == lastBlockEnqueued) { - break; - } - block = block->next; - } - } - MOODYCAMEL_RETHROW; - } - } - - if (this->tailBlock == endBlock) { - assert(currentTailIndex == newTailIndex); - break; - } - this->tailBlock = this->tailBlock->next; - } - - MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (static_cast(nullptr)) T(details::deref_noexcept(itemFirst)))) { - if (firstAllocatedBlock != nullptr) - blockIndex.load(std::memory_order_relaxed)->front.store((pr_blockIndexFront - 1) & (pr_blockIndexSize - 1), std::memory_order_release); - } - - this->tailIndex.store(newTailIndex, std::memory_order_release); - return true; - } - - template - size_t dequeue_bulk(It& itemFirst, size_t max) - { - auto tail = this->tailIndex.load(std::memory_order_relaxed); - auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); - auto desiredCount = static_cast(tail - (this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit)); - if (details::circular_less_than(0, desiredCount)) { - desiredCount = desiredCount < max ? desiredCount : max; - std::atomic_thread_fence(std::memory_order_acquire); - - auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(desiredCount, std::memory_order_relaxed); - - tail = this->tailIndex.load(std::memory_order_acquire); - auto actualCount = static_cast(tail - (myDequeueCount - overcommit)); - if (details::circular_less_than(0, actualCount)) { - actualCount = desiredCount < actualCount ? desiredCount : actualCount; - if (actualCount < desiredCount) { - this->dequeueOvercommit.fetch_add(desiredCount - actualCount, std::memory_order_release); - } - - // Get the first index. Note that since there's guaranteed to be at least actualCount elements, this - // will never exceed tail. - auto firstIndex = this->headIndex.fetch_add(actualCount, std::memory_order_acq_rel); - - // Determine which block the first element is in - auto localBlockIndex = blockIndex.load(std::memory_order_acquire); - auto localBlockIndexHead = localBlockIndex->front.load(std::memory_order_acquire); - - auto headBase = localBlockIndex->entries[localBlockIndexHead].base; - auto firstBlockBaseIndex = firstIndex & ~static_cast(BLOCK_SIZE - 1); - auto offset = static_cast(static_cast::type>(firstBlockBaseIndex - headBase) / static_cast::type>(BLOCK_SIZE)); - auto indexIndex = (localBlockIndexHead + offset) & (localBlockIndex->size - 1); - - // Iterate the blocks and dequeue - auto index = firstIndex; - do { - auto firstIndexInBlock = index; - index_t endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); - endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; - auto block = localBlockIndex->entries[indexIndex].block; - if (MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, details::deref_noexcept(itemFirst) = std::move((*(*block)[index])))) { - while (index != endIndex) { - auto& el = *((*block)[index]); - *itemFirst++ = std::move(el); - el.~T(); - ++index; - } - } - else { - MOODYCAMEL_TRY { - while (index != endIndex) { - auto& el = *((*block)[index]); - *itemFirst = std::move(el); - ++itemFirst; - el.~T(); - ++index; - } - } - MOODYCAMEL_CATCH (...) { - // It's too late to revert the dequeue, but we can make sure that all - // the dequeued objects are properly destroyed and the block index - // (and empty count) are properly updated before we propagate the exception - do { - block = localBlockIndex->entries[indexIndex].block; - while (index != endIndex) { - (*block)[index++]->~T(); - } - block->ConcurrentQueue::Block::template set_many_empty(firstIndexInBlock, static_cast(endIndex - firstIndexInBlock)); - indexIndex = (indexIndex + 1) & (localBlockIndex->size - 1); - - firstIndexInBlock = index; - endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); - endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; - } while (index != firstIndex + actualCount); - - MOODYCAMEL_RETHROW; - } - } - block->ConcurrentQueue::Block::template set_many_empty(firstIndexInBlock, static_cast(endIndex - firstIndexInBlock)); - indexIndex = (indexIndex + 1) & (localBlockIndex->size - 1); - } while (index != firstIndex + actualCount); - - return actualCount; - } - else { - // Wasn't anything to dequeue after all; make the effective dequeue count eventually consistent - this->dequeueOvercommit.fetch_add(desiredCount, std::memory_order_release); - } - } - - return 0; - } - - private: - struct BlockIndexEntry - { - index_t base; - Block* block; - }; - - struct BlockIndexHeader - { - size_t size; - std::atomic front; // Current slot (not next, like pr_blockIndexFront) - BlockIndexEntry* entries; - void* prev; - }; - - - bool new_block_index(size_t numberOfFilledSlotsToExpose) - { - auto prevBlockSizeMask = pr_blockIndexSize - 1; - - // Create the new block - pr_blockIndexSize <<= 1; - auto newRawPtr = static_cast((Traits::malloc)(sizeof(BlockIndexHeader) + std::alignment_of::value - 1 + sizeof(BlockIndexEntry) * pr_blockIndexSize)); - if (newRawPtr == nullptr) { - pr_blockIndexSize >>= 1; // Reset to allow graceful retry - return false; - } - - auto newBlockIndexEntries = reinterpret_cast(details::align_for(newRawPtr + sizeof(BlockIndexHeader))); - - // Copy in all the old indices, if any - size_t j = 0; - if (pr_blockIndexSlotsUsed != 0) { - auto i = (pr_blockIndexFront - pr_blockIndexSlotsUsed) & prevBlockSizeMask; - do { - newBlockIndexEntries[j++] = pr_blockIndexEntries[i]; - i = (i + 1) & prevBlockSizeMask; - } while (i != pr_blockIndexFront); - } - - // Update everything - auto header = new (newRawPtr) BlockIndexHeader; - header->size = pr_blockIndexSize; - header->front.store(numberOfFilledSlotsToExpose - 1, std::memory_order_relaxed); - header->entries = newBlockIndexEntries; - header->prev = pr_blockIndexRaw; // we link the new block to the old one so we can free it later - - pr_blockIndexFront = j; - pr_blockIndexEntries = newBlockIndexEntries; - pr_blockIndexRaw = newRawPtr; - blockIndex.store(header, std::memory_order_release); - - return true; - } - - private: - std::atomic blockIndex; - - // To be used by producer only -- consumer must use the ones in referenced by blockIndex - size_t pr_blockIndexSlotsUsed; - size_t pr_blockIndexSize; - size_t pr_blockIndexFront; // Next slot (not current) - BlockIndexEntry* pr_blockIndexEntries; - void* pr_blockIndexRaw; - -#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG - public: - ExplicitProducer* nextExplicitProducer; - private: -#endif - -#ifdef MCDBGQ_TRACKMEM - friend struct MemStats; -#endif - }; - - - ////////////////////////////////// - // Implicit queue - ////////////////////////////////// - - struct ImplicitProducer : public ProducerBase - { - ImplicitProducer(ConcurrentQueue* parent_) : - ProducerBase(parent_, false), - nextBlockIndexCapacity(IMPLICIT_INITIAL_INDEX_SIZE), - blockIndex(nullptr) - { - new_block_index(); - } - - ~ImplicitProducer() - { - // Note that since we're in the destructor we can assume that all enqueue/dequeue operations - // completed already; this means that all undequeued elements are placed contiguously across - // contiguous blocks, and that only the first and last remaining blocks can be only partially - // empty (all other remaining blocks must be completely full). - -#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED - // Unregister ourselves for thread termination notification - if (!this->inactive.load(std::memory_order_relaxed)) { - details::ThreadExitNotifier::unsubscribe(&threadExitListener); - } -#endif - - // Destroy all remaining elements! - auto tail = this->tailIndex.load(std::memory_order_relaxed); - auto index = this->headIndex.load(std::memory_order_relaxed); - Block* block = nullptr; - assert(index == tail || details::circular_less_than(index, tail)); - bool forceFreeLastBlock = index != tail; // If we enter the loop, then the last (tail) block will not be freed - while (index != tail) { - if ((index & static_cast(BLOCK_SIZE - 1)) == 0 || block == nullptr) { - if (block != nullptr) { - // Free the old block - this->parent->add_block_to_free_list(block); - } - - block = get_block_index_entry_for_index(index)->value.load(std::memory_order_relaxed); - } - - ((*block)[index])->~T(); - ++index; - } - // Even if the queue is empty, there's still one block that's not on the free list - // (unless the head index reached the end of it, in which case the tail will be poised - // to create a new block). - if (this->tailBlock != nullptr && (forceFreeLastBlock || (tail & static_cast(BLOCK_SIZE - 1)) != 0)) { - this->parent->add_block_to_free_list(this->tailBlock); - } - - // Destroy block index - auto localBlockIndex = blockIndex.load(std::memory_order_relaxed); - if (localBlockIndex != nullptr) { - for (size_t i = 0; i != localBlockIndex->capacity; ++i) { - localBlockIndex->index[i]->~BlockIndexEntry(); - } - do { - auto prev = localBlockIndex->prev; - localBlockIndex->~BlockIndexHeader(); - (Traits::free)(localBlockIndex); - localBlockIndex = prev; - } while (localBlockIndex != nullptr); - } - } - - template - inline bool enqueue(U&& element) - { - index_t currentTailIndex = this->tailIndex.load(std::memory_order_relaxed); - index_t newTailIndex = 1 + currentTailIndex; - if ((currentTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { - // We reached the end of a block, start a new one - auto head = this->headIndex.load(std::memory_order_relaxed); - assert(!details::circular_less_than(currentTailIndex, head)); - if (!details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head))) { - return false; - } -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX - debug::DebugLock lock(mutex); -#endif - // Find out where we'll be inserting this block in the block index - BlockIndexEntry* idxEntry; - if (!insert_block_index_entry(idxEntry, currentTailIndex)) { - return false; - } - - // Get ahold of a new block - auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); - if (newBlock == nullptr) { - rewind_block_index_tail(); - idxEntry->value.store(nullptr, std::memory_order_relaxed); - return false; - } -#ifdef MCDBGQ_TRACKMEM - newBlock->owner = this; -#endif - newBlock->ConcurrentQueue::Block::template reset_empty(); - - MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(element)))) { - // May throw, try to insert now before we publish the fact that we have this new block - MOODYCAMEL_TRY { - new ((*newBlock)[currentTailIndex]) T(std::forward(element)); - } - MOODYCAMEL_CATCH (...) { - rewind_block_index_tail(); - idxEntry->value.store(nullptr, std::memory_order_relaxed); - this->parent->add_block_to_free_list(newBlock); - MOODYCAMEL_RETHROW; - } - } - - // Insert the new block into the index - idxEntry->value.store(newBlock, std::memory_order_relaxed); - - this->tailBlock = newBlock; - - MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(element)))) { - this->tailIndex.store(newTailIndex, std::memory_order_release); - return true; - } - } - - // Enqueue - new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); - - this->tailIndex.store(newTailIndex, std::memory_order_release); - return true; - } - - template - bool dequeue(U& element) - { - // See ExplicitProducer::dequeue for rationale and explanation - index_t tail = this->tailIndex.load(std::memory_order_relaxed); - index_t overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); - if (details::circular_less_than(this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit, tail)) { - std::atomic_thread_fence(std::memory_order_acquire); - - index_t myDequeueCount = this->dequeueOptimisticCount.fetch_add(1, std::memory_order_relaxed); - tail = this->tailIndex.load(std::memory_order_acquire); - if ((details::likely)(details::circular_less_than(myDequeueCount - overcommit, tail))) { - index_t index = this->headIndex.fetch_add(1, std::memory_order_acq_rel); - - // Determine which block the element is in - auto entry = get_block_index_entry_for_index(index); - - // Dequeue - auto block = entry->value.load(std::memory_order_relaxed); - auto& el = *((*block)[index]); - - if (!MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, element = std::move(el))) { -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX - // Note: Acquiring the mutex with every dequeue instead of only when a block - // is released is very sub-optimal, but it is, after all, purely debug code. - debug::DebugLock lock(producer->mutex); -#endif - struct Guard { - Block* block; - index_t index; - BlockIndexEntry* entry; - ConcurrentQueue* parent; - - ~Guard() - { - (*block)[index]->~T(); - if (block->ConcurrentQueue::Block::template set_empty(index)) { - entry->value.store(nullptr, std::memory_order_relaxed); - parent->add_block_to_free_list(block); - } - } - } guard = { block, index, entry, this->parent }; - - element = std::move(el); // NOLINT - } - else { - element = std::move(el); // NOLINT - el.~T(); // NOLINT - - if (block->ConcurrentQueue::Block::template set_empty(index)) { - { -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX - debug::DebugLock lock(mutex); -#endif - // Add the block back into the global free pool (and remove from block index) - entry->value.store(nullptr, std::memory_order_relaxed); - } - this->parent->add_block_to_free_list(block); // releases the above store - } - } - - return true; - } - else { - this->dequeueOvercommit.fetch_add(1, std::memory_order_release); - } - } - - return false; - } - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable: 4706) // assignment within conditional expression -#endif - template - bool enqueue_bulk(It itemFirst, size_t count) - { - // First, we need to make sure we have enough room to enqueue all of the elements; - // this means pre-allocating blocks and putting them in the block index (but only if - // all the allocations succeeded). - - // Note that the tailBlock we start off with may not be owned by us any more; - // this happens if it was filled up exactly to the top (setting tailIndex to - // the first index of the next block which is not yet allocated), then dequeued - // completely (putting it on the free list) before we enqueue again. - - index_t startTailIndex = this->tailIndex.load(std::memory_order_relaxed); - auto startBlock = this->tailBlock; - Block* firstAllocatedBlock = nullptr; - auto endBlock = this->tailBlock; - - // Figure out how many blocks we'll need to allocate, and do so - size_t blockBaseDiff = ((startTailIndex + count - 1) & ~static_cast(BLOCK_SIZE - 1)) - ((startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1)); - index_t currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); - if (blockBaseDiff > 0) { -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX - debug::DebugLock lock(mutex); -#endif - do { - blockBaseDiff -= static_cast(BLOCK_SIZE); - currentTailIndex += static_cast(BLOCK_SIZE); - - // Find out where we'll be inserting this block in the block index - BlockIndexEntry* idxEntry = nullptr; // initialization here unnecessary but compiler can't always tell - Block* newBlock; - bool indexInserted = false; - auto head = this->headIndex.load(std::memory_order_relaxed); - assert(!details::circular_less_than(currentTailIndex, head)); - bool full = !details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head)); - - if (full || !(indexInserted = insert_block_index_entry(idxEntry, currentTailIndex)) || (newBlock = this->parent->ConcurrentQueue::template requisition_block()) == nullptr) { - // Index allocation or block allocation failed; revert any other allocations - // and index insertions done so far for this operation - if (indexInserted) { - rewind_block_index_tail(); - idxEntry->value.store(nullptr, std::memory_order_relaxed); - } - currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); - for (auto block = firstAllocatedBlock; block != nullptr; block = block->next) { - currentTailIndex += static_cast(BLOCK_SIZE); - idxEntry = get_block_index_entry_for_index(currentTailIndex); - idxEntry->value.store(nullptr, std::memory_order_relaxed); - rewind_block_index_tail(); - } - this->parent->add_blocks_to_free_list(firstAllocatedBlock); - this->tailBlock = startBlock; - - return false; - } - -#ifdef MCDBGQ_TRACKMEM - newBlock->owner = this; -#endif - newBlock->ConcurrentQueue::Block::template reset_empty(); - newBlock->next = nullptr; - - // Insert the new block into the index - idxEntry->value.store(newBlock, std::memory_order_relaxed); - - // Store the chain of blocks so that we can undo if later allocations fail, - // and so that we can find the blocks when we do the actual enqueueing - if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr) { - assert(this->tailBlock != nullptr); - this->tailBlock->next = newBlock; - } - this->tailBlock = newBlock; - endBlock = newBlock; - firstAllocatedBlock = firstAllocatedBlock == nullptr ? newBlock : firstAllocatedBlock; - } while (blockBaseDiff > 0); - } - - // Enqueue, one block at a time - index_t newTailIndex = startTailIndex + static_cast(count); - currentTailIndex = startTailIndex; - this->tailBlock = startBlock; - assert((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr || count == 0); - if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0 && firstAllocatedBlock != nullptr) { - this->tailBlock = firstAllocatedBlock; - } - while (true) { - index_t stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); - if (details::circular_less_than(newTailIndex, stopIndex)) { - stopIndex = newTailIndex; - } - MOODYCAMEL_CONSTEXPR_IF (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (static_cast(nullptr)) T(details::deref_noexcept(itemFirst)))) { - while (currentTailIndex != stopIndex) { - new ((*this->tailBlock)[currentTailIndex++]) T(*itemFirst++); - } - } - else { - MOODYCAMEL_TRY { - while (currentTailIndex != stopIndex) { - new ((*this->tailBlock)[currentTailIndex]) T(details::nomove_if(nullptr)) T(details::deref_noexcept(itemFirst)))>::eval(*itemFirst)); - ++currentTailIndex; - ++itemFirst; - } - } - MOODYCAMEL_CATCH (...) { - auto constructedStopIndex = currentTailIndex; - auto lastBlockEnqueued = this->tailBlock; - - if (!details::is_trivially_destructible::value) { - auto block = startBlock; - if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { - block = firstAllocatedBlock; - } - currentTailIndex = startTailIndex; - while (true) { - stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); - if (details::circular_less_than(constructedStopIndex, stopIndex)) { - stopIndex = constructedStopIndex; - } - while (currentTailIndex != stopIndex) { - (*block)[currentTailIndex++]->~T(); - } - if (block == lastBlockEnqueued) { - break; - } - block = block->next; - } - } - - currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); - for (auto block = firstAllocatedBlock; block != nullptr; block = block->next) { - currentTailIndex += static_cast(BLOCK_SIZE); - auto idxEntry = get_block_index_entry_for_index(currentTailIndex); - idxEntry->value.store(nullptr, std::memory_order_relaxed); - rewind_block_index_tail(); - } - this->parent->add_blocks_to_free_list(firstAllocatedBlock); - this->tailBlock = startBlock; - MOODYCAMEL_RETHROW; - } - } - - if (this->tailBlock == endBlock) { - assert(currentTailIndex == newTailIndex); - break; - } - this->tailBlock = this->tailBlock->next; - } - this->tailIndex.store(newTailIndex, std::memory_order_release); - return true; - } -#ifdef _MSC_VER -#pragma warning(pop) -#endif - - template - size_t dequeue_bulk(It& itemFirst, size_t max) - { - auto tail = this->tailIndex.load(std::memory_order_relaxed); - auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); - auto desiredCount = static_cast(tail - (this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit)); - if (details::circular_less_than(0, desiredCount)) { - desiredCount = desiredCount < max ? desiredCount : max; - std::atomic_thread_fence(std::memory_order_acquire); - - auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(desiredCount, std::memory_order_relaxed); - - tail = this->tailIndex.load(std::memory_order_acquire); - auto actualCount = static_cast(tail - (myDequeueCount - overcommit)); - if (details::circular_less_than(0, actualCount)) { - actualCount = desiredCount < actualCount ? desiredCount : actualCount; - if (actualCount < desiredCount) { - this->dequeueOvercommit.fetch_add(desiredCount - actualCount, std::memory_order_release); - } - - // Get the first index. Note that since there's guaranteed to be at least actualCount elements, this - // will never exceed tail. - auto firstIndex = this->headIndex.fetch_add(actualCount, std::memory_order_acq_rel); - - // Iterate the blocks and dequeue - auto index = firstIndex; - BlockIndexHeader* localBlockIndex; - auto indexIndex = get_block_index_index_for_index(index, localBlockIndex); - do { - auto blockStartIndex = index; - index_t endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); - endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; - - auto entry = localBlockIndex->index[indexIndex]; - auto block = entry->value.load(std::memory_order_relaxed); - if (MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, details::deref_noexcept(itemFirst) = std::move((*(*block)[index])))) { - while (index != endIndex) { - auto& el = *((*block)[index]); - *itemFirst++ = std::move(el); - el.~T(); - ++index; - } - } - else { - MOODYCAMEL_TRY { - while (index != endIndex) { - auto& el = *((*block)[index]); - *itemFirst = std::move(el); - ++itemFirst; - el.~T(); - ++index; - } - } - MOODYCAMEL_CATCH (...) { - do { - entry = localBlockIndex->index[indexIndex]; - block = entry->value.load(std::memory_order_relaxed); - while (index != endIndex) { - (*block)[index++]->~T(); - } - - if (block->ConcurrentQueue::Block::template set_many_empty(blockStartIndex, static_cast(endIndex - blockStartIndex))) { -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX - debug::DebugLock lock(mutex); -#endif - entry->value.store(nullptr, std::memory_order_relaxed); - this->parent->add_block_to_free_list(block); - } - indexIndex = (indexIndex + 1) & (localBlockIndex->capacity - 1); - - blockStartIndex = index; - endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); - endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; - } while (index != firstIndex + actualCount); - - MOODYCAMEL_RETHROW; - } - } - if (block->ConcurrentQueue::Block::template set_many_empty(blockStartIndex, static_cast(endIndex - blockStartIndex))) { - { -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX - debug::DebugLock lock(mutex); -#endif - // Note that the set_many_empty above did a release, meaning that anybody who acquires the block - // we're about to free can use it safely since our writes (and reads!) will have happened-before then. - entry->value.store(nullptr, std::memory_order_relaxed); - } - this->parent->add_block_to_free_list(block); // releases the above store - } - indexIndex = (indexIndex + 1) & (localBlockIndex->capacity - 1); - } while (index != firstIndex + actualCount); - - return actualCount; - } - else { - this->dequeueOvercommit.fetch_add(desiredCount, std::memory_order_release); - } - } - - return 0; - } - - private: - // The block size must be > 1, so any number with the low bit set is an invalid block base index - static const index_t INVALID_BLOCK_BASE = 1; - - struct BlockIndexEntry - { - std::atomic key; - std::atomic value; - }; - - struct BlockIndexHeader - { - size_t capacity; - std::atomic tail; - BlockIndexEntry* entries; - BlockIndexEntry** index; - BlockIndexHeader* prev; - }; - - template - inline bool insert_block_index_entry(BlockIndexEntry*& idxEntry, index_t blockStartIndex) - { - auto localBlockIndex = blockIndex.load(std::memory_order_relaxed); // We're the only writer thread, relaxed is OK - if (localBlockIndex == nullptr) { - return false; // this can happen if new_block_index failed in the constructor - } - size_t newTail = (localBlockIndex->tail.load(std::memory_order_relaxed) + 1) & (localBlockIndex->capacity - 1); - idxEntry = localBlockIndex->index[newTail]; - if (idxEntry->key.load(std::memory_order_relaxed) == INVALID_BLOCK_BASE || - idxEntry->value.load(std::memory_order_relaxed) == nullptr) { - - idxEntry->key.store(blockStartIndex, std::memory_order_relaxed); - localBlockIndex->tail.store(newTail, std::memory_order_release); - return true; - } - - // No room in the old block index, try to allocate another one! - MOODYCAMEL_CONSTEXPR_IF (allocMode == CannotAlloc) { - return false; - } - else if (!new_block_index()) { - return false; - } - else { - localBlockIndex = blockIndex.load(std::memory_order_relaxed); - newTail = (localBlockIndex->tail.load(std::memory_order_relaxed) + 1) & (localBlockIndex->capacity - 1); - idxEntry = localBlockIndex->index[newTail]; - assert(idxEntry->key.load(std::memory_order_relaxed) == INVALID_BLOCK_BASE); - idxEntry->key.store(blockStartIndex, std::memory_order_relaxed); - localBlockIndex->tail.store(newTail, std::memory_order_release); - return true; - } - } - - inline void rewind_block_index_tail() - { - auto localBlockIndex = blockIndex.load(std::memory_order_relaxed); - localBlockIndex->tail.store((localBlockIndex->tail.load(std::memory_order_relaxed) - 1) & (localBlockIndex->capacity - 1), std::memory_order_relaxed); - } - - inline BlockIndexEntry* get_block_index_entry_for_index(index_t index) const - { - BlockIndexHeader* localBlockIndex; - auto idx = get_block_index_index_for_index(index, localBlockIndex); - return localBlockIndex->index[idx]; - } - - inline size_t get_block_index_index_for_index(index_t index, BlockIndexHeader*& localBlockIndex) const - { -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX - debug::DebugLock lock(mutex); -#endif - index &= ~static_cast(BLOCK_SIZE - 1); - localBlockIndex = blockIndex.load(std::memory_order_acquire); - auto tail = localBlockIndex->tail.load(std::memory_order_acquire); - auto tailBase = localBlockIndex->index[tail]->key.load(std::memory_order_relaxed); - assert(tailBase != INVALID_BLOCK_BASE); - // Note: Must use division instead of shift because the index may wrap around, causing a negative - // offset, whose negativity we want to preserve - auto offset = static_cast(static_cast::type>(index - tailBase) / static_cast::type>(BLOCK_SIZE)); - size_t idx = (tail + offset) & (localBlockIndex->capacity - 1); - assert(localBlockIndex->index[idx]->key.load(std::memory_order_relaxed) == index && localBlockIndex->index[idx]->value.load(std::memory_order_relaxed) != nullptr); - return idx; - } - - bool new_block_index() - { - auto prev = blockIndex.load(std::memory_order_relaxed); - size_t prevCapacity = prev == nullptr ? 0 : prev->capacity; - auto entryCount = prev == nullptr ? nextBlockIndexCapacity : prevCapacity; - auto raw = static_cast((Traits::malloc)( - sizeof(BlockIndexHeader) + - std::alignment_of::value - 1 + sizeof(BlockIndexEntry) * entryCount + - std::alignment_of::value - 1 + sizeof(BlockIndexEntry*) * nextBlockIndexCapacity)); - if (raw == nullptr) { - return false; - } - - auto header = new (raw) BlockIndexHeader; - auto entries = reinterpret_cast(details::align_for(raw + sizeof(BlockIndexHeader))); - auto index = reinterpret_cast(details::align_for(reinterpret_cast(entries) + sizeof(BlockIndexEntry) * entryCount)); - if (prev != nullptr) { - auto prevTail = prev->tail.load(std::memory_order_relaxed); - auto prevPos = prevTail; - size_t i = 0; - do { - prevPos = (prevPos + 1) & (prev->capacity - 1); - index[i++] = prev->index[prevPos]; - } while (prevPos != prevTail); - assert(i == prevCapacity); - } - for (size_t i = 0; i != entryCount; ++i) { - new (entries + i) BlockIndexEntry; - entries[i].key.store(INVALID_BLOCK_BASE, std::memory_order_relaxed); - index[prevCapacity + i] = entries + i; - } - header->prev = prev; - header->entries = entries; - header->index = index; - header->capacity = nextBlockIndexCapacity; - header->tail.store((prevCapacity - 1) & (nextBlockIndexCapacity - 1), std::memory_order_relaxed); - - blockIndex.store(header, std::memory_order_release); - - nextBlockIndexCapacity <<= 1; - - return true; - } - - private: - size_t nextBlockIndexCapacity; - std::atomic blockIndex; - -#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED - public: - details::ThreadExitListener threadExitListener; - private: -#endif - -#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG - public: - ImplicitProducer* nextImplicitProducer; - private: -#endif - -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX - mutable debug::DebugMutex mutex; -#endif -#ifdef MCDBGQ_TRACKMEM - friend struct MemStats; -#endif - }; - - - ////////////////////////////////// - // Block pool manipulation - ////////////////////////////////// - - void populate_initial_block_list(size_t blockCount) - { - initialBlockPoolSize = blockCount; - if (initialBlockPoolSize == 0) { - initialBlockPool = nullptr; - return; - } - - initialBlockPool = create_array(blockCount); - if (initialBlockPool == nullptr) { - initialBlockPoolSize = 0; - } - for (size_t i = 0; i < initialBlockPoolSize; ++i) { - initialBlockPool[i].dynamicallyAllocated = false; - } - } - - inline Block* try_get_block_from_initial_pool() - { - if (initialBlockPoolIndex.load(std::memory_order_relaxed) >= initialBlockPoolSize) { - return nullptr; - } - - auto index = initialBlockPoolIndex.fetch_add(1, std::memory_order_relaxed); - - return index < initialBlockPoolSize ? (initialBlockPool + index) : nullptr; - } - - inline void add_block_to_free_list(Block* block) - { -#ifdef MCDBGQ_TRACKMEM - block->owner = nullptr; -#endif - if (!Traits::RECYCLE_ALLOCATED_BLOCKS && block->dynamicallyAllocated) { - destroy(block); - } - else { - freeList.add(block); - } - } - - inline void add_blocks_to_free_list(Block* block) - { - while (block != nullptr) { - auto next = block->next; - add_block_to_free_list(block); - block = next; - } - } - - inline Block* try_get_block_from_free_list() - { - return freeList.try_get(); - } - - // Gets a free block from one of the memory pools, or allocates a new one (if applicable) - template - Block* requisition_block() - { - auto block = try_get_block_from_initial_pool(); - if (block != nullptr) { - return block; - } - - block = try_get_block_from_free_list(); - if (block != nullptr) { - return block; - } - - MOODYCAMEL_CONSTEXPR_IF (canAlloc == CanAlloc) { - return create(); - } - else { - return nullptr; - } - } - - -#ifdef MCDBGQ_TRACKMEM - public: - struct MemStats { - size_t allocatedBlocks; - size_t usedBlocks; - size_t freeBlocks; - size_t ownedBlocksExplicit; - size_t ownedBlocksImplicit; - size_t implicitProducers; - size_t explicitProducers; - size_t elementsEnqueued; - size_t blockClassBytes; - size_t queueClassBytes; - size_t implicitBlockIndexBytes; - size_t explicitBlockIndexBytes; - - friend class ConcurrentQueue; - - private: - static MemStats getFor(ConcurrentQueue* q) - { - MemStats stats = { 0 }; - - stats.elementsEnqueued = q->size_approx(); - - auto block = q->freeList.head_unsafe(); - while (block != nullptr) { - ++stats.allocatedBlocks; - ++stats.freeBlocks; - block = block->freeListNext.load(std::memory_order_relaxed); - } - - for (auto ptr = q->producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { - bool implicit = dynamic_cast(ptr) != nullptr; - stats.implicitProducers += implicit ? 1 : 0; - stats.explicitProducers += implicit ? 0 : 1; - - if (implicit) { - auto prod = static_cast(ptr); - stats.queueClassBytes += sizeof(ImplicitProducer); - auto head = prod->headIndex.load(std::memory_order_relaxed); - auto tail = prod->tailIndex.load(std::memory_order_relaxed); - auto hash = prod->blockIndex.load(std::memory_order_relaxed); - if (hash != nullptr) { - for (size_t i = 0; i != hash->capacity; ++i) { - if (hash->index[i]->key.load(std::memory_order_relaxed) != ImplicitProducer::INVALID_BLOCK_BASE && hash->index[i]->value.load(std::memory_order_relaxed) != nullptr) { - ++stats.allocatedBlocks; - ++stats.ownedBlocksImplicit; - } - } - stats.implicitBlockIndexBytes += hash->capacity * sizeof(typename ImplicitProducer::BlockIndexEntry); - for (; hash != nullptr; hash = hash->prev) { - stats.implicitBlockIndexBytes += sizeof(typename ImplicitProducer::BlockIndexHeader) + hash->capacity * sizeof(typename ImplicitProducer::BlockIndexEntry*); - } - } - for (; details::circular_less_than(head, tail); head += BLOCK_SIZE) { - //auto block = prod->get_block_index_entry_for_index(head); - ++stats.usedBlocks; - } - } - else { - auto prod = static_cast(ptr); - stats.queueClassBytes += sizeof(ExplicitProducer); - auto tailBlock = prod->tailBlock; - bool wasNonEmpty = false; - if (tailBlock != nullptr) { - auto block = tailBlock; - do { - ++stats.allocatedBlocks; - if (!block->ConcurrentQueue::Block::template is_empty() || wasNonEmpty) { - ++stats.usedBlocks; - wasNonEmpty = wasNonEmpty || block != tailBlock; - } - ++stats.ownedBlocksExplicit; - block = block->next; - } while (block != tailBlock); - } - auto index = prod->blockIndex.load(std::memory_order_relaxed); - while (index != nullptr) { - stats.explicitBlockIndexBytes += sizeof(typename ExplicitProducer::BlockIndexHeader) + index->size * sizeof(typename ExplicitProducer::BlockIndexEntry); - index = static_cast(index->prev); - } - } - } - - auto freeOnInitialPool = q->initialBlockPoolIndex.load(std::memory_order_relaxed) >= q->initialBlockPoolSize ? 0 : q->initialBlockPoolSize - q->initialBlockPoolIndex.load(std::memory_order_relaxed); - stats.allocatedBlocks += freeOnInitialPool; - stats.freeBlocks += freeOnInitialPool; - - stats.blockClassBytes = sizeof(Block) * stats.allocatedBlocks; - stats.queueClassBytes += sizeof(ConcurrentQueue); - - return stats; - } - }; - - // For debugging only. Not thread-safe. - MemStats getMemStats() - { - return MemStats::getFor(this); - } - private: - friend struct MemStats; -#endif - - - ////////////////////////////////// - // Producer list manipulation - ////////////////////////////////// - - ProducerBase* recycle_or_create_producer(bool isExplicit) - { -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH - debug::DebugLock lock(implicitProdMutex); -#endif - // Try to re-use one first - for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { - if (ptr->inactive.load(std::memory_order_relaxed) && ptr->isExplicit == isExplicit) { - bool expected = true; - if (ptr->inactive.compare_exchange_strong(expected, /* desired */ false, std::memory_order_acquire, std::memory_order_relaxed)) { - // We caught one! It's been marked as activated, the caller can have it - return ptr; - } - } - } - - return add_producer(isExplicit ? static_cast(create(this)) : create(this)); - } - - ProducerBase* add_producer(ProducerBase* producer) - { - // Handle failed memory allocation - if (producer == nullptr) { - return nullptr; - } - - producerCount.fetch_add(1, std::memory_order_relaxed); - - // Add it to the lock-free list - auto prevTail = producerListTail.load(std::memory_order_relaxed); - do { - producer->next = prevTail; - } while (!producerListTail.compare_exchange_weak(prevTail, producer, std::memory_order_release, std::memory_order_relaxed)); - -#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG - if (producer->isExplicit) { - auto prevTailExplicit = explicitProducers.load(std::memory_order_relaxed); - do { - static_cast(producer)->nextExplicitProducer = prevTailExplicit; - } while (!explicitProducers.compare_exchange_weak(prevTailExplicit, static_cast(producer), std::memory_order_release, std::memory_order_relaxed)); - } - else { - auto prevTailImplicit = implicitProducers.load(std::memory_order_relaxed); - do { - static_cast(producer)->nextImplicitProducer = prevTailImplicit; - } while (!implicitProducers.compare_exchange_weak(prevTailImplicit, static_cast(producer), std::memory_order_release, std::memory_order_relaxed)); - } -#endif - - return producer; - } - - void reown_producers() - { - // After another instance is moved-into/swapped-with this one, all the - // producers we stole still think their parents are the other queue. - // So fix them up! - for (auto ptr = producerListTail.load(std::memory_order_relaxed); ptr != nullptr; ptr = ptr->next_prod()) { - ptr->parent = this; - } - } - - - ////////////////////////////////// - // Implicit producer hash - ////////////////////////////////// - - struct ImplicitProducerKVP - { - std::atomic key; - ImplicitProducer* value; // No need for atomicity since it's only read by the thread that sets it in the first place - - ImplicitProducerKVP() : value(nullptr) { } - - ImplicitProducerKVP(ImplicitProducerKVP&& other) MOODYCAMEL_NOEXCEPT - { - key.store(other.key.load(std::memory_order_relaxed), std::memory_order_relaxed); - value = other.value; - } - - inline ImplicitProducerKVP& operator=(ImplicitProducerKVP&& other) MOODYCAMEL_NOEXCEPT - { - swap(other); - return *this; - } - - inline void swap(ImplicitProducerKVP& other) MOODYCAMEL_NOEXCEPT - { - if (this != &other) { - details::swap_relaxed(key, other.key); - std::swap(value, other.value); - } - } - }; - - template - friend void moodycamel::swap(typename ConcurrentQueue::ImplicitProducerKVP&, typename ConcurrentQueue::ImplicitProducerKVP&) MOODYCAMEL_NOEXCEPT; - - struct ImplicitProducerHash - { - size_t capacity; - ImplicitProducerKVP* entries; - ImplicitProducerHash* prev; - }; - - inline void populate_initial_implicit_producer_hash() - { - MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) { - return; - } - else { - implicitProducerHashCount.store(0, std::memory_order_relaxed); - auto hash = &initialImplicitProducerHash; - hash->capacity = INITIAL_IMPLICIT_PRODUCER_HASH_SIZE; - hash->entries = &initialImplicitProducerHashEntries[0]; - for (size_t i = 0; i != INITIAL_IMPLICIT_PRODUCER_HASH_SIZE; ++i) { - initialImplicitProducerHashEntries[i].key.store(details::invalid_thread_id, std::memory_order_relaxed); - } - hash->prev = nullptr; - implicitProducerHash.store(hash, std::memory_order_relaxed); - } - } - - void swap_implicit_producer_hashes(ConcurrentQueue& other) - { - MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) { - return; - } - else { - // Swap (assumes our implicit producer hash is initialized) - initialImplicitProducerHashEntries.swap(other.initialImplicitProducerHashEntries); - initialImplicitProducerHash.entries = &initialImplicitProducerHashEntries[0]; - other.initialImplicitProducerHash.entries = &other.initialImplicitProducerHashEntries[0]; - - details::swap_relaxed(implicitProducerHashCount, other.implicitProducerHashCount); - - details::swap_relaxed(implicitProducerHash, other.implicitProducerHash); - if (implicitProducerHash.load(std::memory_order_relaxed) == &other.initialImplicitProducerHash) { - implicitProducerHash.store(&initialImplicitProducerHash, std::memory_order_relaxed); - } - else { - ImplicitProducerHash* hash; - for (hash = implicitProducerHash.load(std::memory_order_relaxed); hash->prev != &other.initialImplicitProducerHash; hash = hash->prev) { - continue; - } - hash->prev = &initialImplicitProducerHash; - } - if (other.implicitProducerHash.load(std::memory_order_relaxed) == &initialImplicitProducerHash) { - other.implicitProducerHash.store(&other.initialImplicitProducerHash, std::memory_order_relaxed); - } - else { - ImplicitProducerHash* hash; - for (hash = other.implicitProducerHash.load(std::memory_order_relaxed); hash->prev != &initialImplicitProducerHash; hash = hash->prev) { - continue; - } - hash->prev = &other.initialImplicitProducerHash; - } - } - } - - // Only fails (returns nullptr) if memory allocation fails - ImplicitProducer* get_or_add_implicit_producer() - { - // Note that since the data is essentially thread-local (key is thread ID), - // there's a reduced need for fences (memory ordering is already consistent - // for any individual thread), except for the current table itself. - - // Start by looking for the thread ID in the current and all previous hash tables. - // If it's not found, it must not be in there yet, since this same thread would - // have added it previously to one of the tables that we traversed. - - // Code and algorithm adapted from http://preshing.com/20130605/the-worlds-simplest-lock-free-hash-table - -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH - debug::DebugLock lock(implicitProdMutex); -#endif - - auto id = details::thread_id(); - auto hashedId = details::hash_thread_id(id); - - auto mainHash = implicitProducerHash.load(std::memory_order_acquire); - assert(mainHash != nullptr); // silence clang-tidy and MSVC warnings (hash cannot be null) - for (auto hash = mainHash; hash != nullptr; hash = hash->prev) { - // Look for the id in this hash - auto index = hashedId; - while (true) { // Not an infinite loop because at least one slot is free in the hash table - index &= hash->capacity - 1u; - - auto probedKey = hash->entries[index].key.load(std::memory_order_relaxed); - if (probedKey == id) { - // Found it! If we had to search several hashes deep, though, we should lazily add it - // to the current main hash table to avoid the extended search next time. - // Note there's guaranteed to be room in the current hash table since every subsequent - // table implicitly reserves space for all previous tables (there's only one - // implicitProducerHashCount). - auto value = hash->entries[index].value; - if (hash != mainHash) { - index = hashedId; - while (true) { - index &= mainHash->capacity - 1u; - auto empty = details::invalid_thread_id; -#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED - auto reusable = details::invalid_thread_id2; - if (mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_seq_cst, std::memory_order_relaxed) || - mainHash->entries[index].key.compare_exchange_strong(reusable, id, std::memory_order_seq_cst, std::memory_order_relaxed)) { -#else - if (mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_seq_cst, std::memory_order_relaxed)) { -#endif - mainHash->entries[index].value = value; - break; - } - ++index; - } - } - - return value; - } - if (probedKey == details::invalid_thread_id) { - break; // Not in this hash table - } - ++index; - } - } - - // Insert! - auto newCount = 1 + implicitProducerHashCount.fetch_add(1, std::memory_order_relaxed); - while (true) { - // NOLINTNEXTLINE(clang-analyzer-core.NullDereference) - if (newCount >= (mainHash->capacity >> 1) && !implicitProducerHashResizeInProgress.test_and_set(std::memory_order_acquire)) { - // We've acquired the resize lock, try to allocate a bigger hash table. - // Note the acquire fence synchronizes with the release fence at the end of this block, and hence when - // we reload implicitProducerHash it must be the most recent version (it only gets changed within this - // locked block). - mainHash = implicitProducerHash.load(std::memory_order_acquire); - if (newCount >= (mainHash->capacity >> 1)) { - size_t newCapacity = mainHash->capacity << 1; - while (newCount >= (newCapacity >> 1)) { - newCapacity <<= 1; - } - auto raw = static_cast((Traits::malloc)(sizeof(ImplicitProducerHash) + std::alignment_of::value - 1 + sizeof(ImplicitProducerKVP) * newCapacity)); - if (raw == nullptr) { - // Allocation failed - implicitProducerHashCount.fetch_sub(1, std::memory_order_relaxed); - implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); - return nullptr; - } - - auto newHash = new (raw) ImplicitProducerHash; - newHash->capacity = static_cast(newCapacity); - newHash->entries = reinterpret_cast(details::align_for(raw + sizeof(ImplicitProducerHash))); - for (size_t i = 0; i != newCapacity; ++i) { - new (newHash->entries + i) ImplicitProducerKVP; - newHash->entries[i].key.store(details::invalid_thread_id, std::memory_order_relaxed); - } - newHash->prev = mainHash; - implicitProducerHash.store(newHash, std::memory_order_release); - implicitProducerHashResizeInProgress.clear(std::memory_order_release); - mainHash = newHash; - } - else { - implicitProducerHashResizeInProgress.clear(std::memory_order_release); - } - } - - // If it's < three-quarters full, add to the old one anyway so that we don't have to wait for the next table - // to finish being allocated by another thread (and if we just finished allocating above, the condition will - // always be true) - if (newCount < (mainHash->capacity >> 1) + (mainHash->capacity >> 2)) { - auto producer = static_cast(recycle_or_create_producer(false)); - if (producer == nullptr) { - implicitProducerHashCount.fetch_sub(1, std::memory_order_relaxed); - return nullptr; - } - -#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED - producer->threadExitListener.callback = &ConcurrentQueue::implicit_producer_thread_exited_callback; - producer->threadExitListener.userData = producer; - details::ThreadExitNotifier::subscribe(&producer->threadExitListener); -#endif - - auto index = hashedId; - while (true) { - index &= mainHash->capacity - 1u; - auto empty = details::invalid_thread_id; -#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED - auto reusable = details::invalid_thread_id2; - if (mainHash->entries[index].key.compare_exchange_strong(reusable, id, std::memory_order_seq_cst, std::memory_order_relaxed)) { - implicitProducerHashCount.fetch_sub(1, std::memory_order_relaxed); // already counted as a used slot - mainHash->entries[index].value = producer; - break; - } -#endif - if (mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_seq_cst, std::memory_order_relaxed)) { - mainHash->entries[index].value = producer; - break; - } - ++index; - } - return producer; - } - - // Hmm, the old hash is quite full and somebody else is busy allocating a new one. - // We need to wait for the allocating thread to finish (if it succeeds, we add, if not, - // we try to allocate ourselves). - mainHash = implicitProducerHash.load(std::memory_order_acquire); - } - } - -#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED - void implicit_producer_thread_exited(ImplicitProducer* producer) - { - // Remove from hash -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH - debug::DebugLock lock(implicitProdMutex); -#endif - auto hash = implicitProducerHash.load(std::memory_order_acquire); - assert(hash != nullptr); // The thread exit listener is only registered if we were added to a hash in the first place - auto id = details::thread_id(); - auto hashedId = details::hash_thread_id(id); - details::thread_id_t probedKey; - - // We need to traverse all the hashes just in case other threads aren't on the current one yet and are - // trying to add an entry thinking there's a free slot (because they reused a producer) - for (; hash != nullptr; hash = hash->prev) { - auto index = hashedId; - do { - index &= hash->capacity - 1u; - probedKey = id; - if (hash->entries[index].key.compare_exchange_strong(probedKey, details::invalid_thread_id2, std::memory_order_seq_cst, std::memory_order_relaxed)) { - break; - } - ++index; - } while (probedKey != details::invalid_thread_id); // Can happen if the hash has changed but we weren't put back in it yet, or if we weren't added to this hash in the first place - } - - // Mark the queue as being recyclable - producer->inactive.store(true, std::memory_order_release); - } - - static void implicit_producer_thread_exited_callback(void* userData) - { - auto producer = static_cast(userData); - auto queue = producer->parent; - queue->implicit_producer_thread_exited(producer); - } -#endif - - ////////////////////////////////// - // Utility functions - ////////////////////////////////// - - template - static inline void* aligned_malloc(size_t size) - { - MOODYCAMEL_CONSTEXPR_IF (std::alignment_of::value <= std::alignment_of::value) - return (Traits::malloc)(size); - else { - size_t alignment = std::alignment_of::value; - void* raw = (Traits::malloc)(size + alignment - 1 + sizeof(void*)); - if (!raw) - return nullptr; - char* ptr = details::align_for(reinterpret_cast(raw) + sizeof(void*)); - *(reinterpret_cast(ptr) - 1) = raw; - return ptr; - } - } - - template - static inline void aligned_free(void* ptr) - { - MOODYCAMEL_CONSTEXPR_IF (std::alignment_of::value <= std::alignment_of::value) - return (Traits::free)(ptr); - else - (Traits::free)(ptr ? *(reinterpret_cast(ptr) - 1) : nullptr); - } - - template - static inline U* create_array(size_t count) - { - assert(count > 0); - U* p = static_cast(aligned_malloc(sizeof(U) * count)); - if (p == nullptr) - return nullptr; - - for (size_t i = 0; i != count; ++i) - new (p + i) U(); - return p; - } - - template - static inline void destroy_array(U* p, size_t count) - { - if (p != nullptr) { - assert(count > 0); - for (size_t i = count; i != 0; ) - (p + --i)->~U(); - } - aligned_free(p); - } - - template - static inline U* create() - { - void* p = aligned_malloc(sizeof(U)); - return p != nullptr ? new (p) U : nullptr; - } - - template - static inline U* create(A1&& a1) - { - void* p = aligned_malloc(sizeof(U)); - return p != nullptr ? new (p) U(std::forward(a1)) : nullptr; - } - - template - static inline void destroy(U* p) - { - if (p != nullptr) - p->~U(); - aligned_free(p); - } - -private: - std::atomic producerListTail; - std::atomic producerCount; - - std::atomic initialBlockPoolIndex; - Block* initialBlockPool; - size_t initialBlockPoolSize; - -#ifndef MCDBGQ_USEDEBUGFREELIST - FreeList freeList; -#else - debug::DebugFreeList freeList; -#endif - - std::atomic implicitProducerHash; - std::atomic implicitProducerHashCount; // Number of slots logically used - ImplicitProducerHash initialImplicitProducerHash; - std::array initialImplicitProducerHashEntries; - std::atomic_flag implicitProducerHashResizeInProgress; - - std::atomic nextExplicitConsumerId; - std::atomic globalExplicitConsumerOffset; - -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH - debug::DebugMutex implicitProdMutex; -#endif - -#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG - std::atomic explicitProducers; - std::atomic implicitProducers; -#endif -}; - - -template -ProducerToken::ProducerToken(ConcurrentQueue& queue) - : producer(queue.recycle_or_create_producer(true)) -{ - if (producer != nullptr) { - producer->token = this; - } -} - -template -ProducerToken::ProducerToken(BlockingConcurrentQueue& queue) - : producer(reinterpret_cast*>(&queue)->recycle_or_create_producer(true)) -{ - if (producer != nullptr) { - producer->token = this; - } -} - -template -ConsumerToken::ConsumerToken(ConcurrentQueue& queue) - : itemsConsumedFromCurrent(0), currentProducer(nullptr), desiredProducer(nullptr) -{ - initialOffset = queue.nextExplicitConsumerId.fetch_add(1, std::memory_order_release); - lastKnownGlobalOffset = static_cast(-1); -} - -template -ConsumerToken::ConsumerToken(BlockingConcurrentQueue& queue) - : itemsConsumedFromCurrent(0), currentProducer(nullptr), desiredProducer(nullptr) -{ - initialOffset = reinterpret_cast*>(&queue)->nextExplicitConsumerId.fetch_add(1, std::memory_order_release); - lastKnownGlobalOffset = static_cast(-1); -} - -template -inline void swap(ConcurrentQueue& a, ConcurrentQueue& b) MOODYCAMEL_NOEXCEPT -{ - a.swap(b); -} - -inline void swap(ProducerToken& a, ProducerToken& b) MOODYCAMEL_NOEXCEPT -{ - a.swap(b); -} - -inline void swap(ConsumerToken& a, ConsumerToken& b) MOODYCAMEL_NOEXCEPT -{ - a.swap(b); -} - -template -inline void swap(typename ConcurrentQueue::ImplicitProducerKVP& a, typename ConcurrentQueue::ImplicitProducerKVP& b) MOODYCAMEL_NOEXCEPT -{ - a.swap(b); -} - -} - -#if defined(_MSC_VER) && (!defined(_HAS_CXX17) || !_HAS_CXX17) -#pragma warning(pop) -#endif - -#if defined(__GNUC__) && !defined(__INTEL_COMPILER) -#pragma GCC diagnostic pop -#endif diff --git a/lib/concurrentqueue/lightweightsemaphore.h b/lib/concurrentqueue/lightweightsemaphore.h deleted file mode 100644 index 41ba094..0000000 --- a/lib/concurrentqueue/lightweightsemaphore.h +++ /dev/null @@ -1,425 +0,0 @@ -// Provides an efficient implementation of a semaphore (LightweightSemaphore). -// This is an extension of Jeff Preshing's sempahore implementation (licensed -// under the terms of its separate zlib license) that has been adapted and -// extended by Cameron Desrochers. - -#pragma once - -#include // For std::size_t -#include -#include // For std::make_signed - -#if defined(_WIN32) -// Avoid including windows.h in a header; we only need a handful of -// items, so we'll redeclare them here (this is relatively safe since -// the API generally has to remain stable between Windows versions). -// I know this is an ugly hack but it still beats polluting the global -// namespace with thousands of generic names or adding a .cpp for nothing. -extern "C" { - struct _SECURITY_ATTRIBUTES; - __declspec(dllimport) void* __stdcall CreateSemaphoreW(_SECURITY_ATTRIBUTES* lpSemaphoreAttributes, long lInitialCount, long lMaximumCount, const wchar_t* lpName); - __declspec(dllimport) int __stdcall CloseHandle(void* hObject); - __declspec(dllimport) unsigned long __stdcall WaitForSingleObject(void* hHandle, unsigned long dwMilliseconds); - __declspec(dllimport) int __stdcall ReleaseSemaphore(void* hSemaphore, long lReleaseCount, long* lpPreviousCount); -} -#elif defined(__MACH__) -#include -#elif defined(__unix__) -#include - -#if defined(__GLIBC_PREREQ) && defined(_GNU_SOURCE) -#if __GLIBC_PREREQ(2,30) -#define MOODYCAMEL_LIGHTWEIGHTSEMAPHORE_MONOTONIC -#endif -#endif -#endif - -namespace moodycamel -{ -namespace details -{ - -// Code in the mpmc_sema namespace below is an adaptation of Jeff Preshing's -// portable + lightweight semaphore implementations, originally from -// https://github.com/preshing/cpp11-on-multicore/blob/master/common/sema.h -// LICENSE: -// Copyright (c) 2015 Jeff Preshing -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgement in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -#if defined(_WIN32) -class Semaphore -{ -private: - void* m_hSema; - - Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; - Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; - -public: - Semaphore(int initialCount = 0) - { - assert(initialCount >= 0); - const long maxLong = 0x7fffffff; - m_hSema = CreateSemaphoreW(nullptr, initialCount, maxLong, nullptr); - assert(m_hSema); - } - - ~Semaphore() - { - CloseHandle(m_hSema); - } - - bool wait() - { - const unsigned long infinite = 0xffffffff; - return WaitForSingleObject(m_hSema, infinite) == 0; - } - - bool try_wait() - { - return WaitForSingleObject(m_hSema, 0) == 0; - } - - bool timed_wait(std::uint64_t usecs) - { - return WaitForSingleObject(m_hSema, (unsigned long)(usecs / 1000)) == 0; - } - - void signal(int count = 1) - { - while (!ReleaseSemaphore(m_hSema, count, nullptr)); - } -}; -#elif defined(__MACH__) -//--------------------------------------------------------- -// Semaphore (Apple iOS and OSX) -// Can't use POSIX semaphores due to http://lists.apple.com/archives/darwin-kernel/2009/Apr/msg00010.html -//--------------------------------------------------------- -class Semaphore -{ -private: - semaphore_t m_sema; - - Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; - Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; - -public: - Semaphore(int initialCount = 0) - { - assert(initialCount >= 0); - kern_return_t rc = semaphore_create(mach_task_self(), &m_sema, SYNC_POLICY_FIFO, initialCount); - assert(rc == KERN_SUCCESS); - (void)rc; - } - - ~Semaphore() - { - semaphore_destroy(mach_task_self(), m_sema); - } - - bool wait() - { - return semaphore_wait(m_sema) == KERN_SUCCESS; - } - - bool try_wait() - { - return timed_wait(0); - } - - bool timed_wait(std::uint64_t timeout_usecs) - { - mach_timespec_t ts; - ts.tv_sec = static_cast(timeout_usecs / 1000000); - ts.tv_nsec = static_cast((timeout_usecs % 1000000) * 1000); - - // added in OSX 10.10: https://developer.apple.com/library/prerelease/mac/documentation/General/Reference/APIDiffsMacOSX10_10SeedDiff/modules/Darwin.html - kern_return_t rc = semaphore_timedwait(m_sema, ts); - return rc == KERN_SUCCESS; - } - - void signal() - { - while (semaphore_signal(m_sema) != KERN_SUCCESS); - } - - void signal(int count) - { - while (count-- > 0) - { - while (semaphore_signal(m_sema) != KERN_SUCCESS); - } - } -}; -#elif defined(__unix__) -//--------------------------------------------------------- -// Semaphore (POSIX, Linux) -//--------------------------------------------------------- -class Semaphore -{ -private: - sem_t m_sema; - - Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; - Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; - -public: - Semaphore(int initialCount = 0) - { - assert(initialCount >= 0); - int rc = sem_init(&m_sema, 0, static_cast(initialCount)); - assert(rc == 0); - (void)rc; - } - - ~Semaphore() - { - sem_destroy(&m_sema); - } - - bool wait() - { - // http://stackoverflow.com/questions/2013181/gdb-causes-sem-wait-to-fail-with-eintr-error - int rc; - do { - rc = sem_wait(&m_sema); - } while (rc == -1 && errno == EINTR); - return rc == 0; - } - - bool try_wait() - { - int rc; - do { - rc = sem_trywait(&m_sema); - } while (rc == -1 && errno == EINTR); - return rc == 0; - } - - bool timed_wait(std::uint64_t usecs) - { - struct timespec ts; - const int usecs_in_1_sec = 1000000; - const int nsecs_in_1_sec = 1000000000; -#ifdef MOODYCAMEL_LIGHTWEIGHTSEMAPHORE_MONOTONIC - clock_gettime(CLOCK_MONOTONIC, &ts); -#else - clock_gettime(CLOCK_REALTIME, &ts); -#endif - ts.tv_sec += (time_t)(usecs / usecs_in_1_sec); - ts.tv_nsec += (long)(usecs % usecs_in_1_sec) * 1000; - // sem_timedwait bombs if you have more than 1e9 in tv_nsec - // so we have to clean things up before passing it in - if (ts.tv_nsec >= nsecs_in_1_sec) { - ts.tv_nsec -= nsecs_in_1_sec; - ++ts.tv_sec; - } - - int rc; - do { -#ifdef MOODYCAMEL_LIGHTWEIGHTSEMAPHORE_MONOTONIC - rc = sem_clockwait(&m_sema, CLOCK_MONOTONIC, &ts); -#else - rc = sem_timedwait(&m_sema, &ts); -#endif - } while (rc == -1 && errno == EINTR); - return rc == 0; - } - - void signal() - { - while (sem_post(&m_sema) == -1); - } - - void signal(int count) - { - while (count-- > 0) - { - while (sem_post(&m_sema) == -1); - } - } -}; -#else -#error Unsupported platform! (No semaphore wrapper available) -#endif - -} // end namespace details - - -//--------------------------------------------------------- -// LightweightSemaphore -//--------------------------------------------------------- -class LightweightSemaphore -{ -public: - typedef std::make_signed::type ssize_t; - -private: - std::atomic m_count; - details::Semaphore m_sema; - int m_maxSpins; - - bool waitWithPartialSpinning(std::int64_t timeout_usecs = -1) - { - ssize_t oldCount; - int spin = m_maxSpins; - while (--spin >= 0) - { - oldCount = m_count.load(std::memory_order_relaxed); - if ((oldCount > 0) && m_count.compare_exchange_strong(oldCount, oldCount - 1, std::memory_order_acquire, std::memory_order_relaxed)) - return true; - std::atomic_signal_fence(std::memory_order_acquire); // Prevent the compiler from collapsing the loop. - } - oldCount = m_count.fetch_sub(1, std::memory_order_acquire); - if (oldCount > 0) - return true; - if (timeout_usecs < 0) - { - if (m_sema.wait()) - return true; - } - if (timeout_usecs > 0 && m_sema.timed_wait((std::uint64_t)timeout_usecs)) - return true; - // At this point, we've timed out waiting for the semaphore, but the - // count is still decremented indicating we may still be waiting on - // it. So we have to re-adjust the count, but only if the semaphore - // wasn't signaled enough times for us too since then. If it was, we - // need to release the semaphore too. - while (true) - { - oldCount = m_count.load(std::memory_order_acquire); - if (oldCount >= 0 && m_sema.try_wait()) - return true; - if (oldCount < 0 && m_count.compare_exchange_strong(oldCount, oldCount + 1, std::memory_order_relaxed, std::memory_order_relaxed)) - return false; - } - } - - ssize_t waitManyWithPartialSpinning(ssize_t max, std::int64_t timeout_usecs = -1) - { - assert(max > 0); - ssize_t oldCount; - int spin = m_maxSpins; - while (--spin >= 0) - { - oldCount = m_count.load(std::memory_order_relaxed); - if (oldCount > 0) - { - ssize_t newCount = oldCount > max ? oldCount - max : 0; - if (m_count.compare_exchange_strong(oldCount, newCount, std::memory_order_acquire, std::memory_order_relaxed)) - return oldCount - newCount; - } - std::atomic_signal_fence(std::memory_order_acquire); - } - oldCount = m_count.fetch_sub(1, std::memory_order_acquire); - if (oldCount <= 0) - { - if ((timeout_usecs == 0) || (timeout_usecs < 0 && !m_sema.wait()) || (timeout_usecs > 0 && !m_sema.timed_wait((std::uint64_t)timeout_usecs))) - { - while (true) - { - oldCount = m_count.load(std::memory_order_acquire); - if (oldCount >= 0 && m_sema.try_wait()) - break; - if (oldCount < 0 && m_count.compare_exchange_strong(oldCount, oldCount + 1, std::memory_order_relaxed, std::memory_order_relaxed)) - return 0; - } - } - } - if (max > 1) - return 1 + tryWaitMany(max - 1); - return 1; - } - -public: - LightweightSemaphore(ssize_t initialCount = 0, int maxSpins = 10000) : m_count(initialCount), m_maxSpins(maxSpins) - { - assert(initialCount >= 0); - assert(maxSpins >= 0); - } - - bool tryWait() - { - ssize_t oldCount = m_count.load(std::memory_order_relaxed); - while (oldCount > 0) - { - if (m_count.compare_exchange_weak(oldCount, oldCount - 1, std::memory_order_acquire, std::memory_order_relaxed)) - return true; - } - return false; - } - - bool wait() - { - return tryWait() || waitWithPartialSpinning(); - } - - bool wait(std::int64_t timeout_usecs) - { - return tryWait() || waitWithPartialSpinning(timeout_usecs); - } - - // Acquires between 0 and (greedily) max, inclusive - ssize_t tryWaitMany(ssize_t max) - { - assert(max >= 0); - ssize_t oldCount = m_count.load(std::memory_order_relaxed); - while (oldCount > 0) - { - ssize_t newCount = oldCount > max ? oldCount - max : 0; - if (m_count.compare_exchange_weak(oldCount, newCount, std::memory_order_acquire, std::memory_order_relaxed)) - return oldCount - newCount; - } - return 0; - } - - // Acquires at least one, and (greedily) at most max - ssize_t waitMany(ssize_t max, std::int64_t timeout_usecs) - { - assert(max >= 0); - ssize_t result = tryWaitMany(max); - if (result == 0 && max > 0) - result = waitManyWithPartialSpinning(max, timeout_usecs); - return result; - } - - ssize_t waitMany(ssize_t max) - { - ssize_t result = waitMany(max, -1); - assert(result > 0); - return result; - } - - void signal(ssize_t count = 1) - { - assert(count >= 0); - ssize_t oldCount = m_count.fetch_add(count, std::memory_order_release); - ssize_t toRelease = -oldCount < count ? -oldCount : count; - if (toRelease > 0) - { - m_sema.signal((int)toRelease); - } - } - - std::size_t availableApprox() const - { - ssize_t count = m_count.load(std::memory_order_relaxed); - return count > 0 ? static_cast(count) : 0; - } -}; - -} // end namespace moodycamel diff --git a/lib/freetype-windows-binaries b/lib/freetype-windows-binaries deleted file mode 160000 index d6fb49d..0000000 --- a/lib/freetype-windows-binaries +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d6fb49d11a9d0011bf4ecfe7e570beaaa189838a diff --git a/lib/lunasvg b/lib/lunasvg deleted file mode 160000 index 4166d0c..0000000 --- a/lib/lunasvg +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4166d0cccfc059b39d5ecfc372524375e59448f9 diff --git a/lib/rt64 b/lib/rt64 index 23cab60..5473732 160000 --- a/lib/rt64 +++ b/lib/rt64 @@ -1 +1 @@ -Subproject commit 23cab603c4f9f4a8b369b38e036f1aa484603878 +Subproject commit 5473732a822a4423b5696e7cb18fecc425a59875 diff --git a/patches/camera_transform_tagging.c b/patches/camera_transform_tagging.c index 60f0d00..d8011f5 100644 --- a/patches/camera_transform_tagging.c +++ b/patches/camera_transform_tagging.c @@ -246,16 +246,20 @@ RECOMP_PATCH void View_Apply(View* view, s32 mask) { if (interpolate_camera) { // Simple interpolation works much better for cameras because they orbit around a focus. gEXMatrixGroupSimple(POLY_OPA_DISP++, CAMERA_TRANSFORM_ID, G_EX_NOPUSH, G_MTX_PROJECTION, - G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE); + G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE, + G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE); gEXMatrixGroupSimple(POLY_XLU_DISP++, CAMERA_TRANSFORM_ID, G_EX_NOPUSH, G_MTX_PROJECTION, - G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE); + G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE, + G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE); } else { // Skip interpolation for this frame. gEXMatrixGroupSimple(POLY_OPA_DISP++, CAMERA_TRANSFORM_ID, G_EX_NOPUSH, G_MTX_PROJECTION, - G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE); + G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE, + G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP); gEXMatrixGroupSimple(POLY_XLU_DISP++, CAMERA_TRANSFORM_ID, G_EX_NOPUSH, G_MTX_PROJECTION, - G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE); + G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE, + G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP); } // Record whether the camera was skipped for later use. diff --git a/patches/effect_patches.c b/patches/effect_patches.c index f267ff8..da0162f 100644 --- a/patches/effect_patches.c +++ b/patches/effect_patches.c @@ -179,9 +179,11 @@ RECOMP_PATCH void TransitionCircle_LoadAndSetTexture(Gfx** gfxp, TexturePtr text gEXPushProjectionMatrix(gfx++); gEXPushGeometryMode(gfx++); gEXMatrixGroupSimple(gfx++, CIRCLE_OVERLAY_TRANSFORM_PROJECTION_ID, G_EX_PUSH, G_MTX_PROJECTION, - G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE); + G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE, + G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE); gEXMatrixGroupSimple(gfx++, CIRCLE_OVERLAY_TRANSFORM_ID, G_EX_PUSH, G_MTX_MODELVIEW, - G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE); + G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE, + G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE); // @recomp Allocate a matrix and vertices in the displaylist because there's no handle to the GfxContext here. Mtx* ortho_matrix = (Mtx*)(gfx + 1); diff --git a/patches/effect_transform_tagging.c b/patches/effect_transform_tagging.c index 6ce4b7b..e039bb2 100644 --- a/patches/effect_transform_tagging.c +++ b/patches/effect_transform_tagging.c @@ -312,11 +312,13 @@ static inline void tag_skip_effect(GraphicsContext* gfxCtx, u32 id) { gEXMatrixGroupSimple(POLY_OPA_DISP++, id, G_EX_PUSH, G_MTX_MODELVIEW, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, - G_EX_COMPONENT_SKIP, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE); + G_EX_COMPONENT_SKIP, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE, + G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP); gEXMatrixGroupSimple(POLY_XLU_DISP++, id + EFFECT_TRANSFORM_ID_COUNT, G_EX_PUSH, G_MTX_MODELVIEW, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, - G_EX_COMPONENT_SKIP, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE); + G_EX_COMPONENT_SKIP, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE, + G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP); CLOSE_DISPS(); } diff --git a/patches/item_transform_tagging.c b/patches/item_transform_tagging.c index 3f63edb..fd2c9f1 100644 --- a/patches/item_transform_tagging.c +++ b/patches/item_transform_tagging.c @@ -47,7 +47,8 @@ RECOMP_PATCH void ArmsHook_Draw(Actor* thisx, PlayState* play) { // @recomp Tag the matrices for the hookshot tip and chain. u32 cur_transform_id = actor_transform_id(thisx); gEXMatrixGroupSimple(POLY_OPA_DISP++, cur_transform_id, G_EX_PUSH, G_MTX_MODELVIEW, - G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE); + G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE, + G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE); gSPMatrix(POLY_OPA_DISP++, Matrix_NewMtx(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); gSPDisplayList(POLY_OPA_DISP++, object_link_child_DL_01D960); @@ -106,7 +107,8 @@ RECOMP_PATCH void Player_DrawHookshotReticle(PlayState* play, Player* player, f3 // @recomp Tag the reticle's transform. gEXMatrixGroupSimple(OVERLAY_DISP++, HOOKSHOT_RETICLE_TRANSFORM_ID, G_EX_PUSH, G_MTX_MODELVIEW, - G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE); + G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE, + G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE); gSPMatrix(OVERLAY_DISP++, Matrix_NewMtx(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); @@ -172,7 +174,8 @@ RECOMP_PATCH void Player_DrawGameplay(PlayState* play, Player* this, s32 lod, Gf gSPBranchList(enddl_command, bowstring_end_hook_dl); // Write the transform tag command. Use simple interpolation to avoid issues from decomposition failure due to a scale of zero. gEXMatrixGroupSimple(&bowstring_start_hook_dl[1], BOWSTRING_TRANSFORM_ID, G_EX_PUSH, G_MTX_MODELVIEW, - G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE); + G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE, + G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE); // Write the pop group command. gEXPopMatrixGroup(&bowstring_end_hook_dl[0], G_MTX_MODELVIEW); } diff --git a/patches/patches.h b/patches/patches.h index ec31b88..07f1495 100644 --- a/patches/patches.h +++ b/patches/patches.h @@ -45,28 +45,28 @@ #endif #define gEXMatrixGroupNoInterpolation(cmd, push, proj, edit) \ - gEXMatrixGroup(cmd, G_EX_ID_IGNORE, G_EX_INTERPOLATE_SIMPLE, push, proj, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_ORDER_LINEAR, edit) + gEXMatrixGroup(cmd, G_EX_ID_IGNORE, G_EX_INTERPOLATE_SIMPLE, push, proj, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_ORDER_LINEAR, edit, G_EX_ASPECT_AUTO, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP) #define gEXMatrixGroupInterpolateOnlyTiles(cmd, push, proj, edit) \ - gEXMatrixGroup(cmd, G_EX_ID_IGNORE, G_EX_INTERPOLATE_SIMPLE, push, proj, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, edit) + gEXMatrixGroup(cmd, G_EX_ID_IGNORE, G_EX_INTERPOLATE_SIMPLE, push, proj, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, edit, G_EX_ASPECT_AUTO, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP) #define gEXMatrixGroupDecomposedNormal(cmd, id, push, proj, edit) \ - gEXMatrixGroupDecomposed(cmd, id, push, proj, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, edit) + gEXMatrixGroupDecomposed(cmd, id, push, proj, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, edit, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_AUTO) #define gEXMatrixGroupDecomposedSkipRot(cmd, id, push, proj, edit) \ - gEXMatrixGroupDecomposed(cmd, id, push, proj, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, edit) + gEXMatrixGroupDecomposed(cmd, id, push, proj, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, edit, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_AUTO) #define gEXMatrixGroupDecomposedSkipPosRot(cmd, id, push, proj, edit) \ - gEXMatrixGroupDecomposed(cmd, id, push, proj, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, edit) + gEXMatrixGroupDecomposed(cmd, id, push, proj, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, edit, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_AUTO) #define gEXMatrixGroupDecomposedSkipAll(cmd, id, push, proj, edit) \ - gEXMatrixGroupDecomposed(cmd, id, push, proj, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, edit) + gEXMatrixGroupDecomposed(cmd, id, push, proj, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, edit, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP) #define gEXMatrixGroupDecomposedVerts(cmd, id, push, proj, edit) \ - gEXMatrixGroupDecomposed(cmd, id, push, proj, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, edit) + gEXMatrixGroupDecomposed(cmd, id, push, proj, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, edit, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_AUTO) #define gEXMatrixGroupDecomposedVertsOrderAuto(cmd, id, push, proj, edit) \ - gEXMatrixGroupDecomposed(cmd, id, push, proj, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_AUTO, edit) + gEXMatrixGroupDecomposed(cmd, id, push, proj, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_AUTO, edit, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_AUTO) int recomp_printf(const char* fmt, ...); diff --git a/patches/recompui_event_structs.h b/patches/recompui_event_structs.h index 914160d..e2f91bc 100644 --- a/patches/recompui_event_structs.h +++ b/patches/recompui_event_structs.h @@ -1,7 +1,7 @@ #ifndef __UI_FUNCS_H__ #define __UI_FUNCS_H__ -// These two enums must be kept in sync with src/ui/elements/ui_types.h! +// These three enums must be kept in sync with src/ui/elements/ui_types.h! typedef enum { UI_EVENT_NONE, UI_EVENT_CLICK, @@ -11,6 +11,9 @@ typedef enum { UI_EVENT_DRAG, UI_EVENT_RESERVED1, // Would be UI_EVENT_TEXT but text events aren't usable in mods currently UI_EVENT_UPDATE, + UI_EVENT_NAVIGATE, + UI_EVENT_MOUSE_BUTTON, + UI_EVENT_MENU_ACTION, UI_EVENT_COUNT } RecompuiEventType; @@ -21,6 +24,16 @@ typedef enum { UI_DRAG_END } RecompuiDragPhase; +typedef enum { + UI_MENU_ACTION_NONE, + UI_MENU_ACTION_ACCEPT, + UI_MENU_ACTION_APPLY, + UI_MENU_ACTION_BACK, + UI_MENU_ACTION_TOGGLE, + UI_MENU_ACTION_TAB_LEFT, + UI_MENU_ACTION_TAB_RIGHT +} RecompuiMenuAction; + typedef struct { RecompuiEventType type; union { @@ -30,15 +43,15 @@ typedef struct { } click; struct { - bool active; + int active; } focus; struct { - bool active; + int active; } hover; struct { - bool active; + int active; } enable; struct { @@ -46,6 +59,10 @@ typedef struct { float y; RecompuiDragPhase phase; } drag; + + struct { + RecompuiMenuAction action; + } menu_action; } data; } RecompuiEventData; diff --git a/patches/sky_transform_tagging.c b/patches/sky_transform_tagging.c index 5ae3c40..131ee27 100644 --- a/patches/sky_transform_tagging.c +++ b/patches/sky_transform_tagging.c @@ -275,11 +275,13 @@ RECOMP_PATCH void Environment_DrawSkyboxStarsImpl(PlayState* play, Gfx** gfxP) { // @recomp Tag the star. if (camera_was_skipped()) { gEXMatrixGroupSimple(gfx++, STAR_TRANSFORM_ID_START + i, G_EX_PUSH, G_MTX_MODELVIEW, - G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE); + G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE, + G_EX_COMPONENT_SKIP, G_EX_COMPONENT_SKIP); } else { gEXMatrixGroupSimple(gfx++, STAR_TRANSFORM_ID_START + i, G_EX_PUSH, G_MTX_MODELVIEW, - G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE); + G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE, + G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE); } // @recomp Draw the star in 3D with a billboarded matrix. diff --git a/patches/specific_actor_transform_tagging.c b/patches/specific_actor_transform_tagging.c index dc5dd3b..4fc36f8 100644 --- a/patches/specific_actor_transform_tagging.c +++ b/patches/specific_actor_transform_tagging.c @@ -86,7 +86,8 @@ RECOMP_PATCH void EnTanron2_Draw(Actor* thisx, PlayState* play2) { gEXMatrixGroupSimple(POLY_XLU_DISP++, actor_transform_id(&D_80BB8458[i]->actor) + 0, G_EX_PUSH, G_MTX_MODELVIEW, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, - G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE); + G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE, + G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE); gSPMatrix(POLY_XLU_DISP++, Matrix_NewMtx(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); gSPDisplayList(POLY_XLU_DISP++, gWartBubbleModelDL); @@ -111,7 +112,8 @@ RECOMP_PATCH void EnTanron2_Draw(Actor* thisx, PlayState* play2) { gEXMatrixGroupSimple(POLY_XLU_DISP++, actor_transform_id(tanron2) + 1, G_EX_PUSH, G_MTX_MODELVIEW, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, - G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE); + G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE, + G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE); gSPMatrix(POLY_XLU_DISP++, Matrix_NewMtx(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); gSPDisplayList(POLY_XLU_DISP++, gWartShadowModelDL); @@ -141,7 +143,8 @@ RECOMP_PATCH void EnTanron2_Draw(Actor* thisx, PlayState* play2) { gEXMatrixGroupSimple(POLY_XLU_DISP++, actor_transform_id(tanron2) + 2, G_EX_PUSH, G_MTX_MODELVIEW, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, - G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE); + G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE, + G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE); gSPMatrix(POLY_XLU_DISP++, Matrix_NewMtx(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); gSPDisplayList(POLY_XLU_DISP++, gEffWaterRippleDL); diff --git a/patches/ui_patches.c b/patches/ui_patches.c index 7bf8955..bfbef2c 100644 --- a/patches/ui_patches.c +++ b/patches/ui_patches.c @@ -932,9 +932,11 @@ RECOMP_PATCH void Message_DrawTextBox(PlayState* play, Gfx** gfxP) { gEXPushOtherMode(gfx++); gEXPushViewport(gfx++); gEXMatrixGroupSimple(gfx++, TEXTBOX_TRANSFORM_PROJECTION_ID, G_EX_PUSH, G_MTX_PROJECTION, - G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE); + G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE, + G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE); gEXMatrixGroupSimple(gfx++, TEXTBOX_TRANSFORM_ID, G_EX_PUSH, G_MTX_MODELVIEW, - G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE); + G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE, + G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE); // @recomp Set up the RSP params. gSPLoadGeometryMode(gfx++, 0); @@ -1120,9 +1122,11 @@ RECOMP_PATCH void ShrinkWindow_Draw(GraphicsContext* gfxCtx) { // @recomp Set up the letterbox matrix groups. gEXMatrixGroupSimple(gfx++, TEXTBOX_TRANSFORM_PROJECTION_ID, G_EX_PUSH, G_MTX_PROJECTION, - G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE); + G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE, + G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE); gEXMatrixGroupSimple(gfx++, TEXTBOX_TRANSFORM_ID, G_EX_PUSH, G_MTX_MODELVIEW, - G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE); + G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE, G_EX_ORDER_LINEAR, G_EX_EDIT_NONE, + G_EX_COMPONENT_INTERPOLATE, G_EX_COMPONENT_INTERPOLATE); // @recomp Allocate and build the matrices. Mtx* ortho_matrix = GRAPH_ALLOC(gfxCtx, sizeof(Mtx)); diff --git a/shadercache/.gitkeep b/shadercache/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/shaders/InterfacePS.hlsl b/shaders/InterfacePS.hlsl deleted file mode 100644 index b8ab340..0000000 --- a/shaders/InterfacePS.hlsl +++ /dev/null @@ -1,11 +0,0 @@ -SamplerState gSampler : register(s1, space0); -Texture2D gTexture : register(t2, space1); - -void PSMain( - in float4 iColor : COLOR, - in float2 iUV : TEXCOORD, - out float4 oColor : SV_TARGET -) -{ - oColor = gTexture.SampleLevel(gSampler, iUV, 0) * iColor; -} diff --git a/shaders/InterfaceVS.hlsl b/shaders/InterfaceVS.hlsl deleted file mode 100644 index 05fd649..0000000 --- a/shaders/InterfaceVS.hlsl +++ /dev/null @@ -1,23 +0,0 @@ -struct Input { - float4x4 transform; - float2 translation; -}; - -[[vk::push_constant]] -ConstantBuffer gInput : register(b0, space0); - -void VSMain( - in float2 iPosition : POSITION, - in float4 iColor : COLOR, - in float2 iUV : TEXCOORD, - out float4 oColor : COLOR, - out float2 oUV : TEXCOORD, - out float4 oPosition : SV_Position -) -{ - float2 translatedPos = iPosition + gInput.translation; - oPosition = mul(gInput.transform, float4(translatedPos, 0, 1)); - - oColor = iColor; - oUV = iUV; -} diff --git a/src/game/config.cpp b/src/game/config.cpp index 17ff9e4..a96c4a0 100644 --- a/src/game/config.cpp +++ b/src/game/config.cpp @@ -1,10 +1,14 @@ #include "zelda_config.h" -#include "recomp_input.h" +#include "zelda_debug.h" +#include "recompui/recompui.h" +#include "recompui/config.h" +#include "recompinput/recompinput.h" #include "zelda_sound.h" -#include "zelda_render.h" #include "zelda_support.h" #include "ultramodern/config.hpp" #include "librecomp/files.hpp" +#include "librecomp/config.hpp" +#include "util/file.h" #include #include #include @@ -18,515 +22,256 @@ #include "apple/rt64_apple.h" #endif -constexpr std::u8string_view general_filename = u8"general.json"; -constexpr std::u8string_view graphics_filename = u8"graphics.json"; -constexpr std::u8string_view controls_filename = u8"controls.json"; -constexpr std::u8string_view sound_filename = u8"sound.json"; - -constexpr auto res_default = ultramodern::renderer::Resolution::Auto; -constexpr auto hr_default = ultramodern::renderer::HUDRatioMode::Clamp16x9; -constexpr auto api_default = ultramodern::renderer::GraphicsApi::Auto; -constexpr auto ar_default = ultramodern::renderer::AspectRatio::Expand; -constexpr auto msaa_default = ultramodern::renderer::Antialiasing::MSAA2X; -constexpr auto rr_default = ultramodern::renderer::RefreshRate::Display; -constexpr auto hpfb_default = ultramodern::renderer::HighPrecisionFramebuffer::Auto; -constexpr int ds_default = 1; -constexpr int rr_manual_default = 60; -constexpr bool developer_mode_default = false; - -static bool is_steam_deck = false; - -ultramodern::renderer::WindowMode wm_default() { - return is_steam_deck ? ultramodern::renderer::WindowMode::Fullscreen : ultramodern::renderer::WindowMode::Windowed; +bool get_general_config_bool_value(const std::string& option_id) { + return std::get(recompui::config::get_general_config().get_option_value(option_id)); } -#ifdef __gnu_linux__ -void detect_steam_deck() { - // Check if the board vendor is Valve. - std::ifstream board_vendor_file("/sys/devices/virtual/dmi/id/board_vendor"); - std::string line; - if (std::getline(board_vendor_file, line).good() && line == "Valve") { - is_steam_deck = true; - return; - } - - // Check if the SteamDeck variable is set to 1. - const char* steam_deck_env = getenv("SteamDeck"); - if (steam_deck_env != nullptr && std::string{steam_deck_env} == "1") { - is_steam_deck = true; - return; - } - - is_steam_deck = false; - return; -} -#else -void detect_steam_deck() { is_steam_deck = false; } -#endif - -template -T from_or_default(const json& j, const std::string& key, T default_value) { - T ret; - auto find_it = j.find(key); - if (find_it != j.end()) { - find_it->get_to(ret); - } - else { - ret = default_value; - } - - return ret; +template +T get_general_config_enum_value(const std::string& option_id) { + return static_cast(std::get(recompui::config::get_general_config().get_option_value(option_id))); } -template -void call_if_key_exists(void (*func)(T), const json& j, const std::string& key) { - auto find_it = j.find(key); - if (find_it != j.end()) { - T val; - find_it->get_to(val); - func(val); - } +template +T get_general_config_number_value(const std::string& option_id) { + return static_cast(std::get(recompui::config::get_general_config().get_option_value(option_id))); } -namespace ultramodern { - void to_json(json& j, const renderer::GraphicsConfig& config) { - j = json{ - {"res_option", config.res_option}, - {"wm_option", config.wm_option}, - {"hr_option", config.hr_option}, - {"api_option", config.api_option}, - {"ds_option", config.ds_option}, - {"ar_option", config.ar_option}, - {"msaa_option", config.msaa_option}, - {"rr_option", config.rr_option}, - {"hpfb_option", config.hpfb_option}, - {"rr_manual_value", config.rr_manual_value}, - {"developer_mode", config.developer_mode}, - }; - } - - void from_json(const json& j, renderer::GraphicsConfig& config) { - config.res_option = from_or_default(j, "res_option", res_default); - config.wm_option = from_or_default(j, "wm_option", wm_default()); - config.hr_option = from_or_default(j, "hr_option", hr_default); - config.api_option = from_or_default(j, "api_option", api_default); - config.ds_option = from_or_default(j, "ds_option", ds_default); - config.ar_option = from_or_default(j, "ar_option", ar_default); - config.msaa_option = from_or_default(j, "msaa_option", msaa_default); - config.rr_option = from_or_default(j, "rr_option", rr_default); - config.hpfb_option = from_or_default(j, "hpfb_option", hpfb_default); - config.rr_manual_value = from_or_default(j, "rr_manual_value", rr_manual_default); - config.developer_mode = from_or_default(j, "developer_mode", developer_mode_default); - } +template +T get_graphics_config_enum_value(const std::string& option_id) { + return static_cast(std::get(recompui::config::get_graphics_config().get_option_value(option_id))); } -namespace recomp { - void to_json(json& j, const InputField& field) { - j = json{ {"input_type", field.input_type}, {"input_id", field.input_id} }; - } - - void from_json(const json& j, InputField& field) { - j.at("input_type").get_to(field.input_type); - j.at("input_id").get_to(field.input_id); - } +template +T get_graphics_config_number_value(const std::string& option_id) { + return static_cast(std::get(recompui::config::get_graphics_config().get_option_value(option_id))); } -std::filesystem::path zelda64::get_app_folder_path() { - // directly check for portable.txt (windows and native linux binary) - if (std::filesystem::exists("portable.txt")) { - return std::filesystem::current_path(); - } - -#if defined(__APPLE__) - // Check for portable file in the directory containing the app bundle. - const auto app_bundle_path = zelda64::get_bundle_directory().parent_path(); - if (std::filesystem::exists(app_bundle_path / "portable.txt")) { - return app_bundle_path; - } -#endif - - std::filesystem::path recomp_dir{}; - -#if defined(_WIN32) - // Deduce local app data path. - PWSTR known_path = NULL; - HRESULT result = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &known_path); - if (result == S_OK) { - recomp_dir = std::filesystem::path{known_path} / zelda64::program_id; - } - - CoTaskMemFree(known_path); -#elif defined(__linux__) || defined(__APPLE__) - // check for APP_FOLDER_PATH env var - if (getenv("APP_FOLDER_PATH") != nullptr) { - return std::filesystem::path{getenv("APP_FOLDER_PATH")}; - } - -#if defined(__APPLE__) - const auto supportdir = zelda64::get_application_support_directory(); - if (supportdir) { - return *supportdir / zelda64::program_id; - } -#endif - - const char *homedir; - - if ((homedir = getenv("HOME")) == nullptr) { - #if defined(__linux__) - homedir = getpwuid(getuid())->pw_dir; - #elif defined(__APPLE__) - homedir = GetHomeDirectory(); - #endif - } - - if (homedir != nullptr) { - recomp_dir = std::filesystem::path{homedir} / (std::u8string{u8".config/"} + std::u8string{zelda64::program_id}); - } -#endif - - return recomp_dir; +template +T get_sound_config_enum_value(const std::string& option_id) { + return static_cast(std::get(recompui::config::get_sound_config().get_option_value(option_id))); } -bool read_json(std::ifstream input_file, nlohmann::json& json_out) { - if (!input_file.good()) { - return false; - } - - try { - input_file >> json_out; - } - catch (nlohmann::json::parse_error&) { - return false; - } - return true; +template +T get_sound_config_number_value(const std::string& option_id) { + return static_cast(std::get(recompui::config::get_sound_config().get_option_value(option_id))); } -bool read_json_with_backups(const std::filesystem::path& path, nlohmann::json& json_out) { - // Try reading and parsing the base file. - if (read_json(std::ifstream{path}, json_out)) { - return true; - } +static void add_general_options(recomp::config::Config &config) { + using EnumOptionVector = const std::vector; - // Try reading and parsing the backup file. - if (read_json(recomp::open_input_backup_file(path), json_out)) { - return true; - } + // debug_mode + config.add_bool_option( + zelda64::configkeys::general::debug_mode, + "Debug Mode", + "Controls whether the debug menu is visible", + false, + true + ); - // Both reads failed. - return false; -} + // targeting_mode + static EnumOptionVector targeting_mode_options = { + {zelda64::TargetingMode::Switch, "Switch"}, + {zelda64::TargetingMode::Hold, "Hold"}, + }; + config.add_enum_option( + zelda64::configkeys::general::targeting_mode, + "Targeting Mode", + "Controls how targeting enemies and objects works. Switch will start or stop targeting each time the target button is pressed. Hold will start when the target button is pressed and stop when the button is released.", + targeting_mode_options, + zelda64::TargetingMode::Switch + ); -bool save_json_with_backups(const std::filesystem::path& path, const nlohmann::json& json_data) { - { - std::ofstream output_file = recomp::open_output_file_with_backup(path); - if (!output_file.good()) { - return false; - } + // autosave_mode + static EnumOptionVector autosave_mode_options = { + {zelda64::AutosaveMode::Off, "Off"}, + {zelda64::AutosaveMode::On, "On"}, + }; + config.add_enum_option( + zelda64::configkeys::general::autosave_mode, + "Autosaving", + "Turns on autosaving and prevents owl saves from being deleted on load. Autosaves act as owl saves and take up the same slot as they do.\n" + "\n" + "Loading an autosave will place you in Clock Town or at the entrance of the current dungeon if you were in one.\n" + "\n" + "\n" + "If autosaving is disabled, existing autosaves will be deleted when loaded.", + autosave_mode_options, + zelda64::AutosaveMode::On + ); - output_file << std::setw(4) << json_data; - } - return recomp::finalize_output_file_with_backup(path); -} + // camera_invert_mode + static EnumOptionVector camera_invert_mode_options = { + {zelda64::CameraInvertMode::InvertNone, "None"}, + {zelda64::CameraInvertMode::InvertX, "Invert X"}, + {zelda64::CameraInvertMode::InvertY, "Invert Y"}, + {zelda64::CameraInvertMode::InvertBoth, "Invert Both"}, + }; + config.add_enum_option( + zelda64::configkeys::general::camera_invert_mode, + "Aiming Camera Mode", + "Inverts the camera controls for first-person aiming. Invert Y is the default and matches the original game.", + camera_invert_mode_options, + zelda64::CameraInvertMode::InvertY + ); -bool save_general_config(const std::filesystem::path& path) { - nlohmann::json config_json{}; + // analog_cam_mode + static EnumOptionVector analog_cam_mode_options = { + {zelda64::AnalogCamMode::Off, "Off"}, + {zelda64::AnalogCamMode::On, "On"}, + }; + config.add_enum_option( + zelda64::configkeys::general::analog_cam_mode, + "Analog Camera", + "Enables an analog \"free\" camera similar to later entries in the series that's mapped to the right analog stick on the controller.\n" + "\n" + "When you move the right stick, the camera will enter free mode and stop centering behind Link. Press the Target button at any time to go back into the normal camera mode. The camera will also return to normal mode after a cutscene plays or when you move between areas.\n" + "\n" + "This option also enables right stick control while looking and aiming.", + analog_cam_mode_options, + zelda64::AnalogCamMode::Off + ); - zelda64::to_json(config_json["targeting_mode"], zelda64::get_targeting_mode()); - recomp::to_json(config_json["background_input_mode"], recomp::get_background_input_mode()); - config_json["rumble_strength"] = recomp::get_rumble_strength(); - config_json["gyro_sensitivity"] = recomp::get_gyro_sensitivity(); - config_json["mouse_sensitivity"] = recomp::get_mouse_sensitivity(); - config_json["joystick_deadzone"] = recomp::get_joystick_deadzone(); - config_json["autosave_mode"] = zelda64::get_autosave_mode(); - config_json["camera_invert_mode"] = zelda64::get_camera_invert_mode(); - config_json["analog_cam_mode"] = zelda64::get_analog_cam_mode(); - config_json["analog_camera_invert_mode"] = zelda64::get_analog_camera_invert_mode(); - config_json["debug_mode"] = zelda64::get_debug_mode_enabled(); - - return save_json_with_backups(path, config_json); -} - -void set_general_settings_from_json(const nlohmann::json& config_json) { - zelda64::set_targeting_mode(from_or_default(config_json, "targeting_mode", zelda64::TargetingMode::Switch)); - recomp::set_background_input_mode(from_or_default(config_json, "background_input_mode", recomp::BackgroundInputMode::On)); - recomp::set_rumble_strength(from_or_default(config_json, "rumble_strength", 25)); - recomp::set_gyro_sensitivity(from_or_default(config_json, "gyro_sensitivity", 50)); - recomp::set_mouse_sensitivity(from_or_default(config_json, "mouse_sensitivity", is_steam_deck ? 50 : 0)); - recomp::set_joystick_deadzone(from_or_default(config_json, "joystick_deadzone", 5)); - zelda64::set_autosave_mode(from_or_default(config_json, "autosave_mode", zelda64::AutosaveMode::On)); - zelda64::set_camera_invert_mode(from_or_default(config_json, "camera_invert_mode", zelda64::CameraInvertMode::InvertY)); - zelda64::set_analog_cam_mode(from_or_default(config_json, "analog_cam_mode", zelda64::AnalogCamMode::Off)); - zelda64::set_analog_camera_invert_mode(from_or_default(config_json, "analog_camera_invert_mode", zelda64::CameraInvertMode::InvertNone)); - zelda64::set_debug_mode_enabled(from_or_default(config_json, "debug_mode", false)); -} - -bool load_general_config(const std::filesystem::path& path) { - nlohmann::json config_json{}; - if (!read_json_with_backups(path, config_json)) { - return false; - } - - set_general_settings_from_json(config_json); - return true; -} - -void assign_mapping(recomp::InputDevice device, recomp::GameInput input, const std::vector& value) { - for (size_t binding_index = 0; binding_index < std::min(value.size(), recomp::bindings_per_input); binding_index++) { - recomp::set_input_binding(input, binding_index, device, value[binding_index]); - } -}; - -// same as assign_mapping, except will clear unassigned bindings if not in value -void assign_mapping_complete(recomp::InputDevice device, recomp::GameInput input, const std::vector& value) { - for (size_t binding_index = 0; binding_index < recomp::bindings_per_input; binding_index++) { - if (binding_index >= value.size()) { - recomp::set_input_binding(input, binding_index, device, recomp::InputField{}); - } else { - recomp::set_input_binding(input, binding_index, device, value[binding_index]); - } - } -}; - -void assign_all_mappings(recomp::InputDevice device, const recomp::DefaultN64Mappings& values) { - assign_mapping_complete(device, recomp::GameInput::A, values.a); - assign_mapping_complete(device, recomp::GameInput::B, values.b); - assign_mapping_complete(device, recomp::GameInput::Z, values.z); - assign_mapping_complete(device, recomp::GameInput::START, values.start); - assign_mapping_complete(device, recomp::GameInput::DPAD_UP, values.dpad_up); - assign_mapping_complete(device, recomp::GameInput::DPAD_DOWN, values.dpad_down); - assign_mapping_complete(device, recomp::GameInput::DPAD_LEFT, values.dpad_left); - assign_mapping_complete(device, recomp::GameInput::DPAD_RIGHT, values.dpad_right); - assign_mapping_complete(device, recomp::GameInput::L, values.l); - assign_mapping_complete(device, recomp::GameInput::R, values.r); - assign_mapping_complete(device, recomp::GameInput::C_UP, values.c_up); - assign_mapping_complete(device, recomp::GameInput::C_DOWN, values.c_down); - assign_mapping_complete(device, recomp::GameInput::C_LEFT, values.c_left); - assign_mapping_complete(device, recomp::GameInput::C_RIGHT, values.c_right); - - assign_mapping_complete(device, recomp::GameInput::X_AXIS_NEG, values.analog_left); - assign_mapping_complete(device, recomp::GameInput::X_AXIS_POS, values.analog_right); - assign_mapping_complete(device, recomp::GameInput::Y_AXIS_NEG, values.analog_down); - assign_mapping_complete(device, recomp::GameInput::Y_AXIS_POS, values.analog_up); - - assign_mapping_complete(device, recomp::GameInput::TOGGLE_MENU, values.toggle_menu); - assign_mapping_complete(device, recomp::GameInput::ACCEPT_MENU, values.accept_menu); - assign_mapping_complete(device, recomp::GameInput::APPLY_MENU, values.apply_menu); -}; - -void zelda64::reset_input_bindings() { - assign_all_mappings(recomp::InputDevice::Keyboard, recomp::default_n64_keyboard_mappings); - assign_all_mappings(recomp::InputDevice::Controller, recomp::default_n64_controller_mappings); -} - -void zelda64::reset_cont_input_bindings() { - assign_all_mappings(recomp::InputDevice::Controller, recomp::default_n64_controller_mappings); -} - -void zelda64::reset_kb_input_bindings() { - assign_all_mappings(recomp::InputDevice::Keyboard, recomp::default_n64_keyboard_mappings); -} - -void zelda64::reset_single_input_binding(recomp::InputDevice device, recomp::GameInput input) { - assign_mapping_complete( - device, - input, - recomp::get_default_mapping_for_input( - device == recomp::InputDevice::Keyboard ? - recomp::default_n64_keyboard_mappings : - recomp::default_n64_controller_mappings, - input - ) + // analog_camera_invert_mode + static EnumOptionVector analog_camera_invert_mode_options = { + {zelda64::CameraInvertMode::InvertNone, "None"}, + {zelda64::CameraInvertMode::InvertX, "Invert X"}, + {zelda64::CameraInvertMode::InvertY, "Invert Y"}, + {zelda64::CameraInvertMode::InvertBoth, "Invert Both"}, + }; + config.add_enum_option( + zelda64::configkeys::general::analog_camera_invert_mode, + "Analog Camera Mode", + "Inverts the camera controls for the analog camera if it's enabled. None is the default.", + analog_camera_invert_mode_options, + zelda64::CameraInvertMode::InvertNone ); } -void reset_graphics_options() { - ultramodern::renderer::GraphicsConfig new_config{}; - new_config.res_option = res_default; - new_config.wm_option = wm_default(); - new_config.hr_option = hr_default; - new_config.ds_option = ds_default; - new_config.ar_option = ar_default; - new_config.msaa_option = msaa_default; - new_config.rr_option = rr_default; - new_config.hpfb_option = hpfb_default; - new_config.rr_manual_value = rr_manual_default; - new_config.developer_mode = developer_mode_default; - ultramodern::renderer::set_graphics_config(new_config); +bool zelda64::get_debug_mode_enabled() { + return get_general_config_bool_value(zelda64::configkeys::general::debug_mode); } -bool save_graphics_config(const std::filesystem::path& path) { - nlohmann::json config_json{}; - ultramodern::to_json(config_json, ultramodern::renderer::get_graphics_config()); - return save_json_with_backups(path, config_json); +zelda64::AutosaveMode zelda64::get_autosave_mode() { + return get_general_config_enum_value(zelda64::configkeys::general::autosave_mode); } -bool load_graphics_config(const std::filesystem::path& path) { - nlohmann::json config_json{}; - if (!read_json_with_backups(path, config_json)) { - return false; - } - - ultramodern::renderer::GraphicsConfig new_config{}; - ultramodern::from_json(config_json, new_config); - ultramodern::renderer::set_graphics_config(new_config); - return true; +zelda64::TargetingMode zelda64::get_targeting_mode() { + return get_general_config_enum_value(zelda64::configkeys::general::targeting_mode); } -void add_input_bindings(nlohmann::json& out, recomp::GameInput input, recomp::InputDevice device) { - const std::string& input_name = recomp::get_input_enum_name(input); - nlohmann::json& out_array = out[input_name]; - out_array = nlohmann::json::array(); - for (size_t binding_index = 0; binding_index < recomp::bindings_per_input; binding_index++) { - out_array[binding_index] = recomp::get_input_binding(input, binding_index, device); - } -}; - -bool save_controls_config(const std::filesystem::path& path) { - nlohmann::json config_json{}; - - config_json["keyboard"] = {}; - config_json["controller"] = {}; - - for (size_t i = 0; i < recomp::get_num_inputs(); i++) { - recomp::GameInput cur_input = static_cast(i); - - add_input_bindings(config_json["keyboard"], cur_input, recomp::InputDevice::Keyboard); - add_input_bindings(config_json["controller"], cur_input, recomp::InputDevice::Controller); - } - - return save_json_with_backups(path, config_json); +zelda64::CameraInvertMode zelda64::get_camera_invert_mode() { + return get_general_config_enum_value(zelda64::configkeys::general::camera_invert_mode); } -bool load_input_device_from_json(const nlohmann::json& config_json, recomp::InputDevice device, const std::string& key) { - // Check if the json object for the given key exists. - auto find_it = config_json.find(key); - if (find_it == config_json.end()) { - return false; - } - - const nlohmann::json& mappings_json = *find_it; - - for (size_t i = 0; i < recomp::get_num_inputs(); i++) { - recomp::GameInput cur_input = static_cast(i); - const std::string& input_name = recomp::get_input_enum_name(cur_input); - - // Check if the json object for the given input exists and that it's an array. - auto find_input_it = mappings_json.find(input_name); - if (find_input_it == mappings_json.end() || !find_input_it->is_array()) { - assign_mapping( - device, - cur_input, - recomp::get_default_mapping_for_input( - device == recomp::InputDevice::Keyboard ? - recomp::default_n64_keyboard_mappings : - recomp::default_n64_controller_mappings, - cur_input - ) - ); - continue; - } - const nlohmann::json& input_json = *find_input_it; - - // Deserialize all the bindings from the json array (up to the max number of bindings per input). - for (size_t binding_index = 0; binding_index < std::min(recomp::bindings_per_input, input_json.size()); binding_index++) { - recomp::InputField cur_field{}; - recomp::from_json(input_json[binding_index], cur_field); - recomp::set_input_binding(cur_input, binding_index, device, cur_field); - } - } - - return true; +zelda64::AnalogCamMode zelda64::get_analog_cam_mode() { + return get_general_config_enum_value(zelda64::configkeys::general::analog_cam_mode); } -bool load_controls_config(const std::filesystem::path& path) { - nlohmann::json config_json{}; - if (!read_json_with_backups(path, config_json)) { - return false; - } - - if (!load_input_device_from_json(config_json, recomp::InputDevice::Keyboard, "keyboard")) { - assign_all_mappings(recomp::InputDevice::Keyboard, recomp::default_n64_keyboard_mappings); - } - - if (!load_input_device_from_json(config_json, recomp::InputDevice::Controller, "controller")) { - assign_all_mappings(recomp::InputDevice::Controller, recomp::default_n64_controller_mappings); - } - return true; +zelda64::CameraInvertMode zelda64::get_analog_camera_invert_mode() { + return get_general_config_enum_value(zelda64::configkeys::general::analog_camera_invert_mode); } -bool save_sound_config(const std::filesystem::path& path) { - nlohmann::json config_json{}; +static void add_graphics_options(recomp::config::Config &config) { + using EnumOptionVector = const std::vector; - config_json["main_volume"] = zelda64::get_main_volume(); - config_json["bgm_volume"] = zelda64::get_bgm_volume(); - config_json["low_health_beeps"] = zelda64::get_low_health_beeps_enabled(); - - return save_json_with_backups(path, config_json); + // Nothing to do here. } -bool load_sound_config(const std::filesystem::path& path) { - nlohmann::json config_json{}; - if (!read_json_with_backups(path, config_json)) { - return false; - } +static void add_sound_options(recomp::config::Config &config) { + using EnumOptionVector = const std::vector; - zelda64::reset_sound_settings(); - call_if_key_exists(zelda64::set_main_volume, config_json, "main_volume"); - call_if_key_exists(zelda64::set_bgm_volume, config_json, "bgm_volume"); - call_if_key_exists(zelda64::set_low_health_beeps_enabled, config_json, "low_health_beeps"); - return true; + // bgm_volume + config.add_percent_number_option( + zelda64::configkeys::sound::bgm_volume, + "Background Music Volume", + "Controls the overall volume of background music.", + 100.0f + ); + + // low_health_beeps + static EnumOptionVector low_health_beeps_mode_options = { + {zelda64::LowHealthBeepsMode::Off, "Off"}, + {zelda64::LowHealthBeepsMode::On, "On"}, + }; + config.add_enum_option( + zelda64::configkeys::sound::low_health_beeps, + "Low Health Beeps", + "Toggles whether or not the low-health beeping sound plays.", + low_health_beeps_mode_options, + zelda64::LowHealthBeepsMode::On + ); } -void zelda64::load_config() { - detect_steam_deck(); +int zelda64::get_bgm_volume() { + return get_sound_config_number_value(zelda64::configkeys::sound::bgm_volume); +} - std::filesystem::path recomp_dir = zelda64::get_app_folder_path(); - std::filesystem::path general_path = recomp_dir / general_filename; - std::filesystem::path graphics_path = recomp_dir / graphics_filename; - std::filesystem::path controls_path = recomp_dir / controls_filename; - std::filesystem::path sound_path = recomp_dir / sound_filename; +bool zelda64::get_low_health_beeps_enabled() { + return get_sound_config_enum_value(zelda64::configkeys::sound::low_health_beeps) == zelda64::LowHealthBeepsMode::On; +} + +static void set_control_defaults() { + // Nothing to do here. +} + +static void set_control_descriptions() { + // TODO descriptions + recompinput::set_game_input_description(recompinput::GameInput::Y_AXIS_POS, "TODO description"); + recompinput::set_game_input_description(recompinput::GameInput::Y_AXIS_NEG, "TODO description"); + recompinput::set_game_input_description(recompinput::GameInput::X_AXIS_NEG, "TODO description"); + recompinput::set_game_input_description(recompinput::GameInput::X_AXIS_POS, "TODO description"); + recompinput::set_game_input_description(recompinput::GameInput::A, "TODO description"); + recompinput::set_game_input_description(recompinput::GameInput::B, "TODO description"); + recompinput::set_game_input_description(recompinput::GameInput::Z, "TODO description"); + recompinput::set_game_input_description(recompinput::GameInput::L, "TODO description"); + recompinput::set_game_input_description(recompinput::GameInput::R, "TODO description"); + recompinput::set_game_input_description(recompinput::GameInput::START, "TODO description"); + recompinput::set_game_input_description(recompinput::GameInput::C_UP, "TODO description"); + recompinput::set_game_input_description(recompinput::GameInput::C_DOWN, "TODO description"); + recompinput::set_game_input_description(recompinput::GameInput::C_LEFT, "TODO description"); + recompinput::set_game_input_description(recompinput::GameInput::C_RIGHT, "TODO description"); + recompinput::set_game_input_description(recompinput::GameInput::DPAD_UP, "TODO description"); + recompinput::set_game_input_description(recompinput::GameInput::DPAD_DOWN, "TODO description"); + recompinput::set_game_input_description(recompinput::GameInput::DPAD_LEFT, "TODO description"); + recompinput::set_game_input_description(recompinput::GameInput::DPAD_RIGHT, "TODO description"); +} + +void zelda64::init_config() { + std::filesystem::path recomp_dir = recompui::file::get_app_folder_path(); if (!recomp_dir.empty()) { std::filesystem::create_directories(recomp_dir); } - // TODO error handling for failing to save config files after resetting them. + recompui::config::GeneralTabOptions general_options{}; + general_options.has_rumble_strength = true; + general_options.has_gyro_sensitivity = true; + general_options.has_mouse_sensitivity = true; - if (!load_general_config(general_path)) { - // Set the general settings from an empty json to use defaults. - set_general_settings_from_json({}); - save_general_config(general_path); - } + auto &general_config = recompui::config::create_general_tab(general_options); + add_general_options(general_config); - if (!load_graphics_config(graphics_path)) { - reset_graphics_options(); - save_graphics_config(graphics_path); - } + set_control_defaults(); + set_control_descriptions(); + recompui::config::create_controls_tab(); - if (!load_controls_config(controls_path)) { - zelda64::reset_input_bindings(); - save_controls_config(controls_path); - } + auto &graphics_config = recompui::config::create_graphics_tab(); + add_graphics_options(graphics_config); - if (!load_sound_config(sound_path)) { - zelda64::reset_sound_settings(); - save_sound_config(sound_path); - } -} - -void zelda64::save_config() { - std::filesystem::path recomp_dir = zelda64::get_app_folder_path(); - - if (recomp_dir.empty()) { - return; - } - - std::filesystem::create_directories(recomp_dir); - - // TODO error handling for failing to save config files. - - save_general_config(recomp_dir / general_filename); - save_graphics_config(recomp_dir / graphics_filename); - save_controls_config(recomp_dir / controls_filename); - save_sound_config(recomp_dir / sound_filename); + auto &sound_config = recompui::config::create_sound_tab(); + add_sound_options(sound_config); + + recompui::config::create_mods_tab(); + + recompui::config::create_tab( + zelda64::debug::tab_name, + zelda64::debug::tab_id, + [](recompui::ContextId context, recompui::Element* parent) { + // TODO implement debug tab + } + ); + + recompui::config::finalize(); + + recompui::config::set_tab_visible(zelda64::debug::tab_id, get_debug_mode_enabled()); } diff --git a/src/game/controls.cpp b/src/game/controls.cpp deleted file mode 100644 index 0bb3857..0000000 --- a/src/game/controls.cpp +++ /dev/null @@ -1,116 +0,0 @@ -#include - -#include "librecomp/helpers.hpp" -#include "recomp_input.h" -#include "ultramodern/ultramodern.hpp" - -// Arrays that hold the mappings for every input for keyboard and controller respectively. -using input_mapping = std::array; -using input_mapping_array = std::array(recomp::GameInput::COUNT)>; -static input_mapping_array keyboard_input_mappings{}; -static input_mapping_array controller_input_mappings{}; - -// Make the button value array, which maps a button index to its bit field. -#define DEFINE_INPUT(name, value, readable) uint16_t(value##u), -static const std::array n64_button_values = { - DEFINE_N64_BUTTON_INPUTS() -}; -#undef DEFINE_INPUT - -// Make the input name array. -#define DEFINE_INPUT(name, value, readable) readable, -static const std::vector input_names = { - DEFINE_ALL_INPUTS() -}; -#undef DEFINE_INPUT - -// Make the input enum name array. -#define DEFINE_INPUT(name, value, readable) #name, -static const std::vector input_enum_names = { - DEFINE_ALL_INPUTS() -}; -#undef DEFINE_INPUT - -size_t recomp::get_num_inputs() { - return (size_t)GameInput::COUNT; -} - -const std::string& recomp::get_input_name(GameInput input) { - return input_names.at(static_cast(input)); -} - -const std::string& recomp::get_input_enum_name(GameInput input) { - return input_enum_names.at(static_cast(input)); -} - -recomp::GameInput recomp::get_input_from_enum_name(const std::string_view enum_name) { - auto find_it = std::find(input_enum_names.begin(), input_enum_names.end(), enum_name); - if (find_it == input_enum_names.end()) { - return recomp::GameInput::COUNT; - } - - return static_cast(find_it - input_enum_names.begin()); -} - -// Due to an RmlUi limitation this can't be const. Ideally it would return a const reference or even just a straight up copy. -recomp::InputField& recomp::get_input_binding(GameInput input, size_t binding_index, recomp::InputDevice device) { - input_mapping_array& device_mappings = (device == recomp::InputDevice::Controller) ? controller_input_mappings : keyboard_input_mappings; - input_mapping& cur_input_mapping = device_mappings.at(static_cast(input)); - - if (binding_index < cur_input_mapping.size()) { - return cur_input_mapping[binding_index]; - } - else { - static recomp::InputField dummy_field = {}; - return dummy_field; - } -} - -void recomp::set_input_binding(recomp::GameInput input, size_t binding_index, recomp::InputDevice device, recomp::InputField value) { - input_mapping_array& device_mappings = (device == recomp::InputDevice::Controller) ? controller_input_mappings : keyboard_input_mappings; - input_mapping& cur_input_mapping = device_mappings.at(static_cast(input)); - - if (binding_index < cur_input_mapping.size()) { - cur_input_mapping[binding_index] = value; - } -} - -bool recomp::get_n64_input(int controller_num, uint16_t* buttons_out, float* x_out, float* y_out) { - uint16_t cur_buttons = 0; - float cur_x = 0.0f; - float cur_y = 0.0f; - - if (controller_num != 0) { - return false; - } - - if (!recomp::game_input_disabled()) { - for (size_t i = 0; i < n64_button_values.size(); i++) { - size_t input_index = (size_t)GameInput::N64_BUTTON_START + i; - cur_buttons |= recomp::get_input_digital(keyboard_input_mappings[input_index]) ? n64_button_values[i] : 0; - cur_buttons |= recomp::get_input_digital(controller_input_mappings[input_index]) ? n64_button_values[i] : 0; - } - - float joystick_deadzone = recomp::get_joystick_deadzone() / 100.0f; - - float joystick_x = recomp::get_input_analog(controller_input_mappings[(size_t)GameInput::X_AXIS_POS]) - - recomp::get_input_analog(controller_input_mappings[(size_t)GameInput::X_AXIS_NEG]); - - float joystick_y = recomp::get_input_analog(controller_input_mappings[(size_t)GameInput::Y_AXIS_POS]) - - recomp::get_input_analog(controller_input_mappings[(size_t)GameInput::Y_AXIS_NEG]); - - recomp::apply_joystick_deadzone(joystick_x, joystick_y, &joystick_x, &joystick_y); - - cur_x = recomp::get_input_analog(keyboard_input_mappings[(size_t)GameInput::X_AXIS_POS]) - - recomp::get_input_analog(keyboard_input_mappings[(size_t)GameInput::X_AXIS_NEG]) + joystick_x; - - cur_y = recomp::get_input_analog(keyboard_input_mappings[(size_t)GameInput::Y_AXIS_POS]) - - recomp::get_input_analog(keyboard_input_mappings[(size_t)GameInput::Y_AXIS_NEG]) + joystick_y; - } - - *buttons_out = cur_buttons; - *x_out = std::clamp(cur_x, -1.0f, 1.0f); - *y_out = std::clamp(cur_y, -1.0f, 1.0f); - - return true; -} diff --git a/src/game/input.cpp b/src/game/input.cpp deleted file mode 100644 index f06a5b1..0000000 --- a/src/game/input.cpp +++ /dev/null @@ -1,909 +0,0 @@ -#include -#include - -#include "ultramodern/ultramodern.hpp" -#include "recomp.h" -#include "recomp_input.h" -#include "zelda_config.h" -#include "recomp_ui.h" -#include "SDL.h" -#include "promptfont.h" -#include "GamepadMotion.hpp" - -constexpr float axis_threshold = 0.5f; - -struct ControllerState { - SDL_GameController* controller; - std::array latest_accelerometer; - GamepadMotion motion; - uint32_t prev_gyro_timestamp; - ControllerState() : controller{}, latest_accelerometer{}, motion{}, prev_gyro_timestamp{} { - motion.Reset(); - motion.SetCalibrationMode(GamepadMotionHelpers::CalibrationMode::Stillness | GamepadMotionHelpers::CalibrationMode::SensorFusion); - }; -}; - -static struct { - const Uint8* keys = nullptr; - SDL_Keymod keymod = SDL_Keymod::KMOD_NONE; - int numkeys = 0; - std::atomic_int32_t mouse_wheel_pos = 0; - std::mutex cur_controllers_mutex; - std::vector cur_controllers{}; - std::unordered_map controller_states; - - std::array rotation_delta{}; - std::array mouse_delta{}; - std::mutex pending_input_mutex; - std::array pending_rotation_delta{}; - std::array pending_mouse_delta{}; - - float cur_rumble; - bool rumble_active; -} InputState; - -static struct { - std::list files_dropped; -} DropState; - -std::atomic scanning_device = recomp::InputDevice::COUNT; -std::atomic scanned_input; - -enum class InputType { - None = 0, // Using zero for None ensures that default initialized InputFields are unbound. - Keyboard, - Mouse, - ControllerDigital, - ControllerAnalog // Axis input_id values are the SDL value + 1 -}; - -void set_scanned_input(recomp::InputField value) { - scanning_device.store(recomp::InputDevice::COUNT); - scanned_input.store(value); -} - -recomp::InputField recomp::get_scanned_input() { - recomp::InputField ret = scanned_input.load(); - scanned_input.store({}); - return ret; -} - -void recomp::start_scanning_input(recomp::InputDevice device) { - scanned_input.store({}); - scanning_device.store(device); -} - -void recomp::stop_scanning_input() { - scanning_device.store(recomp::InputDevice::COUNT); -} - -void queue_if_enabled(SDL_Event* event) { - if (!recomp::all_input_disabled()) { - recompui::queue_event(*event); - } -} - -static std::atomic_bool cursor_enabled = true; - -void recompui::set_cursor_visible(bool visible) { - cursor_enabled.store(visible); -} - -bool should_override_keystate(SDL_Scancode key, SDL_Keymod mod) { - // Override Enter when Alt is held. - if (key == SDL_Scancode::SDL_SCANCODE_RETURN) { - if (mod & SDL_Keymod::KMOD_ALT) { - return true; - } - } - - return false; -} - -bool sdl_event_filter(void* userdata, SDL_Event* event) { - switch (event->type) { - case SDL_EventType::SDL_KEYDOWN: - { - SDL_KeyboardEvent* keyevent = &event->key; - - // Skip repeated events when not in the menu - if (!recompui::is_context_capturing_input() && - event->key.repeat) { - break; - } - - if ((keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_RETURN && (keyevent->keysym.mod & SDL_Keymod::KMOD_ALT)) || - keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_F11 - ) { - recompui::toggle_fullscreen(); - } - if (scanning_device != recomp::InputDevice::COUNT) { - if (keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_ESCAPE) { - recomp::cancel_scanning_input(); - } else if (scanning_device == recomp::InputDevice::Keyboard) { - set_scanned_input({(uint32_t)InputType::Keyboard, keyevent->keysym.scancode}); - } - } else { - if (!should_override_keystate(keyevent->keysym.scancode, static_cast(keyevent->keysym.mod))) { - queue_if_enabled(event); - } - } - } - break; - case SDL_EventType::SDL_CONTROLLERDEVICEADDED: - { - SDL_ControllerDeviceEvent* controller_event = &event->cdevice; - SDL_GameController* controller = SDL_GameControllerOpen(controller_event->which); - printf("Controller added: %d\n", controller_event->which); - if (controller != nullptr) { - printf(" Instance ID: %d\n", SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller))); - ControllerState& state = InputState.controller_states[SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller))]; - state.controller = controller; - - if (SDL_GameControllerHasSensor(controller, SDL_SensorType::SDL_SENSOR_GYRO) && SDL_GameControllerHasSensor(controller, SDL_SensorType::SDL_SENSOR_ACCEL)) { - SDL_GameControllerSetSensorEnabled(controller, SDL_SensorType::SDL_SENSOR_GYRO, SDL_TRUE); - SDL_GameControllerSetSensorEnabled(controller, SDL_SensorType::SDL_SENSOR_ACCEL, SDL_TRUE); - } - } - } - break; - case SDL_EventType::SDL_CONTROLLERDEVICEREMOVED: - { - SDL_ControllerDeviceEvent* controller_event = &event->cdevice; - printf("Controller removed: %d\n", controller_event->which); - InputState.controller_states.erase(controller_event->which); - } - break; - case SDL_EventType::SDL_QUIT: { - if (!ultramodern::is_game_started()) { - ultramodern::quit(); - return true; - } - - zelda64::open_quit_game_prompt(); - recompui::activate_mouse(); - break; - } - case SDL_EventType::SDL_MOUSEWHEEL: - { - SDL_MouseWheelEvent* wheel_event = &event->wheel; - InputState.mouse_wheel_pos.fetch_add(wheel_event->y * (wheel_event->direction == SDL_MOUSEWHEEL_FLIPPED ? -1 : 1)); - } - queue_if_enabled(event); - break; - case SDL_EventType::SDL_CONTROLLERBUTTONDOWN: - if (scanning_device != recomp::InputDevice::COUNT) { - auto menuToggleBinding0 = recomp::get_input_binding(recomp::GameInput::TOGGLE_MENU, 0, recomp::InputDevice::Controller); - auto menuToggleBinding1 = recomp::get_input_binding(recomp::GameInput::TOGGLE_MENU, 1, recomp::InputDevice::Controller); - // note - magic number: 0 is InputType::None - if ((menuToggleBinding0.input_type != 0 && event->cbutton.button == menuToggleBinding0.input_id) || - (menuToggleBinding1.input_type != 0 && event->cbutton.button == menuToggleBinding1.input_id)) { - recomp::cancel_scanning_input(); - } else if (scanning_device == recomp::InputDevice::Controller) { - SDL_ControllerButtonEvent* button_event = &event->cbutton; - auto scanned_input_index = recomp::get_scanned_input_index(); - if ((scanned_input_index == static_cast(recomp::GameInput::TOGGLE_MENU) || - scanned_input_index == static_cast(recomp::GameInput::ACCEPT_MENU) || - scanned_input_index == static_cast(recomp::GameInput::APPLY_MENU)) && ( - button_event->button == SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_UP || - button_event->button == SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_DOWN || - button_event->button == SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_LEFT || - button_event->button == SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_RIGHT)) { - break; - } - - set_scanned_input({(uint32_t)InputType::ControllerDigital, button_event->button}); - } - } else { - queue_if_enabled(event); - } - break; - case SDL_EventType::SDL_CONTROLLERAXISMOTION: - if (scanning_device == recomp::InputDevice::Controller) { - auto scanned_input_index = recomp::get_scanned_input_index(); - if (scanned_input_index == static_cast(recomp::GameInput::TOGGLE_MENU) || - scanned_input_index == static_cast(recomp::GameInput::ACCEPT_MENU) || - scanned_input_index == static_cast(recomp::GameInput::APPLY_MENU)) { - break; - } - - SDL_ControllerAxisEvent* axis_event = &event->caxis; - float axis_value = axis_event->value * (1/32768.0f); - if (axis_value > axis_threshold) { - SDL_Event set_stick_return_event; - set_stick_return_event.type = SDL_USEREVENT; - set_stick_return_event.user.code = axis_event->axis; - set_stick_return_event.user.data1 = nullptr; - set_stick_return_event.user.data2 = nullptr; - recompui::queue_event(set_stick_return_event); - - set_scanned_input({(uint32_t)InputType::ControllerAnalog, axis_event->axis + 1}); - } - else if (axis_value < -axis_threshold) { - SDL_Event set_stick_return_event; - set_stick_return_event.type = SDL_USEREVENT; - set_stick_return_event.user.code = axis_event->axis; - set_stick_return_event.user.data1 = nullptr; - set_stick_return_event.user.data2 = nullptr; - recompui::queue_event(set_stick_return_event); - - set_scanned_input({(uint32_t)InputType::ControllerAnalog, -axis_event->axis - 1}); - } - } else { - queue_if_enabled(event); - } - break; - case SDL_EventType::SDL_CONTROLLERSENSORUPDATE: - if (event->csensor.sensor == SDL_SensorType::SDL_SENSOR_ACCEL) { - // Convert acceleration to g's. - float x = event->csensor.data[0] / SDL_STANDARD_GRAVITY; - float y = event->csensor.data[1] / SDL_STANDARD_GRAVITY; - float z = event->csensor.data[2] / SDL_STANDARD_GRAVITY; - ControllerState& state = InputState.controller_states[event->csensor.which]; - state.latest_accelerometer[0] = x; - state.latest_accelerometer[1] = y; - state.latest_accelerometer[2] = z; - } - else if (event->csensor.sensor == SDL_SensorType::SDL_SENSOR_GYRO) { - // constexpr float gyro_threshold = 0.05f; - // Convert rotational velocity to degrees per second. - constexpr float rad_to_deg = 180.0f / M_PI; - float x = event->csensor.data[0] * rad_to_deg; - float y = event->csensor.data[1] * rad_to_deg; - float z = event->csensor.data[2] * rad_to_deg; - ControllerState& state = InputState.controller_states[event->csensor.which]; - uint64_t cur_timestamp = event->csensor.timestamp; - uint32_t delta_ms = cur_timestamp - state.prev_gyro_timestamp; - state.motion.ProcessMotion(x, y, z, state.latest_accelerometer[0], state.latest_accelerometer[1], state.latest_accelerometer[2], delta_ms * 0.001f); - state.prev_gyro_timestamp = cur_timestamp; - - float rot_x = 0.0f; - float rot_y = 0.0f; - state.motion.GetPlayerSpaceGyro(rot_x, rot_y); - - { - std::lock_guard lock{ InputState.pending_input_mutex }; - InputState.pending_rotation_delta[0] += rot_x; - InputState.pending_rotation_delta[1] += rot_y; - } - } - break; - case SDL_EventType::SDL_MOUSEMOTION: - if (!recomp::game_input_disabled()) { - SDL_MouseMotionEvent* motion_event = &event->motion; - std::lock_guard lock{ InputState.pending_input_mutex }; - InputState.pending_mouse_delta[0] += motion_event->xrel; - InputState.pending_mouse_delta[1] += motion_event->yrel; - } - queue_if_enabled(event); - break; - case SDL_EventType::SDL_DROPBEGIN: - DropState.files_dropped.clear(); - break; - case SDL_EventType::SDL_DROPFILE: - DropState.files_dropped.emplace_back(std::filesystem::path(std::u8string_view((const char8_t *)(event->drop.file)))); - SDL_free(event->drop.file); - break; - case SDL_EventType::SDL_DROPCOMPLETE: - recompui::drop_files(DropState.files_dropped); - break; - case SDL_EventType::SDL_CONTROLLERBUTTONUP: - // Always queue button up events to avoid missing them during binding. - recompui::queue_event(*event); - break; - default: - queue_if_enabled(event); - break; - } - return false; -} - -void recomp::handle_events() { - SDL_Event cur_event; - static bool started = false; - static bool exited = false; - while (SDL_PollEvent(&cur_event) && !exited) { - exited = sdl_event_filter(nullptr, &cur_event); - - // Lock the cursor if all three conditions are true: mouse aiming is enabled, game input is not disabled, and the game has been started. - bool cursor_locked = (recomp::get_mouse_sensitivity() != 0) && !recomp::game_input_disabled() && ultramodern::is_game_started(); - - // Hide the cursor based on its enable state, but override visibility to false if the cursor is locked. - bool cursor_visible = cursor_enabled; - if (cursor_locked) { - cursor_visible = false; - } - - SDL_ShowCursor(cursor_visible ? SDL_ENABLE : SDL_DISABLE); - SDL_SetRelativeMouseMode(cursor_locked ? SDL_TRUE : SDL_FALSE); - } - - if (!started && ultramodern::is_game_started()) { - started = true; - recompui::process_game_started(); - } -} - -constexpr SDL_GameControllerButton SDL_CONTROLLER_BUTTON_SOUTH = SDL_CONTROLLER_BUTTON_A; -constexpr SDL_GameControllerButton SDL_CONTROLLER_BUTTON_EAST = SDL_CONTROLLER_BUTTON_B; -constexpr SDL_GameControllerButton SDL_CONTROLLER_BUTTON_WEST = SDL_CONTROLLER_BUTTON_X; -constexpr SDL_GameControllerButton SDL_CONTROLLER_BUTTON_NORTH = SDL_CONTROLLER_BUTTON_Y; - -const recomp::DefaultN64Mappings recomp::default_n64_keyboard_mappings = { - .a = { - {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_SPACE} - }, - .b = { - {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_LSHIFT} - }, - .l = { - {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_E} - }, - .r = { - {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_R} - }, - .z = { - {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_Q} - }, - .start = { - {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_RETURN} - }, - .c_left = { - {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_LEFT} - }, - .c_right = { - {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_RIGHT} - }, - .c_up = { - {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_UP} - }, - .c_down = { - {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_DOWN} - }, - .dpad_left = { - {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_J} - }, - .dpad_right = { - {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_L} - }, - .dpad_up = { - {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_I} - }, - .dpad_down = { - {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_K} - }, - .analog_left = { - {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_A} - }, - .analog_right = { - {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_D} - }, - .analog_up = { - {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_W} - }, - .analog_down = { - {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_S} - }, - .toggle_menu = { - {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_ESCAPE} - }, - .accept_menu = { - {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_RETURN} - }, - .apply_menu = { - {.input_type = (uint32_t)InputType::Keyboard, .input_id = SDL_SCANCODE_F} - } -}; - -const recomp::DefaultN64Mappings recomp::default_n64_controller_mappings = { - .a = { - {.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_SOUTH}, - }, - .b = { - {.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_WEST}, - }, - .l = { - {.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, - }, - .r = { - {.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_TRIGGERRIGHT + 1}, - }, - .z = { - {.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_TRIGGERLEFT + 1}, - }, - .start = { - {.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_START}, - }, - .c_left = { - {.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = -(SDL_CONTROLLER_AXIS_RIGHTX + 1)}, - {.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_NORTH}, - }, - .c_right = { - {.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_RIGHTX + 1}, - {.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_EAST}, - }, - .c_up = { - {.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = -(SDL_CONTROLLER_AXIS_RIGHTY + 1)}, - {.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_RIGHTSTICK}, - }, - .c_down = { - {.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_RIGHTY + 1}, - {.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, - }, - .dpad_left = { - {.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_DPAD_LEFT}, - }, - .dpad_right = { - {.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_DPAD_RIGHT}, - }, - .dpad_up = { - {.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_DPAD_UP}, - }, - .dpad_down = { - {.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_DPAD_DOWN}, - }, - .analog_left = { - {.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = -(SDL_CONTROLLER_AXIS_LEFTX + 1)}, - }, - .analog_right = { - {.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_LEFTX + 1}, - }, - .analog_up = { - {.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = -(SDL_CONTROLLER_AXIS_LEFTY + 1)}, - }, - .analog_down = { - {.input_type = (uint32_t)InputType::ControllerAnalog, .input_id = SDL_CONTROLLER_AXIS_LEFTY + 1}, - }, - .toggle_menu = { - {.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_BACK}, - }, - .accept_menu = { - {.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_SOUTH}, - }, - .apply_menu = { - {.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_WEST}, - {.input_type = (uint32_t)InputType::ControllerDigital, .input_id = SDL_CONTROLLER_BUTTON_START} - } -}; - -void recomp::poll_inputs() { - InputState.keys = SDL_GetKeyboardState(&InputState.numkeys); - InputState.keymod = SDL_GetModState(); - - { - std::lock_guard lock{ InputState.cur_controllers_mutex }; - InputState.cur_controllers.clear(); - - for (const auto& [id, state] : InputState.controller_states) { - (void)id; // Avoid unused variable warning. - SDL_GameController* controller = state.controller; - if (controller != nullptr) { - InputState.cur_controllers.push_back(controller); - } - } - } - - // Read the deltas while resetting them to zero. - { - std::lock_guard lock{ InputState.pending_input_mutex }; - - InputState.rotation_delta = InputState.pending_rotation_delta; - InputState.pending_rotation_delta = { 0.0f, 0.0f }; - - InputState.mouse_delta = InputState.pending_mouse_delta; - InputState.pending_mouse_delta = { 0.0f, 0.0f }; - } - - // Quicksaving is disabled for now and will likely have more limited functionality - // when restored, rather than allowing saving and loading at any point in time. - #if 0 - if (InputState.keys) { - static bool save_was_held = false; - static bool load_was_held = false; - bool save_is_held = InputState.keys[SDL_SCANCODE_F5] != 0; - bool load_is_held = InputState.keys[SDL_SCANCODE_F7] != 0; - if (save_is_held && !save_was_held) { - zelda64::quicksave_save(); - } - else if (load_is_held && !load_was_held) { - zelda64::quicksave_load(); - } - save_was_held = save_is_held; - } - #endif -} - -void recomp::set_rumble(int controller_num, bool on) { - if (controller_num == 0) { - InputState.rumble_active = on; - } -} - -ultramodern::input::connected_device_info_t recomp::get_connected_device_info(int controller_num) { - switch (controller_num) { - case 0: - return ultramodern::input::connected_device_info_t { - .connected_device = ultramodern::input::Device::Controller, - .connected_pak = ultramodern::input::Pak::RumblePak, - }; - } - - return ultramodern::input::connected_device_info_t { - .connected_device = ultramodern::input::Device::None, - .connected_pak = ultramodern::input::Pak::None, - }; -} - -static float smoothstep(float from, float to, float amount) { - amount = (amount * amount) * (3.0f - 2.0f * amount); - return std::lerp(from, to, amount); -} - -// Update rumble to attempt to mimic the way n64 rumble ramps up and falls off -void recomp::update_rumble() { - // Note: values are not accurate! just approximations based on feel - if (InputState.rumble_active) { - InputState.cur_rumble += 0.17f; - if (InputState.cur_rumble > 1) InputState.cur_rumble = 1; - } else { - InputState.cur_rumble *= 0.92f; - InputState.cur_rumble -= 0.01f; - if (InputState.cur_rumble < 0) InputState.cur_rumble = 0; - } - float smooth_rumble = smoothstep(0, 1, InputState.cur_rumble); - - uint16_t rumble_strength = smooth_rumble * (recomp::get_rumble_strength() * 0xFFFF / 100); - uint32_t duration = 1000000; // Dummy duration value that lasts long enough to matter as the game will reset rumble on its own. - { - std::lock_guard lock{ InputState.cur_controllers_mutex }; - for (const auto& controller : InputState.cur_controllers) { - SDL_GameControllerRumble(controller, 0, rumble_strength, duration); - } - } -} - -bool controller_button_state(int32_t input_id) { - if (input_id >= 0 && input_id < SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_MAX) { - SDL_GameControllerButton button = (SDL_GameControllerButton)input_id; - bool ret = false; - { - std::lock_guard lock{ InputState.cur_controllers_mutex }; - for (const auto& controller : InputState.cur_controllers) { - ret |= SDL_GameControllerGetButton(controller, button); - } - } - - return ret; - } - return false; -} - -static std::atomic_bool right_analog_suppressed = false; - -float controller_axis_state(int32_t input_id, bool allow_suppression) { - if (abs(input_id) - 1 < SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_MAX) { - SDL_GameControllerAxis axis = (SDL_GameControllerAxis)(abs(input_id) - 1); - bool negative_range = input_id < 0; - float ret = 0.0f; - - { - std::lock_guard lock{ InputState.cur_controllers_mutex }; - for (const auto& controller : InputState.cur_controllers) { - float cur_val = SDL_GameControllerGetAxis(controller, axis) * (1/32768.0f); - if (negative_range) { - cur_val = -cur_val; - } - - // Check if this input is a right analog axis and suppress it accordingly. - if (allow_suppression && right_analog_suppressed.load() && - (axis == SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTX || axis == SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTY)) { - cur_val = 0; - } - ret += std::clamp(cur_val, 0.0f, 1.0f); - } - } - - return std::clamp(ret, 0.0f, 1.0f); - } - return false; -} - -float recomp::get_input_analog(const recomp::InputField& field) { - switch ((InputType)field.input_type) { - case InputType::Keyboard: - if (InputState.keys && field.input_id >= 0 && field.input_id < InputState.numkeys) { - if (should_override_keystate(static_cast(field.input_id), InputState.keymod)) { - return 0.0f; - } - return InputState.keys[field.input_id] ? 1.0f : 0.0f; - } - return 0.0f; - case InputType::ControllerDigital: - return controller_button_state(field.input_id) ? 1.0f : 0.0f; - case InputType::ControllerAnalog: - return controller_axis_state(field.input_id, true); - case InputType::Mouse: - // TODO mouse support - return 0.0f; - case InputType::None: - return false; - } -} - -float recomp::get_input_analog(const std::span fields) { - float ret = 0.0f; - for (const auto& field : fields) { - ret += get_input_analog(field); - } - return std::clamp(ret, 0.0f, 1.0f); -} - -bool recomp::get_input_digital(const recomp::InputField& field) { - switch ((InputType)field.input_type) { - case InputType::Keyboard: - if (InputState.keys && field.input_id >= 0 && field.input_id < InputState.numkeys) { - if (should_override_keystate(static_cast(field.input_id), InputState.keymod)) { - return false; - } - return InputState.keys[field.input_id] != 0; - } - return false; - case InputType::ControllerDigital: - return controller_button_state(field.input_id); - case InputType::ControllerAnalog: - // TODO adjustable threshold - return controller_axis_state(field.input_id, true) >= axis_threshold; - case InputType::Mouse: - // TODO mouse support - return false; - case InputType::None: - return false; - } -} - -bool recomp::get_input_digital(const std::span fields) { - bool ret = 0; - for (const auto& field : fields) { - ret |= get_input_digital(field); - } - return ret; -} - -void recomp::get_gyro_deltas(float* x, float* y) { - std::array cur_rotation_delta = InputState.rotation_delta; - float sensitivity = (float)recomp::get_gyro_sensitivity() / 100.0f; - *x = cur_rotation_delta[0] * sensitivity; - *y = cur_rotation_delta[1] * sensitivity; -} - -void recomp::get_mouse_deltas(float* x, float* y) { - std::array cur_mouse_delta = InputState.mouse_delta; - float sensitivity = (float)recomp::get_mouse_sensitivity() / 100.0f; - *x = cur_mouse_delta[0] * sensitivity; - *y = cur_mouse_delta[1] * sensitivity; -} - -void recomp::apply_joystick_deadzone(float x_in, float y_in, float* x_out, float* y_out) { - float joystick_deadzone = (float)recomp::get_joystick_deadzone() / 100.0f; - - if(fabsf(x_in) < joystick_deadzone) { - x_in = 0.0f; - } - else { - if(x_in > 0.0f) { - x_in -= joystick_deadzone; - } - else { - x_in += joystick_deadzone; - } - - x_in /= (1.0f - joystick_deadzone); - } - - if(fabsf(y_in) < joystick_deadzone) { - y_in = 0.0f; - } - else { - if(y_in > 0.0f) { - y_in -= joystick_deadzone; - } - else { - y_in += joystick_deadzone; - } - - y_in /= (1.0f - joystick_deadzone); - } - - *x_out = x_in; - *y_out = y_in; -} - -void recomp::get_right_analog(float* x, float* y) { - float x_val = - controller_axis_state((SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTX + 1), false) - - controller_axis_state(-(SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTX + 1), false); - float y_val = - controller_axis_state((SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTY + 1), false) - - controller_axis_state(-(SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTY + 1), false); - recomp::apply_joystick_deadzone(x_val, y_val, x, y); -} - -void recomp::set_right_analog_suppressed(bool suppressed) { - right_analog_suppressed.store(suppressed); -} - -bool recomp::game_input_disabled() { - // Disable input if any menu that blocks input is open. - return recompui::is_context_capturing_input(); -} - -bool recomp::all_input_disabled() { - // Disable all input if an input is being polled. - return scanning_device != recomp::InputDevice::COUNT; -} - -std::string controller_button_to_string(SDL_GameControllerButton button) { - switch (button) { - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_A: - return PF_GAMEPAD_A; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_B: - return PF_GAMEPAD_B; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_X: - return PF_GAMEPAD_X; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_Y: - return PF_GAMEPAD_Y; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_BACK: - return PF_XBOX_VIEW; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_GUIDE: - return PF_GAMEPAD_HOME; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_START: - return PF_XBOX_MENU; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_LEFTSTICK: - return PF_ANALOG_L_CLICK; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_RIGHTSTICK: - return PF_ANALOG_R_CLICK; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_LEFTSHOULDER: - return PF_XBOX_LEFT_SHOULDER; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: - return PF_XBOX_RIGHT_SHOULDER; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_UP: - return PF_DPAD_UP; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_DOWN: - return PF_DPAD_DOWN; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_LEFT: - return PF_DPAD_LEFT; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_RIGHT: - return PF_DPAD_RIGHT; - // case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_MISC1: - // return ""; - // case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE1: - // return ""; - // case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE2: - // return ""; - // case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE3: - // return ""; - // case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE4: - // return ""; - case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_TOUCHPAD: - return PF_SONY_TOUCHPAD; - default: - return "Button " + std::to_string(button); - } -} - -std::unordered_map scancode_codepoints { - {SDL_SCANCODE_LEFT, PF_KEYBOARD_LEFT}, - // NOTE: UP and RIGHT are swapped with promptfont. - {SDL_SCANCODE_UP, PF_KEYBOARD_RIGHT}, - {SDL_SCANCODE_RIGHT, PF_KEYBOARD_UP}, - {SDL_SCANCODE_DOWN, PF_KEYBOARD_DOWN}, - {SDL_SCANCODE_A, PF_KEYBOARD_A}, - {SDL_SCANCODE_B, PF_KEYBOARD_B}, - {SDL_SCANCODE_C, PF_KEYBOARD_C}, - {SDL_SCANCODE_D, PF_KEYBOARD_D}, - {SDL_SCANCODE_E, PF_KEYBOARD_E}, - {SDL_SCANCODE_F, PF_KEYBOARD_F}, - {SDL_SCANCODE_G, PF_KEYBOARD_G}, - {SDL_SCANCODE_H, PF_KEYBOARD_H}, - {SDL_SCANCODE_I, PF_KEYBOARD_I}, - {SDL_SCANCODE_J, PF_KEYBOARD_J}, - {SDL_SCANCODE_K, PF_KEYBOARD_K}, - {SDL_SCANCODE_L, PF_KEYBOARD_L}, - {SDL_SCANCODE_M, PF_KEYBOARD_M}, - {SDL_SCANCODE_N, PF_KEYBOARD_N}, - {SDL_SCANCODE_O, PF_KEYBOARD_O}, - {SDL_SCANCODE_P, PF_KEYBOARD_P}, - {SDL_SCANCODE_Q, PF_KEYBOARD_Q}, - {SDL_SCANCODE_R, PF_KEYBOARD_R}, - {SDL_SCANCODE_S, PF_KEYBOARD_S}, - {SDL_SCANCODE_T, PF_KEYBOARD_T}, - {SDL_SCANCODE_U, PF_KEYBOARD_U}, - {SDL_SCANCODE_V, PF_KEYBOARD_V}, - {SDL_SCANCODE_W, PF_KEYBOARD_W}, - {SDL_SCANCODE_X, PF_KEYBOARD_X}, - {SDL_SCANCODE_Y, PF_KEYBOARD_Y}, - {SDL_SCANCODE_Z, PF_KEYBOARD_Z}, - {SDL_SCANCODE_0, PF_KEYBOARD_0}, - {SDL_SCANCODE_1, PF_KEYBOARD_1}, - {SDL_SCANCODE_2, PF_KEYBOARD_2}, - {SDL_SCANCODE_3, PF_KEYBOARD_3}, - {SDL_SCANCODE_4, PF_KEYBOARD_4}, - {SDL_SCANCODE_5, PF_KEYBOARD_5}, - {SDL_SCANCODE_6, PF_KEYBOARD_6}, - {SDL_SCANCODE_7, PF_KEYBOARD_7}, - {SDL_SCANCODE_8, PF_KEYBOARD_8}, - {SDL_SCANCODE_9, PF_KEYBOARD_9}, - {SDL_SCANCODE_ESCAPE, PF_KEYBOARD_ESCAPE}, - {SDL_SCANCODE_F1, PF_KEYBOARD_F1}, - {SDL_SCANCODE_F2, PF_KEYBOARD_F2}, - {SDL_SCANCODE_F3, PF_KEYBOARD_F3}, - {SDL_SCANCODE_F4, PF_KEYBOARD_F4}, - {SDL_SCANCODE_F5, PF_KEYBOARD_F5}, - {SDL_SCANCODE_F6, PF_KEYBOARD_F6}, - {SDL_SCANCODE_F7, PF_KEYBOARD_F7}, - {SDL_SCANCODE_F8, PF_KEYBOARD_F8}, - {SDL_SCANCODE_F9, PF_KEYBOARD_F9}, - {SDL_SCANCODE_F10, PF_KEYBOARD_F10}, - {SDL_SCANCODE_F11, PF_KEYBOARD_F11}, - {SDL_SCANCODE_F12, PF_KEYBOARD_F12}, - {SDL_SCANCODE_PRINTSCREEN, PF_KEYBOARD_PRINT_SCREEN}, - {SDL_SCANCODE_SCROLLLOCK, PF_KEYBOARD_SCROLL_LOCK}, - {SDL_SCANCODE_PAUSE, PF_KEYBOARD_PAUSE}, - {SDL_SCANCODE_INSERT, PF_KEYBOARD_INSERT}, - {SDL_SCANCODE_HOME, PF_KEYBOARD_HOME}, - {SDL_SCANCODE_PAGEUP, PF_KEYBOARD_PAGE_UP}, - {SDL_SCANCODE_DELETE, PF_KEYBOARD_DELETE}, - {SDL_SCANCODE_END, PF_KEYBOARD_END}, - {SDL_SCANCODE_PAGEDOWN, PF_KEYBOARD_PAGE_DOWN}, - {SDL_SCANCODE_SPACE, PF_KEYBOARD_SPACE}, - {SDL_SCANCODE_BACKSPACE, PF_KEYBOARD_BACKSPACE}, - {SDL_SCANCODE_TAB, PF_KEYBOARD_TAB}, - {SDL_SCANCODE_RETURN, PF_KEYBOARD_ENTER}, - {SDL_SCANCODE_CAPSLOCK, PF_KEYBOARD_CAPS}, - {SDL_SCANCODE_NUMLOCKCLEAR, PF_KEYBOARD_NUM_LOCK}, - {SDL_SCANCODE_LSHIFT, "L" PF_KEYBOARD_SHIFT}, - {SDL_SCANCODE_RSHIFT, "R" PF_KEYBOARD_SHIFT}, -}; - -std::string keyboard_input_to_string(SDL_Scancode key) { - if (scancode_codepoints.find(key) != scancode_codepoints.end()) { - return scancode_codepoints[key]; - } - return std::to_string(key); -} - -std::string controller_axis_to_string(int axis) { - bool positive = axis > 0; - SDL_GameControllerAxis actual_axis = SDL_GameControllerAxis(abs(axis) - 1); - switch (actual_axis) { - case SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_LEFTX: - return positive ? "\u21C0" : "\u21BC"; - case SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_LEFTY: - return positive ? "\u21C2" : "\u21BE"; - case SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTX: - return positive ? "\u21C1" : "\u21BD"; - case SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTY: - return positive ? "\u21C3" : "\u21BF"; - case SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_TRIGGERLEFT: - return positive ? "\u2196" : "\u21DC"; - case SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_TRIGGERRIGHT: - return positive ? "\u2197" : "\u21DD"; - default: - return "Axis " + std::to_string(actual_axis) + (positive ? '+' : '-'); - } -} - -std::string recomp::InputField::to_string() const { - switch ((InputType)input_type) { - case InputType::None: - return ""; - case InputType::ControllerDigital: - return controller_button_to_string((SDL_GameControllerButton)input_id); - case InputType::ControllerAnalog: - return controller_axis_to_string(input_id); - case InputType::Keyboard: - return keyboard_input_to_string((SDL_Scancode)input_id); - default: - return std::to_string(input_type) + "," + std::to_string(input_id); - } -} diff --git a/src/game/recomp_actor_api.cpp b/src/game/recomp_actor_api.cpp index 759b9a0..72c7928 100644 --- a/src/game/recomp_actor_api.cpp +++ b/src/game/recomp_actor_api.cpp @@ -6,7 +6,7 @@ #include "librecomp/helpers.hpp" #include "librecomp/addresses.hpp" #include "ultramodern/error_handling.hpp" -#include "recomp_ui.h" +#include "recompui/recompui.h" #include "recomp_data.h" #include "../patches/actor_funcs.h" diff --git a/src/game/recomp_api.cpp b/src/game/recomp_api.cpp index 9ad08be..eeb7c4b 100644 --- a/src/game/recomp_api.cpp +++ b/src/game/recomp_api.cpp @@ -3,9 +3,9 @@ #include "recomp.h" #include "librecomp/overlays.hpp" #include "zelda_config.h" -#include "recomp_input.h" -#include "recomp_ui.h" -#include "zelda_render.h" +#include "recompinput/recompinput.h" +#include "recompui/recompui.h" +#include "recompui/renderer.h" #include "zelda_sound.h" #include "librecomp/helpers.hpp" #include "../patches/input.h" @@ -15,7 +15,7 @@ #include "ultramodern/config.hpp" extern "C" void recomp_update_inputs(uint8_t* rdram, recomp_context* ctx) { - recomp::poll_inputs(); + recompinput::poll_inputs(); } extern "C" void recomp_puts(uint8_t* rdram, recomp_context* ctx) { @@ -35,14 +35,14 @@ extern "C" void recomp_get_gyro_deltas(uint8_t* rdram, recomp_context* ctx) { float* x_out = _arg<0, float*>(rdram, ctx); float* y_out = _arg<1, float*>(rdram, ctx); - recomp::get_gyro_deltas(x_out, y_out); + recompinput::get_gyro_deltas(0, x_out, y_out); } extern "C" void recomp_get_mouse_deltas(uint8_t* rdram, recomp_context* ctx) { float* x_out = _arg<0, float*>(rdram, ctx); float* y_out = _arg<1, float*>(rdram, ctx); - recomp::get_mouse_deltas(x_out, y_out); + recompinput::get_mouse_deltas(x_out, y_out); } extern "C" void recomp_powf(uint8_t* rdram, recomp_context* ctx) { @@ -115,7 +115,7 @@ extern "C" void recomp_load_overlays(uint8_t * rdram, recomp_context * ctx) { } extern "C" void recomp_high_precision_fb_enabled(uint8_t * rdram, recomp_context * ctx) { - _return(ctx, static_cast(zelda64::renderer::RT64HighPrecisionFBEnabled())); + _return(ctx, static_cast(recompui::renderer::RT64HighPrecisionFBEnabled())); } extern "C" void recomp_get_resolution_scale(uint8_t* rdram, recomp_context* ctx) { @@ -155,7 +155,8 @@ extern "C" void recomp_get_camera_inputs(uint8_t* rdram, recomp_context* ctx) { float x, y; - recomp::get_right_analog(&x, &y); + // TODO: Use controller number. + recompinput::get_right_analog(0, &x, &y); float magnitude = sqrtf(x * x + y * y); @@ -175,5 +176,5 @@ extern "C" void recomp_get_camera_inputs(uint8_t* rdram, recomp_context* ctx) { extern "C" void recomp_set_right_analog_suppressed(uint8_t* rdram, recomp_context* ctx) { s32 suppressed = _arg<0, s32>(rdram, ctx); - recomp::set_right_analog_suppressed(suppressed); + recompinput::set_right_analog_suppressed(suppressed); } diff --git a/src/game/recomp_data_api.cpp b/src/game/recomp_data_api.cpp index b07dbcf..1c1b815 100644 --- a/src/game/recomp_data_api.cpp +++ b/src/game/recomp_data_api.cpp @@ -5,7 +5,7 @@ #include "slot_map.h" #include "recomp_data.h" -#include "recomp_ui.h" +#include "recompui/recompui.h" #include "librecomp/helpers.hpp" #include "librecomp/overlays.hpp" #include "librecomp/addresses.hpp" diff --git a/src/main/launcher_animation.cpp b/src/main/launcher_animation.cpp new file mode 100644 index 0000000..6fb7194 --- /dev/null +++ b/src/main/launcher_animation.cpp @@ -0,0 +1,13 @@ +#include "zelda_launcher.h" +#include + +void zelda64::launcher_animation_setup(recompui::LauncherMenu *menu) { + auto context = recompui::get_current_context(); + recompui::Element *background_container = menu->get_background_container(); + background_container->set_background_color({ 0x00, 0x00, 0x00, 0xFF }); + + (void)context; +} + +void zelda64::launcher_animation_update(recompui::LauncherMenu *menu) { +} diff --git a/src/main/main.cpp b/src/main/main.cpp index 6c8262a..375c647 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -27,15 +27,22 @@ #undef Always #endif -#include "recomp_ui.h" -#include "recomp_input.h" +#include "recompui/recompui.h" +#include "recompui/program_config.h" +#include "recompui/renderer.h" +#include "recompui/config.h" +#include "util/file.h" +#include "recompinput/input_events.h" +#include "recompinput/recompinput.h" +#include "recompinput/profiles.h" #include "zelda_config.h" #include "zelda_sound.h" -#include "zelda_render.h" #include "zelda_support.h" #include "zelda_game.h" +#include "zelda_launcher.h" #include "recomp_data.h" #include "ovl_patches.hpp" +#include "theme.h" #include "librecomp/game.hpp" #include "librecomp/mods.hpp" #include "librecomp/helpers.hpp" @@ -56,7 +63,7 @@ #include "../../lib/rt64/src/contrib/stb/stb_image.h" -const std::string version_string = "1.2.2"; +const std::string version_string = "1.2.3"; template void exit_error(const char* str, Ts ...args) { @@ -84,9 +91,23 @@ ultramodern::gfx_callbacks_t::gfx_data_t create_gfx() { return {}; } -#if defined(__gnu_linux__) +ultramodern::input::connected_device_info_t get_connected_device_info(int controller_num) { + if (recompinput::players::is_single_player_mode() || recompinput::players::get_player_is_assigned(controller_num)) { + return ultramodern::input::connected_device_info_t{ + .connected_device = ultramodern::input::Device::Controller, + .connected_pak = ultramodern::input::Pak::RumblePak, + }; + } + + return ultramodern::input::connected_device_info_t{ + .connected_device = ultramodern::input::Device::None, + .connected_pak = ultramodern::input::Pak::None, + }; +} + #include "icon_bytes.h" +#if defined(__gnu_linux__) bool SetImageAsIcon(const char* filename, SDL_Window* window) { // Read data @@ -145,14 +166,6 @@ ultramodern::renderer::WindowHandle create_window(ultramodern::gfx_callbacks_t:: #endif window = SDL_CreateWindow("Zelda 64: Recompiled", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1600, 960, flags); -#if defined(__linux__) - SetImageAsIcon("icons/512.png",window); - if (ultramodern::renderer::get_graphics_config().wm_option == ultramodern::renderer::WindowMode::Fullscreen) { // TODO: Remove once RT64 gets native fullscreen support on Linux - SDL_SetWindowFullscreen(window,SDL_WINDOW_FULLSCREEN_DESKTOP); - } else { - SDL_SetWindowFullscreen(window,0); - } -#endif if (window == nullptr) { exit_error("Failed to create window: %s\n", SDL_GetError()); @@ -162,6 +175,10 @@ ultramodern::renderer::WindowHandle create_window(ultramodern::gfx_callbacks_t:: SDL_VERSION(&wmInfo.version); SDL_GetWindowWMInfo(window, &wmInfo); +#if defined(__linux__) + SetImageAsIcon("icons/512.png", window); +#endif + #if defined(_WIN32) return ultramodern::renderer::WindowHandle{ wmInfo.info.win.window, GetCurrentThreadId() }; #elif defined(__linux__) || defined(__ANDROID__) @@ -175,7 +192,7 @@ ultramodern::renderer::WindowHandle create_window(ultramodern::gfx_callbacks_t:: } void update_gfx(void*) { - recomp::handle_events(); + recompinput::handle_events(); } static SDL_AudioCVT audio_convert; @@ -217,7 +234,7 @@ void queue_samples(int16_t* audio_data, size_t sample_count) { // Convert the audio from 16-bit values to floats and swap the audio channels into the // swap buffer to correct for the address xor caused by endianness handling. - float cur_main_volume = zelda64::get_main_volume() / 100.0f; // Get the current main volume, normalized to 0.0-1.0. + float cur_main_volume = recompui::config::sound::get_main_volume() / 100.0f; // Get the current main volume, normalized to 0.0-1.0. for (size_t i = 0; i < sample_count; i += input_channels) { swap_buffer[i + 0 + duplicated_input_frames * input_channels] = audio_data[i + 1] * (0.5f / 32768.0f) * cur_main_volume; swap_buffer[i + 1 + duplicated_input_frames * input_channels] = audio_data[i + 0] * (0.5f / 32768.0f) * cur_main_volume; @@ -352,9 +369,11 @@ std::vector supported_games = { { .rom_hash = 0xEF18B4A9E2386169ULL, .internal_name = "ZELDA MAJORA'S MASK", + .display_name = "Majora's Mask", .game_id = u8"mm.n64.us.1.0", .mod_game_id = "mm", .save_type = recomp::SaveType::Flashram, + .thumbnail_bytes = std::span(icon_bytes), .is_enabled = false, .decompression_routine = zelda64::decompress_mm, .has_compressed_code = true, @@ -538,7 +557,7 @@ void release_preload(PreloadContext& context) { context = {}; } -#else +#elif defined(__linux__) || defined(APPLE) struct PreloadContext { @@ -546,24 +565,51 @@ struct PreloadContext { // TODO implement on other platforms bool preload_executable(PreloadContext& context) { + // Preloading isn't implemented on Linux and MacOS, but it's also unnecessary there, as the OS already preloads the executable. + // Therefore, we can just consider the executable to be preloaded. return false; } void release_preload(PreloadContext& context) { } +#else + +struct PreloadContext {}; + +bool preload_executable(PreloadContext &context) { + return false; +} + +void release_preload(PreloadContext &context) { +} + #endif void enable_texture_pack(recomp::mods::ModContext& context, const recomp::mods::ModHandle& mod) { - zelda64::renderer::enable_texture_pack(context, mod); + recompui::renderer::enable_texture_pack(context, mod); } void disable_texture_pack(recomp::mods::ModContext&, const recomp::mods::ModHandle& mod) { - zelda64::renderer::disable_texture_pack(mod); + recompui::renderer::disable_texture_pack(mod); } void reorder_texture_pack(recomp::mods::ModContext&) { - zelda64::renderer::trigger_texture_pack_update(); + recompui::renderer::trigger_texture_pack_update(); +} + +void on_launcher_init(recompui::LauncherMenu *menu) { + auto game_options_menu = menu->init_game_options_menu( + supported_games[0].game_id, + supported_games[0].mod_game_id, + supported_games[0].display_name, + supported_games[0].thumbnail_bytes, + recompui::GameOptionsMenuLayout::Right + ); + game_options_menu->add_default_options(); + + recompui::Element *menu_container = menu->get_menu_container(); + zelda64::launcher_animation_setup(menu); } #define REGISTER_FUNC(name) recomp::overlays::register_base_export(#name, name) @@ -610,9 +656,6 @@ int main(int argc, char** argv) { // Set up console output to accept UTF-8 on windows SetConsoleOutputCP(CP_UTF8); - // Initialize native file dialogs. - NFD_Init(); - // Change to a font that supports Japanese characters CONSOLE_FONT_INFOEX cfi; cfi.cbSize = sizeof cfi; @@ -638,6 +681,13 @@ int main(int argc, char** argv) { std::filesystem::current_path("/var/data", ec); #endif + // Initialize native file dialogs. + NFD_Init(); + + // Initialize program settings. + recompui::programconfig::set_program_name(zelda64::program_name); + recompui::programconfig::set_program_id(zelda64::program_id); + // Initialize SDL audio and set the output frequency. SDL_InitSubSystem(SDL_INIT_AUDIO); reset_audio(48000); @@ -648,7 +698,11 @@ int main(int argc, char** argv) { fprintf(stderr, "Failed to load controller mappings: %s\n", SDL_GetError()); } - recomp::register_config_path(zelda64::get_app_folder_path()); + // Register fonts. + recompui::register_primary_font("ChiaroNormal.otf", "Chiaro"); + recompui::register_extra_font("ChiaroBold.otf"); + + recomp::register_config_path(recompui::file::get_app_folder_path()); // Register supported games and patches for (const auto& game : supported_games) { @@ -672,18 +726,28 @@ int main(int argc, char** argv) { REGISTER_FUNC(recomp_get_analog_inverted_axes); recompui::register_ui_exports(); recomputil::register_data_api_exports(); + recomptheme::set_custom_theme(); zelda64::register_overlays(); zelda64::register_patches(); recomputil::init_extended_actor_data(); - zelda64::load_config(); + + recompinput::players::set_single_player_mode(true); + + zelda64::init_config(); + + recompui::register_launcher_init_callback(on_launcher_init); + recompui::register_launcher_update_callback(zelda64::launcher_animation_update); recomp::rsp::callbacks_t rsp_callbacks{ .get_rsp_microcode = get_rsp_microcode, }; ultramodern::renderer::callbacks_t renderer_callbacks{ - .create_render_context = zelda64::renderer::create_render_context, + .create_render_context = [](uint8_t *rdram, ultramodern::renderer::WindowHandle window_handle, bool developer_mode) { + auto presentation_mode = ultramodern::renderer::PresentationMode::PresentEarly; + return recompui::renderer::create_render_context(rdram, window_handle, presentation_mode, developer_mode); + }, }; ultramodern::gfx_callbacks_t gfx_callbacks{ @@ -699,15 +763,15 @@ int main(int argc, char** argv) { }; ultramodern::input::callbacks_t input_callbacks{ - .poll_input = recomp::poll_inputs, - .get_input = recomp::get_n64_input, - .set_rumble = recomp::set_rumble, - .get_connected_device_info = recomp::get_connected_device_info, + .poll_input = recompinput::poll_inputs, + .get_input = recompinput::profiles::get_n64_input, + .set_rumble = recompinput::set_rumble, + .get_connected_device_info = get_connected_device_info, }; ultramodern::events::callbacks_t thread_callbacks{ - .vi_callback = recomp::update_rumble, - .gfx_init_callback = recompui::update_supported_options, + .vi_callback = recompinput::update_rumble, + .gfx_init_callback = nullptr, }; ultramodern::error_handling::callbacks_t error_handling_callbacks{ @@ -731,18 +795,24 @@ int main(int argc, char** argv) { // Register the .rtz texture pack file format with the previous content type as its only allowed content type. recomp::mods::register_mod_container_type("rtz", std::vector{ texture_pack_content_type_id }, false); - recomp::start( - project_version, - {}, - rsp_callbacks, - renderer_callbacks, - audio_callbacks, - input_callbacks, - gfx_callbacks, - thread_callbacks, - error_handling_callbacks, - threads_callbacks - ); + // Configure and start. + recomp::Configuration cfg; + cfg.project_version = project_version; + cfg.window_handle = {}; + cfg.rsp_callbacks = rsp_callbacks; + cfg.renderer_callbacks = renderer_callbacks; + cfg.audio_callbacks = audio_callbacks; + cfg.input_callbacks = input_callbacks; + cfg.gfx_callbacks = gfx_callbacks; + cfg.events_callbacks = thread_callbacks; + cfg.error_handling_callbacks = error_handling_callbacks; + cfg.threads_callbacks = threads_callbacks; + cfg.message_queue_control.requeue_pi = true; + cfg.message_queue_control.requeue_ai = true; + cfg.message_queue_control.requeue_sp = true; + cfg.message_queue_control.requeue_dp = true; + cfg.message_queue_control.requeue_timer = true; + recomp::start(cfg); NFD_Quit(); diff --git a/src/main/rt64_render_context.cpp b/src/main/rt64_render_context.cpp deleted file mode 100644 index 2eff6f8..0000000 --- a/src/main/rt64_render_context.cpp +++ /dev/null @@ -1,530 +0,0 @@ -#include -#include -#include -#include - -#define HLSL_CPU -#include "hle/rt64_application.h" -#include "rt64_render_hooks.h" -#include "overloaded.h" - -#include "ultramodern/ultramodern.hpp" -#include "ultramodern/config.hpp" - -#include "zelda_render.h" -#include "recomp_ui.h" -#include "concurrentqueue.h" - -static RT64::UserConfiguration::Antialiasing device_max_msaa = RT64::UserConfiguration::Antialiasing::None; -static bool sample_positions_supported = false; -static bool high_precision_fb_enabled = false; - -static uint8_t DMEM[0x1000]; -static uint8_t IMEM[0x1000]; - -struct TexturePackEnableAction { - std::string mod_id; -}; - -struct TexturePackDisableAction { - std::string mod_id; -}; - -struct TexturePackSecondaryEnableAction { - std::string mod_id; -}; - -struct TexturePackSecondaryDisableAction { - std::string mod_id; -}; - -struct TexturePackUpdateAction { -}; - -using TexturePackAction = std::variant; - -static moodycamel::ConcurrentQueue texture_pack_action_queue; - -unsigned int MI_INTR_REG = 0; - -unsigned int DPC_START_REG = 0; -unsigned int DPC_END_REG = 0; -unsigned int DPC_CURRENT_REG = 0; -unsigned int DPC_STATUS_REG = 0; -unsigned int DPC_CLOCK_REG = 0; -unsigned int DPC_BUFBUSY_REG = 0; -unsigned int DPC_PIPEBUSY_REG = 0; -unsigned int DPC_TMEM_REG = 0; - -void dummy_check_interrupts() {} - -RT64::UserConfiguration::Antialiasing compute_max_supported_aa(RT64::RenderSampleCounts bits) { - if (bits & RT64::RenderSampleCount::Bits::COUNT_2) { - if (bits & RT64::RenderSampleCount::Bits::COUNT_4) { - if (bits & RT64::RenderSampleCount::Bits::COUNT_8) { - return RT64::UserConfiguration::Antialiasing::MSAA8X; - } - return RT64::UserConfiguration::Antialiasing::MSAA4X; - } - return RT64::UserConfiguration::Antialiasing::MSAA2X; - }; - return RT64::UserConfiguration::Antialiasing::None; -} - -RT64::UserConfiguration::AspectRatio to_rt64(ultramodern::renderer::AspectRatio option) { - switch (option) { - case ultramodern::renderer::AspectRatio::Original: - return RT64::UserConfiguration::AspectRatio::Original; - case ultramodern::renderer::AspectRatio::Expand: - return RT64::UserConfiguration::AspectRatio::Expand; - case ultramodern::renderer::AspectRatio::Manual: - return RT64::UserConfiguration::AspectRatio::Manual; - case ultramodern::renderer::AspectRatio::OptionCount: - return RT64::UserConfiguration::AspectRatio::OptionCount; - } -} - -RT64::UserConfiguration::Antialiasing to_rt64(ultramodern::renderer::Antialiasing option) { - switch (option) { - case ultramodern::renderer::Antialiasing::None: - return RT64::UserConfiguration::Antialiasing::None; - case ultramodern::renderer::Antialiasing::MSAA2X: - return RT64::UserConfiguration::Antialiasing::MSAA2X; - case ultramodern::renderer::Antialiasing::MSAA4X: - return RT64::UserConfiguration::Antialiasing::MSAA4X; - case ultramodern::renderer::Antialiasing::MSAA8X: - return RT64::UserConfiguration::Antialiasing::MSAA8X; - case ultramodern::renderer::Antialiasing::OptionCount: - return RT64::UserConfiguration::Antialiasing::OptionCount; - } -} - -RT64::UserConfiguration::RefreshRate to_rt64(ultramodern::renderer::RefreshRate option) { - switch (option) { - case ultramodern::renderer::RefreshRate::Original: - return RT64::UserConfiguration::RefreshRate::Original; - case ultramodern::renderer::RefreshRate::Display: - return RT64::UserConfiguration::RefreshRate::Display; - case ultramodern::renderer::RefreshRate::Manual: - return RT64::UserConfiguration::RefreshRate::Manual; - case ultramodern::renderer::RefreshRate::OptionCount: - return RT64::UserConfiguration::RefreshRate::OptionCount; - } -} - -RT64::UserConfiguration::InternalColorFormat to_rt64(ultramodern::renderer::HighPrecisionFramebuffer option) { - switch (option) { - case ultramodern::renderer::HighPrecisionFramebuffer::Off: - return RT64::UserConfiguration::InternalColorFormat::Standard; - case ultramodern::renderer::HighPrecisionFramebuffer::On: - return RT64::UserConfiguration::InternalColorFormat::High; - case ultramodern::renderer::HighPrecisionFramebuffer::Auto: - return RT64::UserConfiguration::InternalColorFormat::Automatic; - case ultramodern::renderer::HighPrecisionFramebuffer::OptionCount: - return RT64::UserConfiguration::InternalColorFormat::OptionCount; - } -} - -void set_application_user_config(RT64::Application* application, const ultramodern::renderer::GraphicsConfig& config) { - switch (config.res_option) { - default: - case ultramodern::renderer::Resolution::Auto: - application->userConfig.resolution = RT64::UserConfiguration::Resolution::WindowIntegerScale; - application->userConfig.downsampleMultiplier = 1; - break; - case ultramodern::renderer::Resolution::Original: - application->userConfig.resolution = RT64::UserConfiguration::Resolution::Manual; - application->userConfig.resolutionMultiplier = std::max(config.ds_option, 1); - application->userConfig.downsampleMultiplier = std::max(config.ds_option, 1); - break; - case ultramodern::renderer::Resolution::Original2x: - application->userConfig.resolution = RT64::UserConfiguration::Resolution::Manual; - application->userConfig.resolutionMultiplier = 2.0 * std::max(config.ds_option, 1); - application->userConfig.downsampleMultiplier = std::max(config.ds_option, 1); - break; - } - - switch (config.hr_option) { - default: - case ultramodern::renderer::HUDRatioMode::Original: - application->userConfig.extAspectRatio = RT64::UserConfiguration::AspectRatio::Original; - break; - case ultramodern::renderer::HUDRatioMode::Clamp16x9: - application->userConfig.extAspectRatio = RT64::UserConfiguration::AspectRatio::Manual; - application->userConfig.extAspectTarget = 16.0/9.0; - break; - case ultramodern::renderer::HUDRatioMode::Full: - application->userConfig.extAspectRatio = RT64::UserConfiguration::AspectRatio::Expand; - break; - } - - application->userConfig.aspectRatio = to_rt64(config.ar_option); - application->userConfig.antialiasing = to_rt64(config.msaa_option); - application->userConfig.refreshRate = to_rt64(config.rr_option); - application->userConfig.refreshRateTarget = config.rr_manual_value; - application->userConfig.internalColorFormat = to_rt64(config.hpfb_option); - application->userConfig.displayBuffering = RT64::UserConfiguration::DisplayBuffering::Triple; -} - -ultramodern::renderer::SetupResult map_setup_result(RT64::Application::SetupResult rt64_result) { - switch (rt64_result) { - case RT64::Application::SetupResult::Success: - return ultramodern::renderer::SetupResult::Success; - case RT64::Application::SetupResult::DynamicLibrariesNotFound: - return ultramodern::renderer::SetupResult::DynamicLibrariesNotFound; - case RT64::Application::SetupResult::InvalidGraphicsAPI: - return ultramodern::renderer::SetupResult::InvalidGraphicsAPI; - case RT64::Application::SetupResult::GraphicsAPINotFound: - return ultramodern::renderer::SetupResult::GraphicsAPINotFound; - case RT64::Application::SetupResult::GraphicsDeviceNotFound: - return ultramodern::renderer::SetupResult::GraphicsDeviceNotFound; - } - - fprintf(stderr, "Unhandled `RT64::Application::SetupResult` ?\n"); - assert(false); - std::exit(EXIT_FAILURE); -} - -ultramodern::renderer::GraphicsApi map_graphics_api(RT64::UserConfiguration::GraphicsAPI api) { - switch (api) { - case RT64::UserConfiguration::GraphicsAPI::D3D12: - return ultramodern::renderer::GraphicsApi::D3D12; - case RT64::UserConfiguration::GraphicsAPI::Vulkan: - return ultramodern::renderer::GraphicsApi::Vulkan; - case RT64::UserConfiguration::GraphicsAPI::Metal: - return ultramodern::renderer::GraphicsApi::Metal; - case RT64::UserConfiguration::GraphicsAPI::Automatic: - return ultramodern::renderer::GraphicsApi::Auto; - } - - fprintf(stderr, "Unhandled `RT64::UserConfiguration::GraphicsAPI` ?\n"); - assert(false); - std::exit(EXIT_FAILURE); -} - -zelda64::renderer::RT64Context::RT64Context(uint8_t* rdram, ultramodern::renderer::WindowHandle window_handle, bool debug) { - static unsigned char dummy_rom_header[0x40]; - recompui::set_render_hooks(); - - // Set up the RT64 application core fields. - RT64::Application::Core appCore{}; -#if defined(_WIN32) - appCore.window = window_handle.window; -#elif defined(__linux__) || defined(__ANDROID__) - appCore.window = window_handle; -#elif defined(__APPLE__) - appCore.window.window = window_handle.window; - appCore.window.view = window_handle.view; -#endif - - appCore.checkInterrupts = dummy_check_interrupts; - - appCore.HEADER = dummy_rom_header; - appCore.RDRAM = rdram; - appCore.DMEM = DMEM; - appCore.IMEM = IMEM; - - appCore.MI_INTR_REG = &MI_INTR_REG; - - appCore.DPC_START_REG = &DPC_START_REG; - appCore.DPC_END_REG = &DPC_END_REG; - appCore.DPC_CURRENT_REG = &DPC_CURRENT_REG; - appCore.DPC_STATUS_REG = &DPC_STATUS_REG; - appCore.DPC_CLOCK_REG = &DPC_CLOCK_REG; - appCore.DPC_BUFBUSY_REG = &DPC_BUFBUSY_REG; - appCore.DPC_PIPEBUSY_REG = &DPC_PIPEBUSY_REG; - appCore.DPC_TMEM_REG = &DPC_TMEM_REG; - - ultramodern::renderer::ViRegs* vi_regs = ultramodern::renderer::get_vi_regs(); - - appCore.VI_STATUS_REG = &vi_regs->VI_STATUS_REG; - appCore.VI_ORIGIN_REG = &vi_regs->VI_ORIGIN_REG; - appCore.VI_WIDTH_REG = &vi_regs->VI_WIDTH_REG; - appCore.VI_INTR_REG = &vi_regs->VI_INTR_REG; - appCore.VI_V_CURRENT_LINE_REG = &vi_regs->VI_V_CURRENT_LINE_REG; - appCore.VI_TIMING_REG = &vi_regs->VI_TIMING_REG; - appCore.VI_V_SYNC_REG = &vi_regs->VI_V_SYNC_REG; - appCore.VI_H_SYNC_REG = &vi_regs->VI_H_SYNC_REG; - appCore.VI_LEAP_REG = &vi_regs->VI_LEAP_REG; - appCore.VI_H_START_REG = &vi_regs->VI_H_START_REG; - appCore.VI_V_START_REG = &vi_regs->VI_V_START_REG; - appCore.VI_V_BURST_REG = &vi_regs->VI_V_BURST_REG; - appCore.VI_X_SCALE_REG = &vi_regs->VI_X_SCALE_REG; - appCore.VI_Y_SCALE_REG = &vi_regs->VI_Y_SCALE_REG; - - // Set up the RT64 application configuration fields. - RT64::ApplicationConfiguration appConfig; - appConfig.useConfigurationFile = false; - - // Create the RT64 application. - app = std::make_unique(appCore, appConfig); - - // Set initial user config settings based on the current settings. - auto& cur_config = ultramodern::renderer::get_graphics_config(); - set_application_user_config(app.get(), cur_config); - app->userConfig.developerMode = debug; - // Force gbi depth branches to prevent LODs from kicking in. - app->enhancementConfig.f3dex.forceBranch = true; - // Scale LODs based on the output resolution. - app->enhancementConfig.textureLOD.scale = true; - // Pick an API if the user has set an override. - switch (cur_config.api_option) { - case ultramodern::renderer::GraphicsApi::D3D12: - app->userConfig.graphicsAPI = RT64::UserConfiguration::GraphicsAPI::D3D12; - break; - case ultramodern::renderer::GraphicsApi::Vulkan: - app->userConfig.graphicsAPI = RT64::UserConfiguration::GraphicsAPI::Vulkan; - break; - case ultramodern::renderer::GraphicsApi::Metal: - app->userConfig.graphicsAPI = RT64::UserConfiguration::GraphicsAPI::Metal; - break; - case ultramodern::renderer::GraphicsApi::Auto: - app->userConfig.graphicsAPI = RT64::UserConfiguration::GraphicsAPI::Automatic; - break; - } - - // Set up the RT64 application. - uint32_t thread_id = 0; -#ifdef _WIN32 - thread_id = window_handle.thread_id; -#endif - setup_result = map_setup_result(app->setup(thread_id)); - // Get the API that RT64 chose. - chosen_api = map_graphics_api(app->chosenGraphicsAPI); - if (setup_result != ultramodern::renderer::SetupResult::Success) { - app = nullptr; - return; - } - - // Set the application's fullscreen state. - app->setFullScreen(cur_config.wm_option == ultramodern::renderer::WindowMode::Fullscreen); - - // Check if the selected device actually supports MSAA sample positions and MSAA for for the formats that will be used - // and downgrade the configuration accordingly. - if (app->device->getCapabilities().sampleLocations) { - RT64::RenderSampleCounts color_sample_counts = app->device->getSampleCountsSupported(RT64::RenderFormat::R8G8B8A8_UNORM); - RT64::RenderSampleCounts depth_sample_counts = app->device->getSampleCountsSupported(RT64::RenderFormat::D32_FLOAT); - RT64::RenderSampleCounts common_sample_counts = color_sample_counts & depth_sample_counts; - device_max_msaa = compute_max_supported_aa(common_sample_counts); - sample_positions_supported = true; - } - else { - device_max_msaa = RT64::UserConfiguration::Antialiasing::None; - sample_positions_supported = false; - } - - high_precision_fb_enabled = app->shaderLibrary->usesHDR; -} - -zelda64::renderer::RT64Context::~RT64Context() = default; - -void zelda64::renderer::RT64Context::send_dl(const OSTask* task) { - check_texture_pack_actions(); - app->state->rsp->reset(); - app->interpreter->loadUCodeGBI(task->t.ucode & 0x3FFFFFF, task->t.ucode_data & 0x3FFFFFF, true); - app->processDisplayLists(app->core.RDRAM, task->t.data_ptr & 0x3FFFFFF, 0, true); -} - -void zelda64::renderer::RT64Context::update_screen() { - app->updateScreen(); -} - -void zelda64::renderer::RT64Context::shutdown() { - if (app != nullptr) { - app->end(); - } -} - -bool zelda64::renderer::RT64Context::update_config(const ultramodern::renderer::GraphicsConfig& old_config, const ultramodern::renderer::GraphicsConfig& new_config) { - if (old_config == new_config) { - return false; - } - - if (new_config.wm_option != old_config.wm_option) { - app->setFullScreen(new_config.wm_option == ultramodern::renderer::WindowMode::Fullscreen); - } - - set_application_user_config(app.get(), new_config); - - app->updateUserConfig(true); - - if (new_config.msaa_option != old_config.msaa_option) { - app->updateMultisampling(); - } - return true; -} - -void zelda64::renderer::RT64Context::enable_instant_present() { - // Enable the present early presentation mode for minimal latency. - app->enhancementConfig.presentation.mode = RT64::EnhancementConfiguration::Presentation::Mode::PresentEarly; - - app->updateEnhancementConfig(); -} - -uint32_t zelda64::renderer::RT64Context::get_display_framerate() const { - return app->presentQueue->ext.sharedResources->swapChainRate; -} - -float zelda64::renderer::RT64Context::get_resolution_scale() const { - constexpr int ReferenceHeight = 240; - switch (app->userConfig.resolution) { - case RT64::UserConfiguration::Resolution::WindowIntegerScale: - if (app->sharedQueueResources->swapChainHeight > 0) { - return std::max(float((app->sharedQueueResources->swapChainHeight + ReferenceHeight - 1) / ReferenceHeight), 1.0f); - } - else { - return 1.0f; - } - case RT64::UserConfiguration::Resolution::Manual: - return float(app->userConfig.resolutionMultiplier); - case RT64::UserConfiguration::Resolution::Original: - default: - return 1.0f; - } -} - -void zelda64::renderer::RT64Context::check_texture_pack_actions() { - bool packs_changed = false; - TexturePackAction cur_action; - while (texture_pack_action_queue.try_dequeue(cur_action)) { - std::visit(overloaded{ - [&](TexturePackDisableAction &to_disable) { - enabled_texture_packs.erase(to_disable.mod_id); - packs_changed = true; - }, - [&](TexturePackEnableAction &to_enable) { - enabled_texture_packs.insert(to_enable.mod_id); - packs_changed = true; - }, - [&](TexturePackSecondaryDisableAction &to_override_disable) { - secondary_disabled_texture_packs.insert(to_override_disable.mod_id); - packs_changed = true; - }, - [&](TexturePackSecondaryEnableAction &to_override_enable) { - secondary_disabled_texture_packs.erase(to_override_enable.mod_id); - packs_changed = true; - }, - [&](TexturePackUpdateAction &) { - packs_changed = true; - } - }, cur_action); - } - - // If any packs were disabled, unload all packs and load all the active ones. - if (packs_changed) { - // Sort the enabled texture packs in reverse order so that earlier ones override later ones. - std::vector sorted_texture_packs{}; - sorted_texture_packs.reserve(enabled_texture_packs.size()); - for (const std::string& mod : enabled_texture_packs) { - if (!secondary_disabled_texture_packs.contains(mod)) { - sorted_texture_packs.emplace_back(mod); - } - } - - std::sort(sorted_texture_packs.begin(), sorted_texture_packs.end(), - [](const std::string& lhs, const std::string& rhs) { - return recomp::mods::get_mod_order_index(lhs) > recomp::mods::get_mod_order_index(rhs); - } - ); - - // Build the path list from the sorted mod list. - std::vector replacement_directories; - replacement_directories.reserve(enabled_texture_packs.size()); - for (const std::string &mod_id : sorted_texture_packs) { - replacement_directories.emplace_back(RT64::ReplacementDirectory(recomp::mods::get_mod_filename(mod_id))); - } - - if (!replacement_directories.empty()) { - app->textureCache->loadReplacementDirectories(replacement_directories); - } - else { - app->textureCache->clearReplacementDirectories(); - } - } -} - -RT64::UserConfiguration::Antialiasing zelda64::renderer::RT64MaxMSAA() { - return device_max_msaa; -} - -std::unique_ptr zelda64::renderer::create_render_context(uint8_t* rdram, ultramodern::renderer::WindowHandle window_handle, bool developer_mode) { - return std::make_unique(rdram, window_handle, developer_mode); -} - -bool zelda64::renderer::RT64SamplePositionsSupported() { - return sample_positions_supported; -} - -bool zelda64::renderer::RT64HighPrecisionFBEnabled() { - return high_precision_fb_enabled; -} - -void zelda64::renderer::trigger_texture_pack_update() { - texture_pack_action_queue.enqueue(TexturePackUpdateAction{}); -} - -void zelda64::renderer::enable_texture_pack(const recomp::mods::ModContext& context, const recomp::mods::ModHandle& mod) { - texture_pack_action_queue.enqueue(TexturePackEnableAction{mod.manifest.mod_id}); - - // Check for the texture pack enabled config option. - const recomp::mods::ConfigSchema& config_schema = context.get_mod_config_schema(mod.manifest.mod_id); - auto find_it = config_schema.options_by_id.find(zelda64::renderer::special_option_texture_pack_enabled); - if (find_it != config_schema.options_by_id.end()) { - const recomp::mods::ConfigOption& config_option = config_schema.options[find_it->second]; - - if (is_texture_pack_enable_config_option(config_option, false)) { - recomp::mods::ConfigValueVariant value_variant = context.get_mod_config_value(mod.manifest.mod_id, config_option.id); - uint32_t value; - if (uint32_t* value_ptr = std::get_if(&value_variant)) { - value = *value_ptr; - } - else { - value = 0; - } - - if (value) { - zelda64::renderer::secondary_enable_texture_pack(mod.manifest.mod_id); - } - else { - zelda64::renderer::secondary_disable_texture_pack(mod.manifest.mod_id); - } - } - } -} - -void zelda64::renderer::disable_texture_pack(const recomp::mods::ModHandle& mod) { - texture_pack_action_queue.enqueue(TexturePackDisableAction{mod.manifest.mod_id}); -} - -void zelda64::renderer::secondary_enable_texture_pack(const std::string& mod_id) { - texture_pack_action_queue.enqueue(TexturePackSecondaryEnableAction{mod_id}); -} - -void zelda64::renderer::secondary_disable_texture_pack(const std::string& mod_id) { - texture_pack_action_queue.enqueue(TexturePackSecondaryDisableAction{mod_id}); -} - - -// HD texture enable option. Must be an enum with two options. -// The first option is treated as disabled and the second option is treated as enabled. -bool zelda64::renderer::is_texture_pack_enable_config_option(const recomp::mods::ConfigOption& option, bool show_errors) { - if (option.id == zelda64::renderer::special_option_texture_pack_enabled) { - if (option.type != recomp::mods::ConfigOptionType::Enum) { - if (show_errors) { - recompui::message_box(("Mod has the special config option id for enabling an HD texture pack (\"" + zelda64::renderer::special_option_texture_pack_enabled + "\"), but the config option is not an enum.").c_str()); - } - return false; - } - - const recomp::mods::ConfigOptionEnum &option_enum = std::get(option.variant); - if (option_enum.options.size() != 2) { - if (show_errors) { - recompui::message_box(("Mod has the special config option id for enabling an HD texture pack (\"" + zelda64::renderer::special_option_texture_pack_enabled + "\"), but the config option doesn't have exactly 2 values.").c_str()); - } - return false; - } - - return true; - } - return false; -} diff --git a/src/main/theme.cpp b/src/main/theme.cpp new file mode 100644 index 0000000..1091b14 --- /dev/null +++ b/src/main/theme.cpp @@ -0,0 +1,106 @@ +#include "elements/ui_theme.h" +#include "theme.h" + +void recomptheme::set_custom_theme() { + using namespace recompui; + + theme::set_theme_color(theme::color::Background1, Color{8, 7, 13, 255}); + theme::set_theme_color(theme::color::Background2, Color{18, 16, 24, 255}); + theme::set_theme_color(theme::color::Background3, Color{25, 22, 34, 255}); + theme::set_theme_color(theme::color::BGOverlay, Color{190, 184, 219, 25}); // actually the overlay for the modal + theme::set_theme_color(theme::color::ModalOverlay, Color{8, 7, 13, 229}); // actually the background color of the modal itself + theme::set_theme_color(theme::color::BGShadow, Color{0, 0, 0, 89}); + theme::set_theme_color(theme::color::BGShadow2, Color{8, 7, 13, 184}); + theme::set_theme_color(theme::color::Text, Color{242, 242, 242, 255}); + theme::set_theme_color(theme::color::TextActive, Color{245, 245, 245, 255}); + theme::set_theme_color(theme::color::TextDim, Color{204, 204, 204, 255}); + theme::set_theme_color(theme::color::TextInactive, Color{255, 255, 255, 153}); + theme::set_theme_color(theme::color::TextA5, Color{242, 242, 242, 13}); + theme::set_theme_color(theme::color::TextA20, Color{242, 242, 242, 51}); + theme::set_theme_color(theme::color::TextA30, Color{242, 242, 242, 77}); + theme::set_theme_color(theme::color::TextA50, Color{242, 242, 242, 128}); + theme::set_theme_color(theme::color::TextA80, Color{242, 242, 242, 204}); + theme::set_theme_color(theme::color::Primary, Color{0xB9, 0x7D, 0xF2, 255}); + theme::set_theme_color(theme::color::PrimaryL, Color{0xDA, 0xBA, 0xF7, 255}); + theme::set_theme_color(theme::color::PrimaryD, Color{0x7A, 0x2A, 0xC6, 255}); + theme::set_theme_color(theme::color::PrimaryA5, Color{185, 125, 242, 13}); + theme::set_theme_color(theme::color::PrimaryA20, Color{185, 125, 242, 51}); + theme::set_theme_color(theme::color::PrimaryA30, Color{185, 125, 242, 77}); + theme::set_theme_color(theme::color::PrimaryA50, Color{185, 125, 242, 128}); + theme::set_theme_color(theme::color::PrimaryA80, Color{185, 125, 242, 204}); + theme::set_theme_color(theme::color::Secondary, Color{0x17, 0xD6, 0xE8, 255}); + theme::set_theme_color(theme::color::SecondaryL, Color{0xA2, 0xEF, 0xF6, 255}); + theme::set_theme_color(theme::color::SecondaryD, Color{0x25, 0xA1, 0xAD, 255}); + theme::set_theme_color(theme::color::SecondaryA5, Color{23, 214, 232, 13}); + theme::set_theme_color(theme::color::SecondaryA20, Color{23, 214, 232, 51}); + theme::set_theme_color(theme::color::SecondaryA30, Color{23, 214, 232, 77}); + theme::set_theme_color(theme::color::SecondaryA50, Color{23, 214, 232, 128}); + theme::set_theme_color(theme::color::SecondaryA80, Color{23, 214, 232, 204}); + theme::set_theme_color(theme::color::Warning, Color{0xE9, 0xCD, 0x35, 255}); + theme::set_theme_color(theme::color::WarningL, Color{0xF9, 0xE5, 0x7C, 255}); + theme::set_theme_color(theme::color::WarningD, Color{0xC5, 0xAA, 0x16, 255}); + theme::set_theme_color(theme::color::WarningA5, Color{233, 205, 53, 13}); + theme::set_theme_color(theme::color::WarningA20, Color{233, 205, 53, 51}); + theme::set_theme_color(theme::color::WarningA30, Color{233, 205, 53, 77}); + theme::set_theme_color(theme::color::WarningA50, Color{233, 205, 53, 128}); + theme::set_theme_color(theme::color::WarningA80, Color{233, 205, 53, 204}); + theme::set_theme_color(theme::color::Danger, Color{0xF8, 0x60, 0x39, 255}); + theme::set_theme_color(theme::color::DangerL, Color{0xFE, 0x86, 0x67, 255}); + theme::set_theme_color(theme::color::DangerD, Color{0xB2, 0x39, 0x19, 255}); + theme::set_theme_color(theme::color::DangerA5, Color{248, 96, 57, 13}); + theme::set_theme_color(theme::color::DangerA20, Color{248, 96, 57, 51}); + theme::set_theme_color(theme::color::DangerA30, Color{248, 96, 57, 77}); + theme::set_theme_color(theme::color::DangerA50, Color{248, 96, 57, 128}); + theme::set_theme_color(theme::color::DangerA80, Color{248, 96, 57, 204}); + theme::set_theme_color(theme::color::Success, Color{0x45, 0xD0, 0x43, 255}); + theme::set_theme_color(theme::color::SuccessL, Color{0xAA, 0xEA, 0xA9, 255}); + theme::set_theme_color(theme::color::SuccessD, Color{0x2C, 0xA7, 0x2A, 255}); + theme::set_theme_color(theme::color::SuccessA5, Color{69, 208, 67, 13}); + theme::set_theme_color(theme::color::SuccessA20, Color{69, 208, 67, 51}); + theme::set_theme_color(theme::color::SuccessA30, Color{69, 208, 67, 77}); + theme::set_theme_color(theme::color::SuccessA50, Color{69, 208, 67, 128}); + theme::set_theme_color(theme::color::SuccessA80, Color{69, 208, 67, 204}); + theme::set_theme_color(theme::color::Border, Color{255, 255, 255, 51}); + theme::set_theme_color(theme::color::BorderSoft, Color{255, 255, 255, 26}); + theme::set_theme_color(theme::color::BorderHard, Color{255, 255, 255, 77}); + theme::set_theme_color(theme::color::BorderSolid, Color{255, 255, 255, 153}); + theme::set_theme_color(theme::color::Transparent, Color{0, 0, 0, 0}); + theme::set_theme_color(theme::color::A, Color{51, 51, 255, 255}); + theme::set_theme_color(theme::color::AL, Color{178, 178, 255, 255}); + theme::set_theme_color(theme::color::AD, Color{32, 32, 172, 255}); + theme::set_theme_color(theme::color::AA5, Color{51, 51, 255, 13}); + theme::set_theme_color(theme::color::AA20, Color{51, 51, 255, 51}); + theme::set_theme_color(theme::color::AA30, Color{51, 51, 255, 77}); + theme::set_theme_color(theme::color::AA50, Color{51, 51, 255, 128}); + theme::set_theme_color(theme::color::AA80, Color{51, 51, 255, 204}); + theme::set_theme_color(theme::color::White, Color{255, 255, 255, 255}); + theme::set_theme_color(theme::color::WhiteA5, Color{255, 255, 255, 13}); + theme::set_theme_color(theme::color::WhiteA20, Color{255, 255, 255, 51}); + theme::set_theme_color(theme::color::WhiteA30, Color{255, 255, 255, 77}); + theme::set_theme_color(theme::color::WhiteA50, Color{255, 255, 255, 128}); + theme::set_theme_color(theme::color::WhiteA80, Color{255, 255, 255, 204}); + theme::set_theme_color(theme::color::BW05, Color{13, 13, 13, 255}); + theme::set_theme_color(theme::color::BW10, Color{26, 26, 26, 255}); + theme::set_theme_color(theme::color::BW25, Color{64, 64, 64, 255}); + theme::set_theme_color(theme::color::BW50, Color{128, 128, 128, 255}); + theme::set_theme_color(theme::color::BW75, Color{191, 191, 191, 255}); + theme::set_theme_color(theme::color::BW90, Color{229, 229, 229, 255}); + + theme::border::radius_sm = 8.0f; + theme::border::radius_md = 12.0f; + theme::border::radius_lg = 16.0f; + + const uint32_t header_weight = 700; + const uint32_t label_weight = 700; + const uint32_t base_weight = 400; + theme::set_typography_preset(theme::Typography::Header1, 64.0f + 4.0f, 0.07f, header_weight); + theme::set_typography_preset(theme::Typography::Header2, 48.0f + 4.0f, 0.07f, header_weight); + theme::set_typography_preset(theme::Typography::Header3, 32.0f + 4.0f, 0.07f, header_weight); + theme::set_typography_preset(theme::Typography::LabelLG, 32.0f + 4.0f, 0.11f, label_weight); + theme::set_typography_preset(theme::Typography::LabelMD, 24.0f + 4.0f, 0.11f, label_weight); + theme::set_typography_preset(theme::Typography::LabelSM, 16.0f + 4.0f, 0.14f, label_weight); + theme::set_typography_preset(theme::Typography::LabelXS, 14.0f + 4.0f, 0.14f, base_weight); + theme::set_typography_preset(theme::Typography::Body, 16.0f + 4.0f, 0, base_weight); +}; + + diff --git a/src/main/theme.h b/src/main/theme.h new file mode 100644 index 0000000..deaa66b --- /dev/null +++ b/src/main/theme.h @@ -0,0 +1,6 @@ +#pragma once + +namespace recomptheme { + // Applies custom themes. recompui::apply_color_hack MUST be called after. + void set_custom_theme(); +} // namespace recomptheme diff --git a/src/ui/core/ui_context.cpp b/src/ui/core/ui_context.cpp deleted file mode 100644 index cff69fc..0000000 --- a/src/ui/core/ui_context.cpp +++ /dev/null @@ -1,725 +0,0 @@ -#include -#include -#include -#include - -#include "slot_map.h" -#include "RmlUi/Core/StreamMemory.h" - -#include "ultramodern/error_handling.hpp" -#include "recomp_ui.h" -#include "ui_context.h" -#include "../elements/ui_element.h" - -// Hash implementations for ContextId and ResourceId. -template <> -struct std::hash { - std::size_t operator()(const recompui::ContextId& id) const { - return std::hash()(id.slot_id); - } -}; - -template <> -struct std::hash { - std::size_t operator()(const recompui::ResourceId& id) const { - return std::hash()(id.slot_id); - } -}; - -using resource_slotmap = dod::slot_map32>; - -namespace recompui { - struct Context { - std::mutex context_lock; - resource_slotmap resources; - Rml::ElementDocument* document; - Element root_element; - Element* autofocus_element = nullptr; - std::vector loose_elements; - std::unordered_set to_update; - std::vector> to_set_text; - bool captures_input = true; - bool captures_mouse = true; - Context(Rml::ElementDocument* document) : document(document), root_element(document) {} - }; -} // namespace recompui - -using context_slotmap = dod::slot_map32; - -static struct { - std::recursive_mutex all_contexts_lock; - context_slotmap all_contexts; - std::unordered_set opened_contexts; - std::unordered_map documents_to_contexts; - Rml::SharedPtr style_sheet; -} context_state; - -thread_local recompui::Context* opened_context = nullptr; -thread_local recompui::ContextId opened_context_id = recompui::ContextId::null(); - -enum class ContextErrorType { - OpenWithoutClose, - OpenInvalidContext, - CloseWithoutOpen, - CloseWrongContext, - DestroyInvalidContext, - GetContextWithoutOpen, - AddResourceWithoutOpen, - AddResourceToWrongContext, - UpdateElementWithoutContext, - UpdateElementInWrongContext, - SetTextElementWithoutContext, - SetTextElementInWrongContext, - GetResourceWithoutOpen, - GetResourceFailed, - DestroyResourceWithoutOpen, - DestroyResourceInWrongContext, - DestroyResourceNotFound, - GetDocumentInvalidContext, - GetAutofocusInvalidContext, - SetAutofocusInvalidContext, - InternalError, -}; - -enum class SlotTag : uint8_t { - Style = 0, - Element = 1, -}; - -void context_error(recompui::ContextId id, ContextErrorType type) { - (void)id; - - const char* error_message = ""; - - switch (type) { - case ContextErrorType::OpenWithoutClose: - error_message = "Attempted to open a UI context without closing another UI context"; - break; - case ContextErrorType::OpenInvalidContext: - error_message = "Attempted to open an invalid UI context"; - break; - case ContextErrorType::CloseWithoutOpen: - error_message = "Attempted to close a UI context without one being open"; - break; - case ContextErrorType::CloseWrongContext: - error_message = "Attempted to close a different UI context than the one that's open"; - break; - case ContextErrorType::DestroyInvalidContext: - error_message = "Attempted to destroy an invalid UI element"; - break; - case ContextErrorType::GetContextWithoutOpen: - error_message = "Attempted to get the current UI context with no UI context open"; - break; - case ContextErrorType::AddResourceWithoutOpen: - error_message = "Attempted to create a UI resource with no open UI context"; - break; - case ContextErrorType::AddResourceToWrongContext: - error_message = "Attempted to create a UI resource in a different UI context than the one that's open"; - break; - case ContextErrorType::UpdateElementWithoutContext: - error_message = "Attempted to update a UI element with no open UI context"; - break; - case ContextErrorType::UpdateElementInWrongContext: - error_message = "Attempted to update a UI element in a different UI context than the one that's open"; - break; - case ContextErrorType::SetTextElementWithoutContext: - error_message = "Attempted to set the text of a UI element with no open UI context"; - break; - case ContextErrorType::SetTextElementInWrongContext: - error_message = "Attempted to set the text of a UI element in a different UI context than the one that's open"; - break; - case ContextErrorType::GetResourceWithoutOpen: - error_message = "Attempted to get a UI resource with no open UI context"; - break; - case ContextErrorType::GetResourceFailed: - error_message = "Failed to get a UI resource from the current open UI context"; - break; - case ContextErrorType::DestroyResourceWithoutOpen: - error_message = "Attempted to destroy a UI resource with no open UI context"; - break; - case ContextErrorType::DestroyResourceInWrongContext: - error_message = "Attempted to destroy a UI resource in a different UI context than the one that's open"; - break; - case ContextErrorType::DestroyResourceNotFound: - error_message = "Attempted to destroy a UI resource that doesn't exist in the current context"; - break; - case ContextErrorType::GetDocumentInvalidContext: - error_message = "Attempted to get the document of an invalid UI context"; - break; - case ContextErrorType::GetAutofocusInvalidContext: - error_message = "Attempted to get the autofocus element of an invalid UI context"; - break; - case ContextErrorType::SetAutofocusInvalidContext: - error_message = "Attempted to set the autofocus element of an invalid UI context"; - break; - case ContextErrorType::InternalError: - error_message = "Internal error in UI context"; - break; - default: - error_message = "Unknown UI context error"; - break; - } - - // This assumes the error is coming from a mod, as it's unlikely that an end user will see a UI context error - // in the base recomp. - recompui::message_box((std::string{"Fatal error in mod - "} + error_message + ".").c_str()); - assert(false); - ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__); -} - -recompui::ContextId create_context_impl(Rml::ElementDocument* document) { - static Rml::ElementDocument dummy_document{""}; - bool add_to_dict = true; - - if (document == nullptr) { - document = &dummy_document; - add_to_dict = false; - } - - recompui::ContextId ret; - { - std::lock_guard lock{ context_state.all_contexts_lock }; - ret = { context_state.all_contexts.emplace(document).raw }; - - if (add_to_dict) { - context_state.documents_to_contexts.emplace(document, ret); - } - } - - return ret; -} - -void recompui::init_styling(const std::filesystem::path& rcss_file) { - std::string style{}; - { - std::ifstream style_stream{rcss_file}; - style_stream.seekg(0, std::ios::end); - style.resize(style_stream.tellg()); - style_stream.seekg(0, std::ios::beg); - - style_stream.read(style.data(), style.size()); - } - std::unique_ptr rml_stream = std::make_unique(reinterpret_cast(style.data()), style.size()); - rml_stream->SetSourceURL(rcss_file.filename().string()); - context_state.style_sheet = Rml::Factory::InstanceStyleSheetStream(rml_stream.get()); -} - -recompui::ContextId recompui::create_context(const std::filesystem::path& path) { - ContextId new_context = create_context_impl(nullptr); - - auto workingdir = std::filesystem::current_path(); - - new_context.open(); - Rml::ElementDocument* doc = recompui::load_document(path.string()); - opened_context->document = doc; - opened_context->root_element.base = doc; - new_context.close(); - - { - std::lock_guard lock{ context_state.all_contexts_lock }; - context_state.documents_to_contexts.emplace(doc, new_context); - } - - return new_context; -} - -recompui::ContextId recompui::create_context(Rml::ElementDocument* document) { - assert(document != nullptr); - - return create_context_impl(document); -} - -recompui::ContextId recompui::create_context() { - Rml::ElementDocument* doc = create_empty_document(); - doc->SetStyleSheetContainer(context_state.style_sheet); - ContextId ret = create_context_impl(doc); - Element* root = ret.get_root_element(); - // Mark the root element as not being a shim, as that's only needed for elements that were parented to Rml ones manually. - root->shim = false; - - ret.open(); - root->set_width(100.0f, Unit::Percent); - root->set_height(100.0f, Unit::Percent); - root->set_display(Display::Flex); - ret.close(); - - doc->Hide(); - - return ret; -} - -void recompui::destroy_context(ContextId id) { - bool existed = false; - - // TODO prevent deletion of a context while its mutex is in use. Second lock on the context's mutex before popping - // from the slotmap? - - // Check if the provided id exists. - { - std::lock_guard lock{ context_state.all_contexts_lock }; - // Check if the target context is currently open. - existed = context_state.all_contexts.has_key(context_slotmap::key{ id.slot_id }); - } - - - // Raise an error if the context didn't exist. - if (!existed) { - context_error(id, ContextErrorType::DestroyInvalidContext); - } - - id.open(); - id.clear_children(); - id.close(); - - // Delete the provided id. - { - std::lock_guard lock{ context_state.all_contexts_lock }; - context_state.all_contexts.erase(context_slotmap::key{ id.slot_id }); - } -} - -void recompui::destroy_all_contexts() { - recompui::hide_all_contexts(); - - std::lock_guard lock{ context_state.all_contexts_lock }; - - // TODO prevent deletion of a context while its mutex is in use. Second lock on the context's mutex before popping - // from the slotmap - - std::vector keys{}; - for (const auto& [key, item] : context_state.all_contexts.items()) { - keys.push_back(key); - } - - for (auto key : keys) { - Context* ctx = context_state.all_contexts.get(key); - - std::lock_guard context_lock{ ctx->context_lock }; - opened_context = ctx; - opened_context_id = ContextId{ key }; - - opened_context_id.clear_children(); - - opened_context = nullptr; - opened_context_id = ContextId::null(); - } - - context_state.all_contexts.reset(); - context_state.documents_to_contexts.clear(); -} - -void recompui::ContextId::open() { - // Ensure no other context is opened by this thread already. - if (opened_context_id != ContextId::null()) { - context_error(*this, ContextErrorType::OpenWithoutClose); - } - - // Get the context with this id. - Context* ctx; - { - std::lock_guard lock{ context_state.all_contexts_lock }; - ctx = context_state.all_contexts.get(context_slotmap::key{ slot_id }); - // If the context was found, add it to the opened contexts. - if (ctx != nullptr) { - context_state.opened_contexts.emplace(*this); - } - } - - // Check if the context exists. - if (ctx == nullptr) { - context_error(*this, ContextErrorType::OpenInvalidContext); - } - - // Take ownership of the target context. - ctx->context_lock.lock(); - opened_context = ctx; - opened_context_id = *this; -} - -bool recompui::ContextId::open_if_not_already() { - if (opened_context_id == *this) { - return false; - } - - open(); - return true; -} - -void recompui::ContextId::close() { - // Ensure a context is currently opened by this thread. - if (opened_context_id == ContextId::null()) { - context_error(*this, ContextErrorType::CloseWithoutOpen); - } - - // Check that the context that was specified is the same one that's currently open. - if (*this != opened_context_id) { - context_error(*this, ContextErrorType::CloseWrongContext); - } - - // Release ownership of the target context. - opened_context->context_lock.unlock(); - opened_context = nullptr; - opened_context_id = ContextId::null(); - - // Remove this context from the opened contexts. - { - std::lock_guard lock{ context_state.all_contexts_lock }; - context_state.opened_contexts.erase(*this); - } -} - -recompui::ContextId recompui::try_close_current_context() { - if (opened_context_id != ContextId::null()) { - ContextId prev_context = opened_context_id; - opened_context_id.close(); - return prev_context; - } - return ContextId::null(); -} - -void recompui::ContextId::process_updates() { - // Ensure a context is currently opened by this thread. - if (opened_context_id == ContextId::null()) { - context_error(*this, ContextErrorType::InternalError); - } - - // Check that the context that was specified is the same one that's currently open. - if (*this != opened_context_id) { - context_error(*this, ContextErrorType::InternalError); - } - - // Move the current update set into a local variable. This clears the update set - // and allows it to be used to queue updates from any element callbacks. - std::unordered_set to_update = std::move(opened_context->to_update); - - Event update_event = Event::update_event(); - - for (auto cur_resource_id : to_update) { - resource_slotmap::key cur_key{ cur_resource_id.slot_id }; - - // Ignore any resources that aren't elements. - if (cur_key.get_tag() != static_cast(SlotTag::Element)) { - // Assert to catch errors of queueing other resource types for update. - // This isn't an actual error, so there's no issue with continuing in release builds. - assert(false); - continue; - } - - // Get the resource being updaten from the context. - std::unique_ptr