diff --git a/.github/workflows/format-check.yml b/.github/workflows/format-check.yml index 2db1a09..2853921 100644 --- a/.github/workflows/format-check.yml +++ b/.github/workflows/format-check.yml @@ -1,4 +1,3 @@ -# .github/workflows/format-check.yml name: Code Format Check on: [push, pull_request] @@ -9,9 +8,22 @@ permissions: jobs: formatting: runs-on: ubuntu-latest - container: silkeh/clang:latest + steps: - uses: actions/checkout@v4 + + - name: Install LLVM 22 + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 22 + + sudo apt-get install -y clang-format-22 + + clang-format-22 --version + - name: Run clang-format run: | - find src include -name '*.cpp' -o -name '*.h' -print0 | xargs -0 clang-format --dry-run --Werror \ No newline at end of file + find src include \ + \( -name '*.cpp' -o -name '*.hpp' \) \ + -print0 | xargs -0 clang-format-22 --dry-run --Werror \ No newline at end of file diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml new file mode 100644 index 0000000..e8a8ef3 --- /dev/null +++ b/.github/workflows/release-build.yml @@ -0,0 +1,153 @@ +name: Release-Build + +on: + workflow_dispatch: + inputs: + version: + description: Version + required: false + default: dev + + push: + tags: + - "v*" + +permissions: + contents: write + +jobs: + + build: + strategy: + fail-fast: false + matrix: + include: + - os: windows-2022 + platform: windows + + + runs-on: ${{ matrix.os }} + + steps: + + - uses: actions/checkout@v4 + + + - name: Get Version + id: version + shell: bash + run: | + if [[ "${GITHUB_REF}" == refs/tags/* ]]; then + VERSION="${GITHUB_REF_NAME#v}" + else + VERSION="${{ github.event.inputs.version }}" + fi + + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + - name: Setup MSVC + if: matrix.platform == 'windows' + uses: ilammy/msvc-dev-cmd@v1 + + - name: Setup vcpkg + id: setup-vcpkg + if: matrix.platform == 'windows' + uses: lukka/run-vcpkg@v11 + with: + runVcpkgInstall: true + + - name: Configure + if: matrix.platform == 'windows' + run: > + cmake + -B build + -G Ninja + -DCMAKE_BUILD_TYPE=Release + "-DCUBED_VERSION=${{ steps.version.outputs.version }}" + "-DCMAKE_TOOLCHAIN_FILE=${{ steps.setup-vcpkg.outputs.vcpkg-root }}/scripts/buildsystems/vcpkg.cmake" + + - name: Build + run: cmake --build build --config Release --target Cubed + + - name: Copy assets + if: matrix.platform == 'windows' + shell: pwsh + run: | + if (Test-Path assets) { + Copy-Item -Path assets -Destination build/Cubed -Recurse + } else { + Write-Warning "assets folder not found, skipping copy" + } + + - name: Create Zip + if: matrix.platform == 'windows' + shell: pwsh + run: | + Compress-Archive ` + -Path build/Cubed/* ` + -DestinationPath Cubed-${{ steps.version.outputs.version }}-windows-x64.zip + + #################################################################### + # Upload + #################################################################### + + - name: Upload Windows Artifacts + if: matrix.platform == 'windows' + uses: actions/upload-artifact@v4 + with: + name: windows + path: | + *.zip + + ########################################################################## + # Release + ########################################################################## + + release: + + needs: build + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Download Artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Generate SHA256 + run: | + cd artifacts + + find . -type f | while read file + do + sha256sum "$file" + done > SHA256SUMS + + - name: Determine prerelease + id: prerelease + run: | + MAIN_BRANCH="${{ github.event.repository.default_branch }}" + git fetch origin "$MAIN_BRANCH" --depth=1 2>/dev/null || true + + if git merge-base --is-ancestor "${{ github.sha }}" "origin/$MAIN_BRANCH"; then + echo "is_prerelease=false" >> $GITHUB_OUTPUT + echo "is_prerelease=false" + else + echo "is_prerelease=true" >> $GITHUB_OUTPUT + echo "is_prerelease=true" + fi + + - name: Release + uses: softprops/action-gh-release@v2 + with: + files: | + artifacts/**/* + artifacts/SHA256SUMS + generate_release_notes: true + draft: false + prerelease: ${{ steps.prerelease.outputs.is_prerelease }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5584e1a..b3536eb 100644 --- a/.gitignore +++ b/.gitignore @@ -42,4 +42,5 @@ CMakeError.log .DS_Store assets/config.toml .venv/ -pyout/ \ No newline at end of file +pyout/ +vcpkg_installed/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f70335..20f6ff0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.14...3.24) +cmake_minimum_required(VERSION 3.21) project(Cubed LANGUAGES C CXX) set(CMAKE_CXX_STANDARD 23) @@ -6,167 +6,118 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Debug) +if(NOT CMAKE_CONFIGURATION_TYPES + AND NOT CMAKE_BUILD_TYPE) + + set(CMAKE_BUILD_TYPE Debug CACHE STRING "" FORCE) + +endif() + +if(NOT DEFINED CUBED_VERSION) + set(CUBED_VERSION "dev") endif() set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${PROJECT_NAME}) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${PROJECT_NAME}) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) -if(MSVC) - add_compile_options(/utf-8) -endif() - -find_package(OpenGL REQUIRED) - -if (UNIX AND NOT APPLE) - find_package(Freetype REQUIRED) - find_package(PkgConfig REQUIRED) - pkg_check_modules(EGL REQUIRED egl) - pkg_check_modules(Wayland REQUIRED wayland-client wayland-egl) - find_package(glfw3 REQUIRED) -endif() - -add_library(glad STATIC third_party/glad/src/glad.c) -target_include_directories(glad PUBLIC third_party/glad/include) - -include(FetchContent) - -if (WIN32) - FetchContent_Declare( - glfw - GIT_REPOSITORY https://github.com/glfw/glfw.git - GIT_TAG 3.4 - ) - - set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE) - set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE) - set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) - set(GLFW_INSTALL OFF CACHE BOOL "" FORCE) - set(GLFW_VULKAN_STATIC ON CACHE BOOL "" FORCE) - set(GLFW_STATIC ON CACHE BOOL "" FORCE) - - FetchContent_MakeAvailable(glfw) - - FetchContent_Declare( - freetype - GIT_REPOSITORY https://gitlab.freedesktop.org/freetype/freetype.git - GIT_TAG VER-2-14-3 - ) - FetchContent_MakeAvailable(freetype) - if(TARGET freetype) - add_library(Freetype::Freetype ALIAS freetype) - endif() - set(_BUILD_SHARED_LIBS_SAVED ${BUILD_SHARED_LIBS}) - set(BUILD_SHARED_LIBS ON) - - FetchContent_Declare( - onetbb - GIT_REPOSITORY https://github.com/uxlfoundation/oneTBB.git - GIT_TAG v2023.0.0 - ) - set(BUILD_TESTING OFF CACHE BOOL "Build tests" FORCE) - set(TBB_TEST OFF CACHE BOOL "Build TBB tests" FORCE) - FetchContent_MakeAvailable(onetbb) - - set(BUILD_SHARED_LIBS ${_BUILD_SHARED_LIBS_SAVED}) - unset(_BUILD_SHARED_LIBS_SAVED) - -endif() - -FetchContent_Declare( - glm - GIT_REPOSITORY https://github.com/g-truc/glm.git - GIT_TAG 1.0.3 - +list(APPEND CMAKE_MODULE_PATH + "${CMAKE_CURRENT_SOURCE_DIR}/cmake" + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules" ) -FetchContent_MakeAvailable(glm) -FetchContent_Declare( - soil2 - GIT_REPOSITORY https://github.com/SpartanJ/SOIL2.git - GIT_TAG 1.31 - -) -FetchContent_MakeAvailable(soil2) -FetchContent_Declare( - tomlplusplus - GIT_REPOSITORY https://github.com/marzer/tomlplusplus.git - GIT_TAG v3.4.0 -) -FetchContent_MakeAvailable(tomlplusplus) + +include(Dependencies) + +add_subdirectory(third_party/glad) add_subdirectory(third_party/imgui) -set(INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include) +add_executable(${PROJECT_NAME}) -add_executable(${PROJECT_NAME} - src/main.cpp - src/app.cpp - src/debug_collector.cpp - src/camera.cpp - src/config.cpp - src/dev_panel.cpp - src/gameplay/biome.cpp - src/gameplay/chunk.cpp - src/gameplay/chunk_generator.cpp - src/gameplay/player.cpp - src/gameplay/tree.cpp - src/gameplay/world.cpp - src/input.cpp - src/map_table.cpp - src/renderer.cpp - src/shader.cpp - src/texture_manager.cpp - src/tools/cubed_random.cpp - src/tools/math_tools.cpp - src/tools/shader_tools.cpp - src/tools/font.cpp - src/tools/perlin_noise.cpp - src/ui/text.cpp - src/window.cpp - src/gameplay/builders/biome_builder.cpp - src/gameplay/builders/plain_builder.cpp - src/gameplay/builders/mountain_builder.cpp - src/gameplay/builders/river_builder.cpp - src/gameplay/builders/desert_builder.cpp - src/gameplay/builders/forest_builder.cpp - src/gameplay/cave_carver.cpp - src/gameplay/cave_path.cpp - src/gameplay/builders/snowy_plain_builder.cpp - src/gameplay/river_worm.cpp - src/gameplay/river_path.cpp - src/block.cpp - src/gameplay/vertex_data.cpp - src/gameplay/builders/ocean_builder.cpp +add_subdirectory(src) + +file(GLOB_RECURSE PROTO_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/src/proto/*.proto ) -if(CMAKE_BUILD_TYPE STREQUAL "Debug") - message(STATUS "Building with AddressSanitizer enabled for target: ${PROJECT_NAME}") +protobuf_generate( + TARGET ${PROJECT_NAME} + LANGUAGE cpp + PROTOS ${PROTO_FILES} + IMPORT_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/src/proto +) - target_compile_options(${PROJECT_NAME} PRIVATE - #-fsanitize=address - #-fsanitize=thread - -fno-omit-frame-pointer - -g - ) +configure_file( + src/version.hpp.in + ${CMAKE_BINARY_DIR}/generated/version.hpp + @ONLY +) - target_link_options(${PROJECT_NAME} PRIVATE - #-fsanitize=address - #-fsanitize=thread - ) +target_compile_options(${PROJECT_NAME} + PRIVATE + $<$:-fno-omit-frame-pointer> + $<$:-g> + #$<$:-fsanitize=address> + #$<$:-fsanitize=thread> - target_compile_definitions(${PROJECT_NAME} PRIVATE DEBUG_MODE) - target_compile_definitions(${PROJECT_NAME} PRIVATE - ASSETS_PATH="${CMAKE_SOURCE_DIR}/assets/" + $<$:-Wall> + $<$:-Wextra> + $<$:-Wpedantic> + + $<$:/utf-8> + $<$:/W4> +) + +target_link_options(${PROJECT_NAME} + PRIVATE + #$<$:-fsanitize=address> + #$<$:-fsanitize=thread> +) + +target_compile_definitions(${PROJECT_NAME} + PRIVATE + ASIO_STANDALONE + ASIO_NO_DEPRECATED + $<$:DEBUG_MODE> + $<$: + WIN32_LEAN_AND_MEAN + NOMINMAX + _CRT_SECURE_NO_WARNINGS + > +) + +if(CMAKE_CONFIGURATION_TYPES) + # Visual Studio / Xcode multi-configuration generator + target_compile_definitions(${PROJECT_NAME} + PRIVATE + ASSETS_PATH="$<$:${PROJECT_SOURCE_DIR}/assets/>$<$>:./assets/>" ) else() - target_compile_definitions(${PROJECT_NAME} PRIVATE - ASSETS_PATH="./assets/" - ) + # Ninja / Makefiles single-configuration generator + if(CMAKE_BUILD_TYPE STREQUAL "Debug") + target_compile_definitions(${PROJECT_NAME} + PRIVATE + ASSETS_PATH="${PROJECT_SOURCE_DIR}/assets/" + ) + else() + target_compile_definitions(${PROJECT_NAME} + PRIVATE + ASSETS_PATH="./assets/" + ) + endif() endif() -target_include_directories(${PROJECT_NAME} PUBLIC ${INCLUDE_DIR}) + +target_include_directories(${PROJECT_NAME} + PRIVATE + ${PROJECT_SOURCE_DIR}/include + ${PROJECT_SOURCE_DIR}/third_party/asio/include + ${CMAKE_BINARY_DIR}/generated + ${PROJECT_BINARY_DIR} + ${PROJECT_BINARY_DIR}/src + +) + target_link_libraries(${PROJECT_NAME} PRIVATE @@ -179,42 +130,24 @@ target_link_libraries(${PROJECT_NAME} tomlplusplus::tomlplusplus imgui tbb + protobuf::libprotobuf + absl::log + absl::check + absl::base + absl::strings + absl::flat_hash_map + zstd::zstd + $<$:ws2_32> + ) -if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") -# target_link_libraries(${PROJECT_NAME} PRIVATE tbb) -endif() - -if (UNIX AND NOT APPLE) - target_link_libraries(${PROJECT_NAME} - PRIVATE - ${EGL_LIBRARIES} - ${Wayland_LIBRARIES} +if(WIN32) + add_custom_command( + TARGET ${PROJECT_NAME} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + $ + $ + COMMAND_EXPAND_LISTS ) - - - target_include_directories(${PROJECT_NAME} - PRIVATE - ${EGL_INCLUDE_DIRS} - ${Wayland_INCLUDE_DIRS} - ) - - - target_compile_options(${PROJECT_NAME} PRIVATE ${EGL_CFLAGS_OTHER} ${Wayland_CFLAGS_OTHER}) -endif() - -if (WIN32) - foreach(TBB_LIB IN ITEMS tbb tbbmalloc tbbmalloc_proxy) - if(TARGET ${TBB_LIB}) - add_custom_command( - TARGET ${PROJECT_NAME} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - $ - $ - COMMENT "Copying ${TBB_LIB}.dll" - ) - else() - message(STATUS "Target ${TBB_LIB} not found, skipping copy") - endif() - endforeach() endif() \ No newline at end of file diff --git a/assets/shaders/player_f_shader.glsl b/assets/shaders/player_f_shader.glsl new file mode 100644 index 0000000..68c9da0 --- /dev/null +++ b/assets/shaders/player_f_shader.glsl @@ -0,0 +1,8 @@ +#version 460 + +out vec4 color; +in vec2 tc; + +void main() { + color = vec4(0.0, 0.0, 1.0, 1.0); +} \ No newline at end of file diff --git a/assets/shaders/player_v_shader.glsl b/assets/shaders/player_v_shader.glsl new file mode 100644 index 0000000..2bf71d5 --- /dev/null +++ b/assets/shaders/player_v_shader.glsl @@ -0,0 +1,15 @@ +#version 460 + +layout (location = 0) in vec3 pos; +layout (location = 1) in vec2 texCoord; + +uniform mat4 mv_matrix; +uniform mat4 proj_matrix; + +out vec2 tc; + +void main() { + vec4 viewPos = mv_matrix * vec4(pos, 1.0); + tc = texCoord; + gl_Position = proj_matrix * viewPos; +} \ No newline at end of file diff --git a/cmake/Dependencies.cmake b/cmake/Dependencies.cmake new file mode 100644 index 0000000..44c3802 --- /dev/null +++ b/cmake/Dependencies.cmake @@ -0,0 +1,79 @@ +include(FetchContent) + +# System packages +find_package(OpenGL REQUIRED) +find_package(Protobuf REQUIRED) +find_package(absl REQUIRED) +find_package(zstd REQUIRED) +if (UNIX AND NOT APPLE) + find_package(Freetype REQUIRED) + find_package(glfw3 REQUIRED) +endif() + + +# Third-party libraries +FetchContent_Declare( + glm + GIT_REPOSITORY https://github.com/g-truc/glm.git + GIT_TAG 1.0.3 + +) +FetchContent_MakeAvailable(glm) +FetchContent_Declare( + soil2 + GIT_REPOSITORY https://github.com/SpartanJ/SOIL2.git + GIT_TAG 1.31 + +) +FetchContent_MakeAvailable(soil2) +FetchContent_Declare( + tomlplusplus + GIT_REPOSITORY https://github.com/marzer/tomlplusplus.git + GIT_TAG v3.4.0 +) +FetchContent_MakeAvailable(tomlplusplus) + + +if (WIN32) + FetchContent_Declare( + glfw + GIT_REPOSITORY https://github.com/glfw/glfw.git + GIT_TAG 3.4 + ) + + set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE) + set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE) + set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) + set(GLFW_INSTALL OFF CACHE BOOL "" FORCE) + set(GLFW_VULKAN_STATIC ON CACHE BOOL "" FORCE) + set(BUILD_SHARED_LIBS ON CACHE BOOL "" FORCE) + + FetchContent_MakeAvailable(glfw) + + FetchContent_Declare( + freetype + GIT_REPOSITORY https://gitlab.freedesktop.org/freetype/freetype.git + GIT_TAG VER-2-14-3 + ) + FetchContent_MakeAvailable(freetype) + if(TARGET freetype) + add_library(Freetype::Freetype ALIAS freetype) + endif() + set(_BUILD_SHARED_LIBS_SAVED ${BUILD_SHARED_LIBS}) + set(BUILD_SHARED_LIBS ON) + + FetchContent_Declare( + onetbb + GIT_REPOSITORY https://github.com/uxlfoundation/oneTBB.git + GIT_TAG v2023.0.0 + ) + + set(BUILD_TESTING OFF CACHE BOOL "Build tests" FORCE) + set(TBB_TEST OFF CACHE BOOL "Build TBB tests" FORCE) + FetchContent_MakeAvailable(onetbb) + + set(BUILD_SHARED_LIBS ${_BUILD_SHARED_LIBS_SAVED}) + unset(_BUILD_SHARED_LIBS_SAVED) + +endif() + diff --git a/cmake/modules/Findzstd.cmake b/cmake/modules/Findzstd.cmake new file mode 100644 index 0000000..4d8a415 --- /dev/null +++ b/cmake/modules/Findzstd.cmake @@ -0,0 +1,22 @@ +find_path(ZSTD_INCLUDE_DIRS + NAMES zstd.h + HINTS ${zstd_ROOT_DIR}/include) + +find_library(ZSTD_LIBRARIES + NAMES zstd + HINTS ${zstd_ROOT_DIR}/lib) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(zstd DEFAULT_MSG ZSTD_LIBRARIES ZSTD_INCLUDE_DIRS) + +mark_as_advanced( + ZSTD_LIBRARIES + ZSTD_INCLUDE_DIRS) + +if(ZSTD_FOUND AND NOT (TARGET zstd::zstd)) + add_library (zstd::zstd UNKNOWN IMPORTED) + set_target_properties(zstd::zstd + PROPERTIES + IMPORTED_LOCATION ${ZSTD_LIBRARIES} + INTERFACE_INCLUDE_DIRECTORIES ${ZSTD_INCLUDE_DIRS}) +endif() \ No newline at end of file diff --git a/include/Cubed/app.hpp b/include/Cubed/app.hpp index 516b74d..06eabbe 100644 --- a/include/Cubed/app.hpp +++ b/include/Cubed/app.hpp @@ -1,8 +1,10 @@ #pragma once +#include "Cubed/gameplay/client_world.hpp" +#include "Cubed/gameplay/network_server.hpp" +#include "Cubed/gameplay/server_world.hpp" #define GLFW_INCLUDE_NONE #include "Cubed/camera.hpp" #include "Cubed/dev_panel.hpp" -#include "Cubed/gameplay/world.hpp" #include "Cubed/renderer.hpp" #include "Cubed/texture_manager.hpp" #include "Cubed/window.hpp" @@ -10,6 +12,13 @@ namespace Cubed { class App { public: + struct Argument { + bool is_client = false; + int port = 25530; + std::string ip{"127.0.0.1"}; + std::string player{"Unknown"}; + }; + App(); ~App(); static void cursor_position_callback(GLFWwindow* window, double xpos, @@ -36,14 +45,20 @@ public: Renderer& renderer(); TextureManager& texture_manager(); Window& window(); - World& world(); + ClientWorld& client_world(); + ServerWorld& server_world(); + const Argument& argument() const; private: Camera m_camera; TextureManager m_texture_manager; - World m_world; + NetworkServer m_server; + std::shared_ptr m_client; + ClientWorld m_client_world; + DevPanel m_dev_panel{*this}; - Renderer m_renderer{m_camera, m_world, m_texture_manager, m_dev_panel}; + Renderer m_renderer{m_camera, m_client_world, m_texture_manager, + m_dev_panel}; Window m_window{m_renderer}; @@ -53,9 +68,10 @@ private: inline static double fps_time_count = 0.0f; inline static int frame_count = 0; inline static int fps = 0; - - void init(); - + Argument m_argument; + void init(int argc, char** argv); + void handle_argument(int argc, char** argv); + void handle_toml(); auto init_camera(); auto init_texture(); auto init_world(); diff --git a/include/Cubed/camera.hpp b/include/Cubed/camera.hpp index d831a13..89a087b 100644 --- a/include/Cubed/camera.hpp +++ b/include/Cubed/camera.hpp @@ -8,12 +8,12 @@ namespace Cubed { -class Player; +class ClientPlayer; class Camera { private: bool m_firse_mouse = true; - Player* m_player; + ClientPlayer* m_player; float m_last_mouse_x, m_last_mouse_y; glm::vec3 m_camera_pos; bool m_under_water = false; @@ -23,7 +23,7 @@ public: void update_move_camera(); - void camera_init(Player* player); + void camera_init(ClientPlayer* player); void hot_reload(); void reset_camera(); void update_cursor_position_camera(double xpos, double ypos); diff --git a/include/Cubed/config.hpp b/include/Cubed/config.hpp index 48f92e3..113dc2b 100644 --- a/include/Cubed/config.hpp +++ b/include/Cubed/config.hpp @@ -1,17 +1,9 @@ #pragma once #include "Cubed/tools/cubed_assert.hpp" - -#include +#include "Cubed/tools/toml.utils.hpp" namespace Cubed { -template -concept TomlValueType = - std::same_as || std::same_as || std::same_as || - std::same_as || std::same_as || - std::same_as || std::same_as || - std::same_as; - class Config { public: Config(); @@ -24,7 +16,7 @@ public: void load_or_create_config(); void save_to_file(); - template T get(std::string_view key) const { + template T get(std::string_view key) const { size_t cur = 0; auto pos = key.find('.'); const toml::table* table = &m_tbl; @@ -61,7 +53,7 @@ public: } } template void set(std::string_view key, T&& val) { - if constexpr (!TomlValueType>) { + if constexpr (!TOML::TomlValueType>) { static_assert(false, "Type Not Support"); } size_t cur = 0; diff --git a/include/Cubed/constants.hpp b/include/Cubed/constants.hpp index f1e0f23..74ef7d5 100644 --- a/include/Cubed/constants.hpp +++ b/include/Cubed/constants.hpp @@ -1,5 +1,4 @@ #pragma once -#include "Cubed/gameplay/chunk_pos.hpp" #include namespace Cubed { @@ -26,13 +25,10 @@ constexpr float DEFAULT_G = 22.5f; constexpr int SIZE_X = CHUNK_SIZE; constexpr int SIZE_Y = WORLD_SIZE_Y; constexpr int SIZE_Z = CHUNK_SIZE; -constexpr int RESERVED_THREADS = 3; +constexpr int RESERVED_THREADS = 5; constexpr float DEFAULT_CAVE_PROBABILITY = 0.035f; -constexpr ChunkPos CHUNK_DIR[]{{1, 0}, {-1, 0}, {0, 1}, {0, -1}, - {1, 1}, {-1, 1}, {1, -1}, {-1, -1}}; - using HeightMapArray = std::array, CHUNK_SIZE>; } // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/dev_panel.hpp b/include/Cubed/dev_panel.hpp index 6f949fd..f020657 100644 --- a/include/Cubed/dev_panel.hpp +++ b/include/Cubed/dev_panel.hpp @@ -5,7 +5,7 @@ namespace Cubed { class App; -class Player; +class ClientPlayer; class DevPanel { struct ConfigView { float fov = 70.0f; @@ -26,9 +26,6 @@ class DevPanel { int gait = 0; float pos[3] = {0.0f, 0.0f, 0.0f}; }; - struct TextEditing { - bool perlin_seed = false; - }; public: DevPanel(App& app); @@ -38,9 +35,8 @@ public: private: App& m_app; ConfigView m_config; - Player* m_player; + ClientPlayer* m_player; PlayerProfile m_player_profile; - TextEditing m_text_editing; bool m_need_save_config = false; bool m_gen_thread_running = true; int m_theme = 0; @@ -58,6 +54,8 @@ private: void show_chunk_table_bar(); void show_settings_tab_item(); void show_world_tab_item(); + void show_server_world_table_bar(); + void show_client_world_table_bar(); void show_player_tab_item(); void show_items_tab_item(); void show_shader_tab_item(); diff --git a/include/Cubed/gameplay/biome.hpp b/include/Cubed/gameplay/biome.hpp index f4af4ed..da0a499 100644 --- a/include/Cubed/gameplay/biome.hpp +++ b/include/Cubed/gameplay/biome.hpp @@ -1,6 +1,9 @@ #pragma once +#include "Cubed/tools/cubed_assert.hpp" + #include #include +#include #include namespace Cubed { @@ -77,4 +80,32 @@ DesertParams& desert_params(); MountainParams& mountain_params(); RiverParams& river_params(); +inline BiomeType get_biome_from_id(int id) { + using enum BiomeType; + auto to = std::to_underlying; + if (id == to(PLAIN)) { + return PLAIN; + } + if (id == to(FOREST)) { + return FOREST; + } + if (id == to(DESERT)) { + return DESERT; + } + if (id == to(MOUNTAIN)) { + return MOUNTAIN; + } + if (id == to(RIVER)) { + return RIVER; + } + if (id == to(SNOWY_PLAIN)) { + return SNOWY_PLAIN; + } + if (id == to(OCEAN)) { + return OCEAN; + } + ASSERT_MSG(false, "Unknown Biome Id"); + throw std::invalid_argument("Unknown Biome Id"); +} + } // namespace Cubed diff --git a/include/Cubed/gameplay/block.hpp b/include/Cubed/gameplay/block.hpp index d859711..5912211 100644 --- a/include/Cubed/gameplay/block.hpp +++ b/include/Cubed/gameplay/block.hpp @@ -2,12 +2,15 @@ #include #include +#include #include #include namespace Cubed { using BlockType = uint8_t; +using OptionalBlockVectorArray = + std::array>, 4>; struct BlockTexture { std::string name; diff --git a/include/Cubed/gameplay/cave_carver.hpp b/include/Cubed/gameplay/cave_carver.hpp index b5bc764..5120c4a 100644 --- a/include/Cubed/gameplay/cave_carver.hpp +++ b/include/Cubed/gameplay/cave_carver.hpp @@ -1,5 +1,6 @@ #pragma once #include "Cubed/constants.hpp" +#include "Cubed/gameplay/chunk_pos.hpp" #include "Cubed/gameplay/path.hpp" #include diff --git a/include/Cubed/gameplay/chunk_generator.hpp b/include/Cubed/gameplay/chunk_generator.hpp index 2cf5d04..2fce476 100644 --- a/include/Cubed/gameplay/chunk_generator.hpp +++ b/include/Cubed/gameplay/chunk_generator.hpp @@ -11,11 +11,11 @@ #include namespace Cubed { -class Chunk; +class ServerChunk; class ChunkGenerator { public: - ChunkGenerator(Chunk& chunk); + ChunkGenerator(ServerChunk& chunk); static void init(); static void reload(); @@ -26,7 +26,7 @@ public: void assign_chunk_biome(); // Adjust Biome void resolve_biome_adjacency_conflict( - const std::array& adj_chunks); + const std::array& adj_chunks); // Generate Heightmap void generate_heightmap(); // Adjust Height @@ -42,7 +42,7 @@ public: // Generate Structure void generate_vegetation(); BiomeType get_biome_at(float world_x, float world_z); - Chunk& chunk(); + ServerChunk& chunk(); Random& random(); const std::array& neighbor_biome() const; void ocean_build(); @@ -53,7 +53,7 @@ private: static inline std::atomic is_init{false}; static inline unsigned m_generator_seed{0}; static inline std::atomic is_seed_change{false}; - Chunk& m_chunk; + ServerChunk& m_chunk; Random m_random; std::unique_ptr m_biome_builder{nullptr}; bool is_cur_chunk_ins = false; diff --git a/include/Cubed/gameplay/chunk_pos.hpp b/include/Cubed/gameplay/chunk_pos.hpp index b89026f..6d5f549 100644 --- a/include/Cubed/gameplay/chunk_pos.hpp +++ b/include/Cubed/gameplay/chunk_pos.hpp @@ -1,5 +1,7 @@ #pragma once +#include "Cubed/constants.hpp" + #include namespace Cubed { @@ -34,5 +36,29 @@ struct ChunkPos { return *this; }; }; +constexpr ChunkPos CHUNK_DIR[]{{1, 0}, {-1, 0}, {0, 1}, {0, -1}, + {1, 1}, {-1, 1}, {1, -1}, {-1, -1}}; +inline ChunkPos get_chunk_pos(int world_x, int world_z) { + int chunk_x, chunk_z; + if (world_x < 0) { + chunk_x = (world_x + 1) / CHUNK_SIZE - 1; + } + if (world_x >= 0) { + chunk_x = world_x / CHUNK_SIZE; + } + if (world_z < 0) { + chunk_z = (world_z + 1) / CHUNK_SIZE - 1; + } + if (world_z >= 0) { + chunk_z = world_z / CHUNK_SIZE; + } + return {chunk_x, chunk_z}; +} + +inline float distance2(const ChunkPos& a, const ChunkPos& b) { + float dx = static_cast(a.x) - b.x; + float dz = static_cast(a.z) - b.z; + return dx * dx + dz * dz; +} } // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/chunk.hpp b/include/Cubed/gameplay/client_chunk.hpp similarity index 60% rename from include/Cubed/gameplay/chunk.hpp rename to include/Cubed/gameplay/client_chunk.hpp index c686210..bc283cf 100644 --- a/include/Cubed/gameplay/chunk.hpp +++ b/include/Cubed/gameplay/client_chunk.hpp @@ -1,94 +1,42 @@ #pragma once - +#include "Cubed/constants.hpp" #include "Cubed/gameplay/biome.hpp" #include "Cubed/gameplay/block.hpp" -#include "Cubed/gameplay/chunk_generator.hpp" #include "Cubed/gameplay/chunk_pos.hpp" #include "Cubed/gameplay/vertex_data.hpp" +#include "world/chunk_data.pb.h" #include +#include +#include #include namespace Cubed { - -struct ChunkInfo { - ChunkPos pos{0, 0}; - unsigned seed{0}; - BiomeType biome{BiomeType::NONE}; - unsigned first_random{0}; - bool has_cave_start{false}; - bool has_cave{false}; +class ClientWorld; +struct ChunkRenderSnapshot { + GLuint normal_vao; + size_t normal_vertices_count; + GLuint cross_vao; + size_t cross_vertices_count; + GLuint normal_discard_vao; + size_t normal_discard_vertices_count; + GLuint normal_blend_vao; + size_t normal_blend_vertices_count; + GLuint water_vao; + size_t water_vertices_count; + glm::vec3 center; + glm::vec3 half_extents; }; - -class World; -// if want to use, do init_chunk(), gen_vertex_data() and -class Chunk { -private: - using OptionalBlockVectorArray = - std::array>, 4>; - - struct FaceKey { - BlockType block_id = 0; - int face = -1; // 0-5, used to index NORMALS/TANGENTS/TEX_COORDS - - bool valid() const { return block_id != 0; } - bool operator==(const FaceKey& o) const { - return block_id == o.block_id && face == o.face; - } - bool operator!=(const FaceKey& o) const { return !(*this == o); } - }; - - static constexpr int SIZE_X = CHUNK_SIZE; - static constexpr int SIZE_Y = WORLD_SIZE_Y; - static constexpr int SIZE_Z = CHUNK_SIZE; - static constexpr int VERTEX_DATA_SUM = 5; - std::atomic m_dirty{false}; - std::atomic m_need_upload{true}; - std::atomic m_is_on_gen_vertex_data{false}; - std::atomic m_gening{false}; - std::atomic m_temp_chunk{false}; - - bool m_has_cave{false}; - - std::atomic m_biome = BiomeType::PLAIN; - std::mutex m_vertexs_data_mutex; - - std::unique_ptr m_generator; - - ChunkPos m_chunk_pos; - World& m_world; - HeightMapArray m_heightmap; - // the index is a array of block id - std::vector m_blocks; - - /* - 0 - normal - 1 - cross_plane - 2 - normal_discard - 3 - transparent and blend - 4 - water - */ - std::vector m_vertex_data; - float frequency = 0.01f; - float height = 80; - unsigned m_seed = 0; - - BiomeConditions m_conditions; - ChunkInfo m_info; - void clear_dirty(); - void gen_vertices(const OptionalBlockVectorArray& neighbor_block); - void gen_cross_plane_vertices(int world_x, int world_y, int world_z, - BlockType id); - void emit_quad(int axis, int face_dir, int layer, int i, int j, int w, - int h, int u_axis, int v_axis, FaceKey key); - +class ClientChunk { public: - Chunk(World& world, ChunkPos chunk_pos, bool temp_chunk = false); - ~Chunk(); - Chunk(const Chunk&) = delete; - Chunk& operator=(const Chunk&) = delete; - Chunk(Chunk&&) noexcept; - Chunk& operator=(Chunk&&) noexcept; + ClientChunk(ClientWorld& world); + ~ClientChunk(); + ClientChunk(const ClientChunk&) = delete; + ClientChunk& operator=(const ClientChunk&) = delete; + ClientChunk(ClientChunk&&) noexcept; + ClientChunk& operator=(ClientChunk&&) noexcept; + static int index(int x, int y, int z); + static int index(const glm::vec3& pos); static std::tuple world_to_block(int world_x, int world_y, int world_z, int chunk_x, int chunk_z); @@ -101,33 +49,9 @@ public: BiomeType get_biome() const; ChunkPos get_chunk_pos() const; const std::vector& get_chunk_blocks() const; - HeightMapArray get_heightmap() const; - static int index(int x, int y, int z); - static int index(const glm::vec3& pos); - // Init Chunk - // Determine biome from temperature and humidity noise - void gen_phase_one(); - // Resolve biome adjacency conflicts with neighbor chunks - void gen_phase_two(const std::array& adj_chunks); - // Generate heightmap using biome-specific noise - void gen_phase_three(); - // Blend heightmap with neighbors for smooth transitions - void gen_phase_four( - const std::array, 8>& neighbor_heightmap, - const std::array& neighbor_biome); - // Generate terrain blocks from heightmap and biome - void gen_phase_five(); - // Blend surface blocks at chunk borders with neighbors - void gen_phase_six(const std::array>, - 4>& neighbor_block); - // Generate biome-specific vegetation/structures - void gen_phase_seven(); - // void gen_vertex_data(); - // 0 : (1, 0) - // 1 : (-1, 0) - // 2 : (0, 1) - // 3 : (0, -1) + void receive_chunk(const ChunkDataRsp& data); void gen_vertex_data(const OptionalBlockVectorArray& neighbor_block); + // Can only be called on the render thread void upload_to_gpu(); GLuint get_normal_vao() const; @@ -152,20 +76,58 @@ public: void need_upload(); void set_chunk_block(int index, unsigned id); - // ensure thread safe! - void gen_chunk(); - bool is_temp_chunk() const; ChunkPos chunk_pos() const; BiomeType biome() const; void biome(BiomeType b); - HeightMapArray& heightmap(); std::vector& blocks(); - World& world(); + ClientWorld& world(); unsigned seed() const; - BiomeConditions& conditions(); - ChunkInfo get_info() const; - bool& has_cave(); -}; + const ChunkRenderSnapshot* get_render_snapshot() const; +private: + struct FaceKey { + BlockType block_id = 0; + int face = -1; // 0-5, used to index NORMALS/TANGENTS/TEX_COORDS + + bool valid() const { return block_id != 0; } + bool operator==(const FaceKey& o) const { + return block_id == o.block_id && face == o.face; + } + bool operator!=(const FaceKey& o) const { return !(*this == o); } + }; + + static constexpr int SIZE_X = CHUNK_SIZE; + static constexpr int SIZE_Y = WORLD_SIZE_Y; + static constexpr int SIZE_Z = CHUNK_SIZE; + static constexpr int BLOCK_SIZE = SIZE_X * SIZE_Y * SIZE_Z; + static constexpr int VERTEX_DATA_SUM = 5; + std::atomic m_dirty{false}; + std::atomic m_need_upload{true}; + std::atomic m_is_on_gen_vertex_data{false}; + std::atomic m_biome = BiomeType::PLAIN; + std::mutex m_vertexs_data_mutex; + ChunkPos m_chunk_pos; + ClientWorld& m_world; + // the index is a array of block id + std::vector m_blocks; + /* + 0 - normal + 1 - cross_plane + 2 - normal_discard + 3 - transparent and blend + 4 - water + */ + std::vector m_vertex_data; + + ChunkRenderSnapshot m_render_snapshot; + + unsigned m_seed = 0; + void clear_dirty(); + void gen_vertices(const OptionalBlockVectorArray& neighbor_block); + void gen_cross_plane_vertices(int world_x, int world_y, int world_z, + BlockType id); + void emit_quad(int axis, int face_dir, int layer, int i, int j, int w, + int h, int u_axis, int v_axis, FaceKey key); +}; } // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/player.hpp b/include/Cubed/gameplay/client_player.hpp similarity index 68% rename from include/Cubed/gameplay/player.hpp rename to include/Cubed/gameplay/client_player.hpp index 2dba631..371505a 100644 --- a/include/Cubed/gameplay/player.hpp +++ b/include/Cubed/gameplay/client_player.hpp @@ -6,17 +6,61 @@ #include "Cubed/gameplay/game_mode.hpp" #include "Cubed/input.hpp" +#include #include #include -#include - +#include namespace Cubed { - enum class Gait { WALK = 0, RUN }; +class ClientWorld; +class ClientPlayer { +public: + using ChunkPosSet = absl::flat_hash_set; + ClientPlayer(ClientWorld& world); + ~ClientPlayer(); -class World; + void update_chunk_set(const ChunkPosSet& set); + const ChunkPosSet& get_chunk_pos_set() const; + ChunkPosSet& get_chunk_pos_set(); + + static AABB get_aabb(const glm::vec3& pos); + const glm::vec3& get_front() const; + const Gait& get_gait() const; + const std::optional& get_look_block_pos() const; + // thread safe + glm::vec3 get_player_pos() const; + const MoveState& get_move_state() const; + + void change_mode(GameMode mode); + void hot_reload(); + void set_player_pos(const glm::vec3& pos); + void set_place_block(unsigned id); + void update(float delta_time); + void update_front_vec(float offset_x, float offset_y); + void update_player_move_state(int key, int action); + void update_scroll(double yoffset); + + float& max_walk_speed(); + float& max_run_speed(); + float& max_speed(); + float& acceleration(); + float& deceleration(); + float& g(); + float& fly_y_speed(); + + unsigned place_block() const; + + Gait& gait(); + GameMode& game_mode(); + + const ClientWorld& get_world() const; + + void set_uuid(std::string_view uuid); + const std::string& get_uuid() const; + const std::string& get_name() const; + + void init(std::string_view name); -class Player { private: using enum GameMode; float m_max_walk_speed = DEFAULT_MAX_WALK_SPEED; @@ -24,7 +68,6 @@ private: float m_acceleration = DEFAULT_ACCELERATION; float m_deceleration = DEFAULT_DECELERATION; float m_g = DEFAULT_G; - constexpr static float MAX_SPACE_ON_TIME = 0.3f; float m_yaw = 0.0f; @@ -50,63 +93,33 @@ private: // player is tow block tall, the pos is the lower pos glm::vec3 m_player_pos{0.0f, 255.0f, 0.0f}; - ChunkPos m_player_chunk_pos{0, 0}; + ChunkPos m_last_chunk_pos{0, 0}; glm::vec3 m_front{0, 0, -1}; glm::vec3 m_right{0, 0, 0}; - glm::vec3 m_size{0.6f, 1.8f, 0.6f}; + static constexpr glm::vec3 M_SIZE{0.6f, 1.8f, 0.6f}; Gait m_gait = Gait::WALK; MoveState m_move_state{}; GameMode m_game_mode = CREATIVE; std::optional m_look_block = std::nullopt; std::string m_name{}; - World& m_world; + std::string m_uuid; + ClientWorld& m_world; + mutable std::shared_mutex m_player_pos_mutex; + mutable std::shared_mutex m_chunk_pos_mutex; + ChunkPosSet m_player_chunk_pos_set; bool ray_cast(const glm::vec3& start, const glm::vec3& dir, glm::ivec3& block_pos, glm::vec3& normal, float distance = 4.0f); - void check_player_chunk_transition(); void update_direction(); void update_lookup_block(); void update_move(float delta_time); - void update_x_move(); - void update_y_move(); - void update_z_move(); - -public: - Player(World& world, const std::string& name); - ~Player(); - AABB get_aabb() const; - const glm::vec3& get_front() const; - const Gait& get_gait() const; - const std::optional& get_look_block_pos() const; - const glm::vec3& get_player_pos() const; - const MoveState& get_move_state() const; - - void change_mode(GameMode mode); - void hot_reload(); - void set_player_pos(const glm::vec3& pos); - void set_place_block(unsigned id); - void update(float delta_time); - void update_front_vec(float offset_x, float offset_y); - void update_player_move_state(int key, int action); - void update_scroll(double yoffset); - - float& max_walk_speed(); - float& max_run_speed(); - float& max_speed(); - float& acceleration(); - float& deceleration(); - float& g(); - float& fly_y_speed(); - - unsigned place_block() const; - - Gait& gait(); - GameMode& game_mode(); - const World& get_world() const; + void update_x_move(glm::vec3& player_pos); + void update_y_move(glm::vec3& player_pos); + void update_z_move(glm::vec3& player_pos); + void update_player_chunk(); }; - } // namespace Cubed diff --git a/include/Cubed/gameplay/client_world.hpp b/include/Cubed/gameplay/client_world.hpp new file mode 100644 index 0000000..e236a70 --- /dev/null +++ b/include/Cubed/gameplay/client_world.hpp @@ -0,0 +1,138 @@ +#pragma once +#include "Cubed/gameplay/block.hpp" +#include "Cubed/gameplay/chunk_pos.hpp" +#include "Cubed/gameplay/client_chunk.hpp" +#include "Cubed/gameplay/client_player.hpp" +#include "Cubed/gameplay/game_time.hpp" +#include "Cubed/gameplay/network_client.hpp" +#include "Cubed/tools/priority_thread_pool.hpp" + +#include +#include +#include +#include +#include +namespace Cubed { + +struct RemotePlayerInfo { + std::string name; + glm::vec3 render_pos; + glm::vec3 target_pos; +}; + +struct RemotePlayerRenderData { + std::string name; + glm::vec3 render_pos; +}; + +class ClientWorld { +public: + ClientWorld(); + ~ClientWorld(); + void init(std::string_view player_name, + std::shared_ptr client); + void update(float delta_time); + const std::optional& get_look_block_pos() const; + ClientPlayer& get_player(); + int get_block(const glm::ivec3& block_pos) const; + bool is_solid(const glm::ivec3& block_pos) const; + bool can_pass_block(const glm::ivec3& block_pos) const; + BlockType get_block_tpye(const glm::ivec3& block_pos) const; + + void rebuild_world(); + + void push_delete_vbo(GLuint vbo); + void push_delete_vao(GLuint vao); + // void hot_reload(); + + // void rebuild_world(); + void report_block_change(const glm::ivec3& pos, unsigned id) const; + void receive_block_change(const BlockChangeRsp& rsp); + void receive_time(const UpdateTime& rsp); + + void receive_remote_player(const PlayerInfoRsp& rsp); + void receive_player_logout(const LogoutRsp& rsp); + int rendering_distance() const; + void rendering_distance(int rendering_distance); + int get_chunk_task_id() const; + void start_client_thread(std::string_view uuid); + void stop_client_thread(); + + void start_thread_pool(); + void stop_thread_pool(); + void change_pool_threads(int threads); + void hot_reload(); + void request_chunk(); + std::vector& planes(); + const std::vector& render_snapshots() const; + const std::vector& render_player_data() const; + glm::vec3 sunlight_dir() const; + void receive_chunk(std::vector data, PacketHeader header); + void request_exit(); + bool is_receive_exit(); + int chunk_size() const; + static AABB get_block_aabb(const glm::ivec3& pos); + template + void register_timer(std::string_view id, TickType threshold, Fn&& f) { + m_timers.emplace(std::piecewise_construct, + std::forward_as_tuple(std::string(id)), + std::forward_as_tuple(threshold, std::forward(f))); + } + +private: + enum class ChunkLoadStyle { RANDOM, CENTER }; + using ChunkHashMap = + tbb::concurrent_hash_map, + ChunkPos::TBBHash>; + using ChunkPosSet = absl::flat_hash_set; + using ChunkPosVector = std::vector; + using OtherPlayerHashMap = + std::unordered_map; + using chunk_acc = ChunkHashMap::accessor; + using chunk_cacc = ChunkHashMap::const_accessor; + static constexpr int WORLD_EXIT_TIMEOUT = 200; + static constexpr int MAX_UPLOAD_CHUNK_SUM = 16; + ClientPlayer m_player; + OtherPlayerHashMap m_other_players; + ChunkHashMap m_chunks; + std::vector m_planes; + std::jthread m_client_thread; + + std::mutex m_delete_vbo_mutex; + std::mutex m_delete_vao_mutex; + mutable std::shared_mutex m_other_players_mutex; + + tbb::concurrent_queue> m_pending_upload_queue; + tbb::concurrent_queue m_dirty_chunk_queue; + + std::vector m_pending_delete_vbo; + std::vector m_pending_delete_vao; + + std::deque m_dirty_queue; + std::vector m_render_snapshots; + std::vector m_render_player_data; + tbb::concurrent_unordered_map m_timers; + std::atomic m_game_running{false}; + std::atomic m_receive_exit{false}; + std::atomic m_rendering_distance{24}; + std::atomic m_game_ticks{0}; + std::atomic m_day_tick{6000}; + std::atomic m_requesting_chunk{false}; + std::atomic m_is_rebuilding{false}; + std::atomic m_chunk_task_id{0}; + std::shared_ptr m_client; + ChunkLoadStyle m_chunk_load_style{ChunkLoadStyle::CENTER}; + + std::atomic> m_thread_pool; + + void client_run(std::stop_token token); + + void set_player_pos(); + + void report_player_pos(); + + void set_block(const glm::ivec3& pos, unsigned id); + + void update_chunk(const ChunkPosSet& old, const ChunkPosSet& now); +}; +} // namespace Cubed diff --git a/include/Cubed/gameplay/game_time.hpp b/include/Cubed/gameplay/game_time.hpp index 5cb08b7..64aab77 100644 --- a/include/Cubed/gameplay/game_time.hpp +++ b/include/Cubed/gameplay/game_time.hpp @@ -1,10 +1,40 @@ #pragma once // Prevent unsigned underflow issues in subtraction +#include "Cubed/tools/cubed_assert.hpp" + +#include +#include +namespace Cubed { using TickType = long long; constexpr int DEFAULT_PER_TICK_TIME = 50; constexpr TickType DAY_TIME = 24000; -constexpr TickType PER_HOUR = 1000; \ No newline at end of file +constexpr TickType PER_HOUR = 1000; + +class Timer { +public: + template + Timer(TickType threshold, Fn&& f) + : m_fn(std::forward(f)), m_threshold(threshold) { + ASSERT_MSG(threshold > 0, "Threshold Must Rreater Than 0"); + } + bool update() { + if (++m_current >= m_threshold) { + m_current = 0; + m_fn(); + return true; + } + return false; + } + void reset() { m_current = 0; } + +private: + std::function m_fn; + TickType m_threshold; + TickType m_current = 0; +}; + +} // namespace Cubed diff --git a/include/Cubed/gameplay/network_client.hpp b/include/Cubed/gameplay/network_client.hpp new file mode 100644 index 0000000..6f18dc9 --- /dev/null +++ b/include/Cubed/gameplay/network_client.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include "Cubed/gameplay/packet.hpp" + +#include +#include +#include +#include +namespace Cubed { +using asio::ip::tcp; +class ClientWorld; +class NetworkClient : public std::enable_shared_from_this { +public: + NetworkClient(ClientWorld& world); + ~NetworkClient(); + void close(); + void stop(); + void send(Packet packet, int priority = 10); + void start(std::string ip, int port = 25530); + bool is_connected() const; + bool is_connect_error() const; + +private: + struct Task { + int priority = 10; + std::uint64_t sequence = 0; + Packet packet; + Task(int p, std::uint64_t seq, Packet pac) + : priority(p), sequence(seq), packet(std::move(pac)) {} + }; + + struct TaskCompare { + bool operator()(const Task& a, const Task& b) const { + + if (a.priority != b.priority) { + return a.priority > b.priority; + } + + return a.sequence > b.sequence; + } + }; + + asio::io_context m_io; + + std::thread m_net_thread; + static constexpr uint32_t MAX_PACKET_SIZE = 4 * 1024 * 1024; + tcp::socket m_socket; + std::vector m_read_buffer; + + std::priority_queue, TaskCompare> m_write_queue; + + asio::strand m_strand; + std::atomic m_closed{false}; + std::atomic m_connected{false}; + std::atomic m_connect_error{false}; + // ClientWorld is managed by App + ClientWorld& m_world; + std::atomic_uint64_t m_sequence{0}; + + asio::awaitable connect(std::string ip, int port); + asio::awaitable read_loop(); + + void do_write(); +}; +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/network_server.hpp b/include/Cubed/gameplay/network_server.hpp new file mode 100644 index 0000000..5ed0ace --- /dev/null +++ b/include/Cubed/gameplay/network_server.hpp @@ -0,0 +1,33 @@ +#pragma once +#include "Cubed/gameplay/server_world.hpp" +#include "Cubed/gameplay/session.hpp" + +#include +#include +namespace Cubed { + +class NetworkServer { +public: + NetworkServer(int port = 25530); + ~NetworkServer(); + void stop(); + + // Run in another thread after initialization is complete + void start_server(int port = 25530); + + int port() const; + ServerWorld& server_world(); + +private: + asio::io_context m_io; + std::thread m_net_thread; + int m_port = 25530; + std::atomic m_stopped{false}; + std::atomic m_started{false}; + ServerWorld m_world; + std::mutex m_session_mutex; + std::unordered_map> m_session; + asio::awaitable listen(); + void net_run(); +}; +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/packet.hpp b/include/Cubed/gameplay/packet.hpp new file mode 100644 index 0000000..a87fa17 --- /dev/null +++ b/include/Cubed/gameplay/packet.hpp @@ -0,0 +1,216 @@ +#pragma once +#include "Cubed/tools/compression.hpp" +#include "packet.pb.h" // IWYU pragma: keep + +#include +#include +#include +#include +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#else +#include +#endif +#include +#include +#include +#include +namespace Cubed { +constexpr size_t HEADER_LEN = + sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint32_t) + sizeof(uint32_t); +constexpr size_t PACKET_COMPRESSION_THRESHOLD = 100; +using Packet = std::shared_ptr>; +enum class CompressType : uint16_t { + NONE = 0, + ZSTD = 1, +}; + +inline CompressType get_compress_type(uint16_t id) { + using enum CompressType; + switch (id) { + case std::to_underlying(NONE): + return NONE; + case std::to_underlying(ZSTD): + return ZSTD; + } + throw std::runtime_error(std::format("Unknown CompressType {}", id)); +} + +struct PacketHeader { + uint16_t cmd{}; + CompressType compress_type{}; // 0=none 1=zlib + uint32_t uncompressed_size{}; + uint32_t compressed_size{}; +}; + +enum class PacketEnum : uint16_t { + LOGIN_REQ = 1001, + LOGIN_RSP = 1002, + LOGOUT_REQ = 1003, + LOGOUT_RSP = 1004, + PLAYER_INFO = 2001, + PLAYER_POS = 2002, + PLAYER_INFO_RSP = 2003, + CHUNK_DATA_REQ = 3001, + CHUNK_DATA_RSP = 3002, + BLOCK_CHANGE_REQ = 3003, + BLOCK_CHANGE_RSP = 3004, + S2C_CLEAR_ALL_CHUNKS = 3005, + UPDATE_TIME = 3006, + PING = 9001, + PONG = 9002 + +}; + +template struct always_false : std::false_type {}; // NOLINT + +template constexpr uint16_t get_packet_id() { + static_assert(always_false::value, "Unknown Type"); + return 0; +} + +template <> constexpr uint16_t get_packet_id() { + return std::to_underlying(PacketEnum::LOGIN_REQ); +} +template <> constexpr uint16_t get_packet_id() { + return std::to_underlying(PacketEnum::LOGIN_RSP); +} +template <> constexpr uint16_t get_packet_id() { + return std::to_underlying(PacketEnum::LOGOUT_REQ); +} +template <> constexpr uint16_t get_packet_id() { + return std::to_underlying(PacketEnum::LOGOUT_RSP); +} +template <> constexpr uint16_t get_packet_id() { + return std::to_underlying(PacketEnum::PLAYER_INFO); +} +template <> constexpr uint16_t get_packet_id() { + return std::to_underlying(PacketEnum::PLAYER_POS); +} +template <> constexpr uint16_t get_packet_id() { + return std::to_underlying(PacketEnum::PLAYER_INFO_RSP); +} +template <> constexpr uint16_t get_packet_id() { + return std::to_underlying(PacketEnum::CHUNK_DATA_REQ); +} +template <> constexpr uint16_t get_packet_id() { + return std::to_underlying(PacketEnum::CHUNK_DATA_RSP); +} +template <> constexpr uint16_t get_packet_id() { + return std::to_underlying(PacketEnum::BLOCK_CHANGE_REQ); +} +template <> constexpr uint16_t get_packet_id() { + return std::to_underlying(PacketEnum::BLOCK_CHANGE_RSP); +} +template <> constexpr uint16_t get_packet_id() { + return std::to_underlying(PacketEnum::S2C_CLEAR_ALL_CHUNKS); +} +template <> constexpr uint16_t get_packet_id() { + return std::to_underlying(PacketEnum::UPDATE_TIME); +} +template <> constexpr uint16_t get_packet_id() { + return std::to_underlying(PacketEnum::PING); +} +template <> constexpr uint16_t get_packet_id() { + return std::to_underlying(PacketEnum::PONG); +} + +template + requires std::derived_from +Packet make_packet(const T& msg) { + PacketHeader header{}; + header.cmd = get_packet_id(); + uint32_t raw_size = static_cast(msg.ByteSizeLong()); + std::vector raw(raw_size); + + if (!msg.SerializeToArray(raw.data(), raw_size)) { + return {}; + } + std::vector payload; + if (raw_size >= PACKET_COMPRESSION_THRESHOLD) { + std::vector compressed = compress_data(raw); + if (compressed.size() < raw.size()) { + payload = std::move(compressed); + header.compress_type = CompressType::ZSTD; + } else { + payload = std::move(raw); + header.compress_type = CompressType::NONE; + } + } else { + payload = std::move(raw); + header.compress_type = CompressType::NONE; + } + header.uncompressed_size = raw_size; + header.compressed_size = static_cast(payload.size()); + + auto packet = + std::make_shared>(HEADER_LEN + payload.size()); + + uint16_t cmd_net = htons(header.cmd); + uint16_t compress_type_net = + htons(std::to_underlying(header.compress_type)); + uint32_t uncompressed_size_net = htonl(header.uncompressed_size); + uint32_t compressed_size_net = htonl(header.compressed_size); + + std::memcpy(packet->data(), &cmd_net, sizeof(cmd_net)); + + std::memcpy(packet->data() + 2, &compress_type_net, + sizeof(compress_type_net)); + std::memcpy(packet->data() + 4, &uncompressed_size_net, + sizeof(uncompressed_size_net)); + std::memcpy(packet->data() + 8, &compressed_size_net, + sizeof(compressed_size_net)); + std::memcpy(packet->data() + HEADER_LEN, payload.data(), payload.size()); + + return packet; +} + +inline PacketHeader decode_packet_header(std::span header) { + if (header.size() < HEADER_LEN) + throw std::runtime_error("Invalid header"); + uint16_t cmd_net; + uint16_t compress_type_net; + uint32_t uncompressed_size_net; + uint32_t compressed_size_net; + std::memcpy(&cmd_net, header.data(), sizeof(cmd_net)); + std::memcpy(&compress_type_net, header.data() + 2, + sizeof(compress_type_net)); + std::memcpy(&uncompressed_size_net, header.data() + 4, + sizeof(uncompressed_size_net)); + std::memcpy(&compressed_size_net, header.data() + 8, + sizeof(compressed_size_net)); + + return {ntohs(cmd_net), get_compress_type(ntohs(compress_type_net)), + ntohl(uncompressed_size_net), ntohl(compressed_size_net)}; +} +template + requires std::derived_from +bool decode_packet(T& message, std::span data, + const PacketHeader& header) { + if (data.size() != header.compressed_size) { + return false; + } + + if (header.compress_type == CompressType::NONE && + header.uncompressed_size != header.compressed_size) { + return false; + } + + switch (header.compress_type) { + case CompressType::NONE: { + return message.ParseFromArray( + data.data(), static_cast(header.uncompressed_size)); + } + case CompressType::ZSTD: { + auto raw = decompress_data(data, header.uncompressed_size); + return message.ParseFromArray(raw.data(), static_cast(raw.size())); + } + default: + return false; + } +} + +} // namespace Cubed diff --git a/include/Cubed/gameplay/server_chunk.hpp b/include/Cubed/gameplay/server_chunk.hpp new file mode 100644 index 0000000..8fa7075 --- /dev/null +++ b/include/Cubed/gameplay/server_chunk.hpp @@ -0,0 +1,97 @@ +#pragma once +#include "Cubed/constants.hpp" +#include "Cubed/gameplay/biome.hpp" +#include "Cubed/gameplay/block.hpp" +#include "Cubed/gameplay/chunk_generator.hpp" +#include "Cubed/gameplay/chunk_pos.hpp" + +#include +#include +#include +#include +namespace Cubed { +class ServerWorld; +class ServerChunk { +public: + ServerChunk(ServerWorld& world, ChunkPos chunk_pos, + bool temp_chunk = false); + ServerChunk(const ServerChunk&) = delete; + ServerChunk(ServerChunk&&) noexcept; + ServerChunk& operator=(const ServerChunk&) = delete; + ServerChunk& operator=(ServerChunk&&) noexcept; + + static std::tuple world_to_block(int world_x, int world_y, + int world_z, int chunk_x, + int chunk_z); + static std::tuple world_to_block(const glm::ivec3& block_pos, + ChunkPos chunk_pos); + static std::tuple block_to_world(int x, int y, int z, + int chunk_x, int chunk_z); + static std::tuple block_to_world(const glm::ivec3& block_pos, + ChunkPos chunk_pos); + + void set_chunk_block(int index, unsigned id); + // ensure thread safe! + void gen_chunk(); + + BiomeType get_biome() const; + ChunkPos get_chunk_pos() const; + const std::vector& get_chunk_blocks() const; + HeightMapArray get_heightmap() const; + bool is_temp_chunk() const; + ChunkPos chunk_pos() const; + BiomeType biome() const; + void biome(BiomeType b); + HeightMapArray& heightmap(); + std::vector& blocks(); + ServerWorld& world(); + unsigned seed() const; + BiomeConditions& conditions(); + bool& has_cave(); + const OptionalBlockVectorArray& get_neightbor_blocks() const; + static int index(int x, int y, int z); + static int index(const glm::vec3& pos); + +private: + static constexpr int SIZE_X = CHUNK_SIZE; + static constexpr int SIZE_Y = WORLD_SIZE_Y; + static constexpr int SIZE_Z = CHUNK_SIZE; + + std::atomic m_gening{false}; + std::atomic m_temp_chunk{false}; + + bool m_has_cave{false}; + + std::atomic m_biome = BiomeType::PLAIN; + + ChunkPos m_chunk_pos; + ServerWorld& m_world; + HeightMapArray m_heightmap; + // the index is a array of block id + std::vector m_blocks; + OptionalBlockVectorArray m_neightbor_blocks; + float frequency = 0.01f; + float height = 80; + unsigned m_seed = 0; + + BiomeConditions m_conditions; + + std::unique_ptr m_generator; + + // Init Chunk + // Determine biome from temperature and humidity noise + void gen_phase_one(); + + // Generate heightmap using biome-specific noise + void gen_phase_two(); + + // Generate terrain blocks from heightmap and biome + void gen_phase_three(); + // Blend surface blocks at chunk borders with neighbors + void gen_phase_four(const std::array>, + 4>& neighbor_block); + // Generate biome-specific vegetation/structures + void gen_phase_five(); +}; + +} // namespace Cubed diff --git a/include/Cubed/gameplay/server_player.hpp b/include/Cubed/gameplay/server_player.hpp new file mode 100644 index 0000000..db7aa3e --- /dev/null +++ b/include/Cubed/gameplay/server_player.hpp @@ -0,0 +1,54 @@ +#pragma once +#include "Cubed/gameplay/chunk_pos.hpp" +#include "Cubed/gameplay/game_time.hpp" + +#include +#include +#include +#include +#include +#include +#include +namespace Cubed { +class ServerWorld; +class Session; +class ServerPlayer { + +public: + using ChunkPosSet = absl::flat_hash_set; + ServerPlayer(const ServerPlayer&) = delete; + ServerPlayer(ServerPlayer&&) = delete; + ServerPlayer& operator=(const ServerPlayer&) = delete; + ServerPlayer& operator=(ServerPlayer&&) = delete; + ServerPlayer(std::string_view name, std::string_view uuid, + ServerWorld& m_world, std::shared_ptr session, + TickType gametick); + + const glm::vec3& get_pos() const; + const std::string& get_name() const; + const std::string& get_uuid() const; + std::shared_ptr get_session() const; + void update_pos(float x, float y, float z); + void update_sync_gametick(TickType gametick); + bool is_disconnect(TickType current_gametick) const; + int task_id() const; + void task_id(int id); + bool has_player(ChunkPos pos) const; + void update_chunk_set(const ChunkPosSet& set); + const ChunkPosSet& get_chunk_pos_set() const; + ChunkPosSet& get_chunk_pos_set(); + +private: + static constexpr TickType TIMEOUT = 200; + std::string m_name; + std::string m_uuid; + glm::vec3 m_pos{0.0f}; + ServerWorld& m_world; + ChunkPos m_last_chunk_pos{0, 0}; + std::shared_ptr m_session; + std::atomic m_last_gametick{0}; + std::atomic m_chunk_task_id{0}; + mutable std::shared_mutex m_chunk_pos_mutex; + ChunkPosSet m_player_chunk_pos_set; +}; +} // namespace Cubed diff --git a/include/Cubed/gameplay/server_world.hpp b/include/Cubed/gameplay/server_world.hpp new file mode 100644 index 0000000..0baea37 --- /dev/null +++ b/include/Cubed/gameplay/server_world.hpp @@ -0,0 +1,187 @@ +#pragma once + +#include "Cubed/gameplay/cave_carver.hpp" +#include "Cubed/gameplay/chunk_pos.hpp" +#include "Cubed/gameplay/game_time.hpp" +#include "Cubed/gameplay/river_worm.hpp" +#include "Cubed/gameplay/server_chunk.hpp" +#include "Cubed/gameplay/server_player.hpp" +#include "Cubed/tools/priority_thread_pool.hpp" +#include "Cubed/tools/recent_queue.hpp" +#include "Cubed/tools/thread_pool.hpp" +#include "world/block_change.pb.h" + +#include +#include +#include +#include +#include +#include +#include +#include +namespace Cubed { +class Session; +class ServerWorld { +public: + enum class ThreadPoolKind { NET, GEN }; + ServerWorld(); + ~ServerWorld(); + void stop(); + void handle_player_exit(const std::string& uuid); + void init_world(); + void need_gen(std::string uuid); + void update(); + void hot_reload(); + + int rendering_distance() const; + void rendering_distance(int rendering_distance); + void start_gen_thread(); + void start_server_thread(); + + void stop_gen_thread(); + void stop_server_thread(); + + void stop_thread_pool(); + void start_thread_pool(); + + void serever_run(std::stop_token stoken); + + CaveCarver& cave_carcer(); + RiverWorm& river_worm(); + + TickType game_tick() const; + TickType day_tick() const; + + void day_tick(TickType tick); + + int per_tick_time() const; + void per_tick_time(int ms); + bool is_tick_running() const; + void tick_running(bool run); + + int gen_pool_threads() const; + int max_threads() const; + + void change_pool_threads(ThreadPoolKind kind, int threads); + + int chunk_load_style() const; + void set_chunk_load_style(int id); + + bool set_block(const glm::ivec3& block_pos, unsigned id); + + void sync_player_pos(const std::string& uuid, float x, float y, float z); + void handle_player_login(const std::string& player_name, + std::shared_ptr session); + glm::vec3 get_player_pos(const std::string& uuid) const; + + void handle_chunk_req(int task_id, const std::string& uuid, ChunkPos pos); + void handle_block_change(const BlockChangeReq& req); + + int chunk_size() const; + template + void register_timer(std::string_view id, TickType threshold, Fn&& f) { + m_timers.emplace(std::piecewise_construct, + std::forward_as_tuple(std::string(id)), + std::forward_as_tuple(threshold, std::forward(f))); + } + +private: + enum class ChunkState { NONE, GENERATING, READY, PENDING_DELETE }; + struct ChunkEntity { + ChunkState state; + std::shared_ptr chunk; + uint32_t ref_count = 0; + }; + + enum class ChunkLoadStyle { RANDOM, CENTER }; + struct PendingRequest { + std::string uuid; + int task_id; + ChunkPos pos; + }; + struct PendingChunk { + ChunkPos pos; + std::unique_ptr chunk; + }; + + using ChunkHashMap = + tbb::concurrent_hash_map; + using PlayerHashMap = std::unordered_map; + using NewChunkVector = std::vector; + using ChunkPosSet = absl::flat_hash_set; + using PlayerUUIDMap = tbb::concurrent_hash_map; + + using chunk_acc = ChunkHashMap::accessor; + using chunk_caac = ChunkHashMap::const_accessor; + + using uuid_acc = PlayerUUIDMap::accessor; + using uuid_cacc = PlayerUUIDMap::const_accessor; + // key = uuid + PlayerHashMap m_players; + ChunkHashMap m_chunks; + + CaveCarver m_cave_carcer; + RiverWorm m_river_worm; + + std::jthread m_gen_thread; + std::jthread m_server_thread; + + std::atomic m_chunk_gen_finished{false}; + std::atomic m_could_gen{true}; + std::atomic m_gen_running{false}; + std::atomic m_need_gen_chunk{false}; + std::atomic m_init{false}; + std::atomic m_stopped{false}; + std::atomic m_rendering_distance{24}; + std::atomic m_gen_pool_threads{0}; + std::atomic m_net_pool_threads{0}; + std::atomic m_max_threads{1}; + + std::atomic m_game_ticks{0}; + std::atomic m_day_tick{6000}; + std::atomic m_tick_running{true}; + std::atomic m_per_tick_time = DEFAULT_PER_TICK_TIME; // ms + + mutable std::shared_mutex m_player_mutex; + std::mutex m_need_gen_queue_mutex; + std::condition_variable_any m_gen_cv; + + RecentQueue m_need_gen_queue; + + std::atomic> m_gen_thread_pool; + std::atomic> m_net_thread_pool; + + std::atomic m_chunk_load_style{ChunkLoadStyle::CENTER}; + + PlayerUUIDMap m_uuid_to_name; + + tbb::concurrent_unordered_map m_timers; + tbb::concurrent_queue m_waiting_chunk_requests; + tbb::concurrent_queue> m_finished_queue; + + void init_chunks(); + + void gen_chunks_internal(const std::string& uuid); + + void compute_required_chunks(ChunkPosSet& required_chunks, + const std::optional& uuid); + void sync_and_collect_missing_chunks(std::vector&, + const ChunkPosSet&); + void submit_new_chunks(const std::string& uuid, NewChunkVector& new_chunks); + // void wait_all_chunk_tasks(); + + void update_ref_count(const ChunkPosSet& old, const ChunkPosSet& now); + + void send_time(); + + void send_chunk(int task_id, const std::string& uuid, ChunkPos pos); + + int + change_pool_threads(std::atomic>& thread_pool, + int threads); + int change_pool_threads( + std::atomic>& thread_pool, + int threads); + void send_server_stop(); +}; +} // namespace Cubed diff --git a/include/Cubed/gameplay/session.hpp b/include/Cubed/gameplay/session.hpp new file mode 100644 index 0000000..8e3153b --- /dev/null +++ b/include/Cubed/gameplay/session.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include "Cubed/gameplay/packet.hpp" + +#include +#include +#include +#include +namespace Cubed { + +using asio::ip::tcp; +class ServerWorld; +class Session : public std::enable_shared_from_this { + +public: + Session(tcp::socket socket, ServerWorld& server_world, + asio::io_context& io); + ~Session(); + void start(); + void send(Packet packet, int priority = 10); + + void close(); + const std::string& uuid() const; + +private: + struct Task { + int priority = 10; + std::uint64_t sequence = 0; + Packet packet; + Task(int p, std::uint64_t seq, Packet pac) + : priority(p), sequence(seq), packet(std::move(pac)) {} + }; + + struct TaskCompare { + bool operator()(const Task& a, const Task& b) const { + + if (a.priority != b.priority) { + return a.priority > b.priority; + } + + return a.sequence > b.sequence; + } + }; + + static constexpr uint32_t MAX_PACKET_SIZE = 4 * 1024 * 1024; + tcp::socket m_socket; + std::vector m_read_buffer; + std::priority_queue, TaskCompare> m_write_queue; + asio::strand m_strand; + std::string m_uuid; + ServerWorld& m_server_world; + std::atomic m_closed{false}; + + std::atomic_uint64_t m_sequence{0}; + + asio::awaitable read_loop(); + + void do_write(); +}; +} // namespace Cubed diff --git a/include/Cubed/gameplay/tree.hpp b/include/Cubed/gameplay/tree.hpp index da1f564..1613fc7 100644 --- a/include/Cubed/gameplay/tree.hpp +++ b/include/Cubed/gameplay/tree.hpp @@ -4,13 +4,13 @@ namespace Cubed { -class Chunk; +class ServerChunk; struct TreeStructNode { glm::ivec3 offset{0, 0, 0}; unsigned id = 0; }; -bool build_tree(Chunk& chunk, const glm::ivec3& pos); +bool build_tree(ServerChunk& chunk, const glm::ivec3& pos); } // namespace Cubed diff --git a/include/Cubed/gameplay/vertex_data.hpp b/include/Cubed/gameplay/vertex_data.hpp index b3ae5bb..b9d07b9 100644 --- a/include/Cubed/gameplay/vertex_data.hpp +++ b/include/Cubed/gameplay/vertex_data.hpp @@ -5,14 +5,14 @@ #include #include namespace Cubed { -class World; +class ClientWorld; struct VertexData { std::vector m_vertices; GLuint m_vbo = 0; GLuint m_vao = 0; std::atomic m_sum{0}; - World& m_world; - VertexData(World& world); + ClientWorld& m_world; + VertexData(ClientWorld& world); ~VertexData(); VertexData(const VertexData&) = delete; VertexData(VertexData&&) noexcept; diff --git a/include/Cubed/gameplay/world.hpp b/include/Cubed/gameplay/world.hpp deleted file mode 100644 index a789752..0000000 --- a/include/Cubed/gameplay/world.hpp +++ /dev/null @@ -1,173 +0,0 @@ -#pragma once -#include "Cubed/AABB.hpp" -#include "Cubed/gameplay/cave_carver.hpp" -#include "Cubed/gameplay/chunk.hpp" -#include "Cubed/gameplay/game_time.hpp" -#include "Cubed/gameplay/river_worm.hpp" -#include "Cubed/tools/thread_pool.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Cubed { - -struct ChunkRenderSnapshot { - GLuint normal_vao; - size_t normal_vertices_count; - GLuint cross_vao; - size_t cross_vertices_count; - GLuint normal_discard_vao; - size_t normal_discard_vertices_count; - GLuint normal_blend_vao; - size_t normal_blend_vertices_count; - GLuint water_vao; - size_t water_vertices_count; - glm::vec3 center; - glm::vec3 half_extents; -}; - -class Player; -class TextureManager; -class World { -private: - enum class ChunkLoadStyle { RANDOM, CENTER }; - - struct PendingChunk { - Chunk chunk; - std::future future; - }; - - using OptionalBlockVectorArray = - std::array>, 4>; - using ChunkPtrUpdateList = std::vector>; - using ChunkPairVector = std::vector>; - using ChunkPairQueue = std::queue>; - using ConstChunkMap = - std::unordered_map; - using ChunkPosSet = std::unordered_set; - using ChunkHashMap = std::unordered_map; - using PendingChunkHashMap = - std::unordered_map; - glm::vec3 m_gen_player_pos{0.0f, 0.0f, 0.0f}; - ChunkHashMap m_chunks; - std::unordered_map m_players; - std::vector m_planes; - - std::thread m_gen_thread; - std::thread m_server_thread; - std::atomic> m_gen_thread_pool; - std::stop_source m_server_stop_source; - - std::atomic m_per_tick_time = DEFAULT_PER_TICK_TIME; // ms - - std::atomic m_day_tick = 6000; - - mutable std::shared_mutex m_chunks_mutex; - std::mutex m_gen_signal_mutex; - std::mutex m_new_chunk_mutex; - std::mutex m_delete_vbo_mutex; - std::mutex m_delete_vao_mutex; - std::mutex m_gen_player_pos_mutex; - std::vector m_pending_delete_vbo; - std::vector m_pending_delete_vao; - std::condition_variable m_gen_cv; - std::atomic m_gen_running{false}; - std::atomic m_need_gen_chunk{false}; - std::atomic m_is_rebuilding{false}; - std::atomic m_chunk_gen_finished{false}; - std::atomic m_could_gen{true}; - std::atomic m_tick_running{true}; - std::atomic m_rendering_distance{24}; - std::atomic m_pool_threads{0}; - std::atomic m_max_threads{1}; - std::atomic m_game_ticks{0}; - std::atomic m_chunk_load_style{ChunkLoadStyle::RANDOM}; - std::vector m_dirty_queue; - std::vector m_render_snapshots; - std::vector> m_new_finished_chunk; - // Can only be used in the gen thread - PendingChunkHashMap new_chunks; - - CaveCarver m_cave_carcer; - RiverWorm m_river_worm; - void init_chunks(); - - void gen_chunks_internal(); - void sync_player_pos(glm::vec3& player_pos); - void compute_required_chunks(ChunkPosSet& required_chunks); - void sync_and_collect_missing_chunks(std::vector&, - const ChunkPosSet&); - - void submit_new_chunks(); - void poll_finished_chunks(); - void wait_all_chunk_tasks(); - -public: - World(); - ~World(); - - bool can_move(const AABB& player_box) const; - // const BlockRenderData& get_block_render_data(int x, int y ,int z); - - const std::optional& - get_look_block_pos(const std::string& name) const; - // const Chunk* get_chunk(const ChunkPos& pos) const; - - Player& get_player(const std::string& name); - void init_world(); - int get_block(const glm::ivec3& block_pos) const; - bool is_solid(const glm::ivec3& block_pos) const; - bool can_pass_block(const glm::ivec3& block_pos) const; - BlockType get_block_tpye(const glm::ivec3& block_pos) const; - static ChunkPos get_chunk_pos(int world_x, int world_z); - - void need_gen(); - - void set_block(const glm::ivec3& pos, unsigned id); - void update(float delta_time); - - void push_delete_vbo(GLuint vbo); - void push_delete_vao(GLuint vao); - void hot_reload(); - - void rebuild_world(); - - int rendering_distance() const; - void rendering_distance(int rendering_distance); - void start_gen_thread(); - void start_server_thread(); - void stop_gen_thread(); - void stop_server_thread(); - void stop_thread_pool(); - void start_thread_pool(); - void serever_run(std::stop_token stoken); - - CaveCarver& cave_carcer(); - RiverWorm& river_worm(); - std::vector& planes(); - std::vector& render_snapshots(); - - glm::vec3 sunlight_dir() const; - TickType game_tick() const; - TickType day_tick() const; - void day_tick(TickType tick); - int per_tick_time() const; - void per_tick_time(int ms); - - bool is_tick_running() const; - void tick_running(bool run); - int pool_threads() const; - int max_threads() const; - void change_pool_threads(int threads); - int chunk_load_style() const; - void set_chunk_load_style(int id); - ChunkInfo get_chunk_info(const glm::vec3& world_pos) const; -}; - -} // namespace Cubed diff --git a/include/Cubed/primitive_data.hpp b/include/Cubed/primitive_data.hpp index 3b8a79e..5bee0cc 100644 --- a/include/Cubed/primitive_data.hpp +++ b/include/Cubed/primitive_data.hpp @@ -272,6 +272,55 @@ constexpr float CROSS_TANGENTS[2][6][3] = { {-0.7071f, 0.0f, 0.7071f}}}; #pragma endregion + +#pragma region Player + +constexpr float VERTICES_PLAYER[6][6][3] = { + // ===== front (z = +1) ===== + {{0.0f, 0.0f, 1.0f}, // bottom left + {0.0f, 2.0f, 1.0f}, // top left + {1.0f, 2.0f, 1.0f}, // top right + {1.0f, 2.0f, 1.0f}, // top right + {1.0f, 0.0f, 1.0f}, // bottom right + {0.0f, 0.0f, 1.0f}}, // bottom left + // ===== right (x = +1) ===== + {{1.0f, 0.0f, 1.0f}, // bottom front + {1.0f, 0.0f, 0.0f}, // bottom back + {1.0f, 2.0f, 0.0f}, // top back + {1.0f, 2.0f, 0.0f}, // top back + {1.0f, 2.0f, 1.0f}, // top front + {1.0f, 0.0f, 1.0f}}, // bottom front + // ===== back (z = -1) ===== + {{0.0f, 0.0f, 0.0f}, // bottom left + {1.0f, 0.0f, 0.0f}, // bottom right + {1.0f, 2.0f, 0.0f}, // top right + {1.0f, 2.0f, 0.0f}, // top right + {0.0f, 2.0f, 0.0f}, // top left + {0.0f, 0.0f, 0.0f}}, // bottom left + // ===== left (x = -1) ===== + {{0.0f, 0.0f, 0.0f}, // bottom back + {0.0f, 0.0f, 1.0f}, // bottom front + {0.0f, 2.0f, 1.0f}, // top front + {0.0f, 2.0f, 1.0f}, // top front + {0.0f, 2.0f, 0.0f}, // top back + {0.0f, 0.0f, 0.0f}}, // bottom back + // ===== top (y = +2) ===== + {{0.0f, 2.0f, 0.0f}, // back left + {1.0f, 2.0f, 0.0f}, // back right + {1.0f, 2.0f, 1.0f}, // front right + {1.0f, 2.0f, 1.0f}, // front right + {0.0f, 2.0f, 1.0f}, // front left + {0.0f, 2.0f, 0.0f}}, // back left + // ===== bottom (y = -1) ===== + {{0.0f, 0.0f, 1.0f}, // front left + {1.0f, 0.0f, 1.0f}, // front right + {1.0f, 0.0f, 0.0f}, // back right + {1.0f, 0.0f, 0.0f}, // back right + {0.0f, 0.0f, 0.0f}, // back left + {0.0f, 0.0f, 1.0f}} // front left +}; +#pragma endregion + // [-1, 1] constexpr float QUAD_VERTICES[] = { // postion // texcoorlds diff --git a/include/Cubed/renderer.hpp b/include/Cubed/renderer.hpp index 74ef14c..91b1eb6 100644 --- a/include/Cubed/renderer.hpp +++ b/include/Cubed/renderer.hpp @@ -11,13 +11,13 @@ namespace Cubed { class Camera; class TextureManager; -class World; +class ClientWorld; class DevPanel; class Renderer { public: constexpr static int NUM_VAO = 7; - Renderer(const Camera& camera, World& world, + Renderer(const Camera& camera, ClientWorld& world, const TextureManager& texture_manager, DevPanel& dev_panel); ~Renderer(); void hot_reload(); @@ -91,7 +91,7 @@ private: const Camera& m_camera; DevPanel& m_dev_panel; const TextureManager& m_texture_manager; - World& m_world; + ClientWorld& m_world; bool m_discard_tranparent = true; bool m_shader_on = true; @@ -99,6 +99,9 @@ private: bool m_water_depth_fade = true; bool m_pbr = true; bool m_flip_y = false; + + bool m_init = false; + int m_shadow_mode = 0; int m_light_cull_face = 0; float m_aspect = 0.0f; @@ -119,7 +122,7 @@ private: GLuint m_outline_indices_vbo = 0; GLuint m_outline_vbo = 0; GLuint m_ui_vbo = 0; - + GLuint m_player_vbo = 0; GLuint m_fbo = 0; GLuint m_screen_texture = 0; GLuint m_screen_depth_texture = 0; @@ -171,7 +174,7 @@ private: 2 - outline vao 3 - ui vao 4 - text vao - + 5 - player vao */ std::vector m_vao; std::vector m_ui; @@ -186,6 +189,7 @@ private: void render_text(); void render_ui(); void render_world(); + void render_player(); void render_underwater(); void render_dev_panel(); diff --git a/include/Cubed/texture_manager.hpp b/include/Cubed/texture_manager.hpp index c62043a..7383378 100644 --- a/include/Cubed/texture_manager.hpp +++ b/include/Cubed/texture_manager.hpp @@ -8,6 +8,7 @@ namespace Cubed { class TextureManager { private: bool m_need_reload = false; + bool m_init = false; GLuint m_block_status_array = 0; GLuint m_texture_array = 0; GLuint m_cross_plane_array = 0; diff --git a/include/Cubed/tools/arg_parser.hpp b/include/Cubed/tools/arg_parser.hpp new file mode 100644 index 0000000..d9b2f9b --- /dev/null +++ b/include/Cubed/tools/arg_parser.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include +#include + +namespace Cubed { +class ArgParser { +public: + ArgParser(int argc, char** argv) : m_args(argv, argc) {}; + ArgParser(std::span args) : m_args(args) {} + + bool has_next() const { return m_index < m_args.size(); } + + std::string_view next() { + if (!has_next()) { + throw std::runtime_error("No more arguments"); + } + return m_args[m_index++]; + } + + std::string_view require_next(std::string_view option) { + if (!has_next()) { + throw std::runtime_error( + std::format("{} requires an argument", option)); + } + return next(); + } + +private: + std::span m_args; + size_t m_index = 1; +}; +} // namespace Cubed diff --git a/include/Cubed/tools/compression.hpp b/include/Cubed/tools/compression.hpp new file mode 100644 index 0000000..fe556c1 --- /dev/null +++ b/include/Cubed/tools/compression.hpp @@ -0,0 +1,40 @@ +#pragma once +#include +#include +#include +#include +#include +#include +namespace Cubed { +constexpr int DEFAULT_ZSTD_LEVEL = 3; +inline std::vector compress_data(std::span data) { + size_t max_size = ZSTD_compressBound(data.size()); + std::vector compressed_data(max_size); + size_t compressed_bytes = + ZSTD_compress(compressed_data.data(), max_size, data.data(), + data.size(), DEFAULT_ZSTD_LEVEL); + if (ZSTD_isError(compressed_bytes)) { + throw std::runtime_error(std::format( + "Compress Fail {}", ZSTD_getErrorName(compressed_bytes))); + } + compressed_data.resize(compressed_bytes); + return compressed_data; +} + +inline std::vector decompress_data(std::span data, + uint32_t original_size) { + std::vector decompressed_data(original_size); + size_t decompressed_bytes = ZSTD_decompress( + decompressed_data.data(), original_size, data.data(), data.size()); + if (ZSTD_isError(decompressed_bytes)) { + throw std::runtime_error(std::format( + "Decompress Fail {}", ZSTD_getErrorName(decompressed_bytes))); + } + if (decompressed_bytes != original_size) { + throw std::runtime_error("Unexpected decompressed size"); + } + + return decompressed_data; +} + +} // namespace Cubed diff --git a/include/Cubed/tools/log.hpp b/include/Cubed/tools/log.hpp index 783f68c..7a3d0a1 100644 --- a/include/Cubed/tools/log.hpp +++ b/include/Cubed/tools/log.hpp @@ -9,7 +9,7 @@ namespace Cubed { namespace Logger { -enum class Level { TRACE, DEBUG, INFO, ERROR, WARN }; +enum class Level { L_TRACE, L_DEBUG, L_INFO, L_ERROR, L_WARN }; template inline void info(std::format_string fmt, Args&&... args) { @@ -53,7 +53,7 @@ inline void log(Level level, std::source_location loc, std::chrono::system_clock::now()); std::string msg = std::vformat(fmt.get(), std::make_format_args(args...)); switch (level) { - case Logger::Level::TRACE: + case Logger::Level::L_TRACE: std::osyncstream(std::cout) << "\033[1;34m" << std::format("[TRACE][{:%Y-%m-%d %H:%M:%S}]", now_time) << "[" @@ -61,20 +61,20 @@ inline void log(Level level, std::source_location loc, << "[" << loc.function_name() << "]" << msg << "\033[0m" << "\n"; break; - case Logger::Level::DEBUG: + case Logger::Level::L_DEBUG: std::osyncstream(std::cout) << "\033[1;34m" << std::format("[DEBUG][{:%Y-%m-%d %H:%M:%S}]", now_time) << msg << "\033[0m" << "\n"; break; - case Logger::Level::INFO: + case Logger::Level::L_INFO: info(fmt, std::forward(args)...); break; - case Logger::Level::WARN: + case Logger::Level::L_WARN: warn(fmt, std::forward(args)...); break; - case Logger::Level::ERROR: + case Logger::Level::L_ERROR: error(fmt, std::forward(args)...); break; } diff --git a/include/Cubed/tools/math_tools.hpp b/include/Cubed/tools/math_tools.hpp index 4ee8b1c..9ca874a 100644 --- a/include/Cubed/tools/math_tools.hpp +++ b/include/Cubed/tools/math_tools.hpp @@ -1,18 +1,108 @@ #pragma once +#include #include - +#include namespace Cubed { namespace Math { -void extract_frustum_planes(const glm::mat4& mvp_matrix, - std::vector& planes); +inline void extract_frustum_planes(const glm::mat4& mvp_matrix, + std::vector& planes) { + if (planes.size() != 6) { + planes.resize(6); + } -float smootherstep(float edge0, float edge1, float x); -bool is_aabb_in_frustum(const glm::vec3& center, const glm::vec3& half_extents, - const std::vector& planes); -float deterministic_random(int x, int z, uint64_t seed); -glm::vec3 slerp(const glm::vec3& from, const glm::vec3& to, float t); + const float* m = glm::value_ptr(mvp_matrix); + + // left plane + planes[0] = + glm::vec4(m[3] + m[0], m[7] + m[4], m[11] + m[8], m[15] + m[12]); + // right plane + planes[1] = + glm::vec4(m[3] - m[0], m[7] - m[4], m[11] - m[8], m[15] - m[12]); + // bottom plane + planes[2] = + glm::vec4(m[3] + m[1], m[7] + m[5], m[11] + m[9], m[15] + m[13]); + // top plane + planes[3] = + glm::vec4(m[3] - m[1], m[7] - m[5], m[11] - m[9], m[15] - m[13]); + // near plane + planes[4] = + glm::vec4(m[3] + m[2], m[7] + m[6], m[11] + m[10], m[15] + m[14]); + // far plane + planes[5] = + glm::vec4(m[3] - m[2], m[7] - m[6], m[11] - m[10], m[15] - m[14]); + + for (auto& p : planes) { + p = glm::normalize(p); + } +} + +inline float smootherstep(float edge0, float edge1, float x) { + + x = std::clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f); + + return x * x * x * (x * (6.0f * x - 15.0f) + 10.0f); +} + +inline bool is_aabb_in_frustum(const glm::vec3& center, + const glm::vec3& half_extents, + const std::vector& planes) { + for (const auto& plane : planes) { + // distance + float d = glm::dot(glm::vec3(plane), center) + plane.w; + float r = half_extents.x * std::abs(plane.x) + + half_extents.y * std::abs(plane.y) + + half_extents.z * std::abs(plane.z); + if (d + r < 0) { + return false; + } + } + return true; +} +inline float deterministic_random(int x, int z, uint64_t seed) { + uint64_t h = seed; + h = h * 6364136223846793005ULL + (uint64_t)x; + h = h * 6364136223846793005ULL + (uint64_t)z; + return (float)(h >> 40) / (float)(1 << 24); +} + +inline glm::vec3 slerp(const glm::vec3& from, const glm::vec3& to, float t) { + + float cos_theta = glm::clamp(glm::dot(from, to), -1.0f, 1.0f); + + if (cos_theta > 0.9995f) { + return glm::normalize(glm::mix(from, to, t)); + } + + if (cos_theta < -0.9995f) { + + glm::vec3 axis = (std::fabs(from.x) < 0.9f) + ? glm::vec3(1.0f, 0.0f, 0.0f) + : glm::vec3(0.0f, 1.0f, 0.0f); + glm::vec3 ortho = glm::normalize(glm::cross(from, axis)); + + float angle = glm::pi() * t; + + glm::vec3 rotated = + from * std::cos(angle) + glm::cross(ortho, from) * std::sin(angle); + + return glm::normalize(rotated); + } + + float theta = std::acos(cos_theta); + float sin_theta = std::sin(theta); + + float a = std::sin((1.0f - t) * theta) / sin_theta; + float b = std::sin(t * theta) / sin_theta; + + return glm::normalize(a * from + b * to); +} + +inline float distance2(const glm::vec3& a, const glm::vec3& b) { + glm::vec3 diff = a - b; + return glm::dot(diff, diff); +} } // namespace Math diff --git a/include/Cubed/tools/priority_thread_pool.hpp b/include/Cubed/tools/priority_thread_pool.hpp new file mode 100644 index 0000000..a1059ce --- /dev/null +++ b/include/Cubed/tools/priority_thread_pool.hpp @@ -0,0 +1,151 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +namespace Cubed { +class PriorityThreadPool { +private: + struct Task { + int priority = 10; + std::uint64_t sequence; + std::function task; + Task(int p, std::uint64_t seq, std::function t) + : priority(p), sequence(seq), task(std::move(t)) {} + }; + + struct TaskCompare { + bool operator()(const Task& a, const Task& b) const { + + if (a.priority != b.priority) { + return a.priority > b.priority; + } + + return a.sequence > b.sequence; + } + }; + + std::vector m_workers; + std::priority_queue, TaskCompare> m_tasks; + std::mutex m_mtx; + std::condition_variable_any m_cv; + std::atomic m_stopping{false}; + std::atomic m_thread_sum{0}; + std::atomic_uint64_t m_sequence{0}; + +public: + PriorityThreadPool(const PriorityThreadPool&) = delete; + PriorityThreadPool(PriorityThreadPool&&) = delete; + PriorityThreadPool& operator=(const PriorityThreadPool&) = delete; + PriorityThreadPool& operator=(PriorityThreadPool&&) = delete; + explicit PriorityThreadPool(size_t thread_sum) : m_thread_sum(thread_sum) { + for (size_t i = 0; i < thread_sum; i++) { + m_workers.emplace_back([this](std::stop_token stoken) { + while (true) { + std::function task; + { + std::unique_lock lock(m_mtx); + m_cv.wait(lock, stoken, + [this] { return !m_tasks.empty(); }); + if (stoken.stop_requested() && m_tasks.empty()) { + return; + } + task = std::move(m_tasks.top().task); + m_tasks.pop(); + } + task(); + } + }); + } + } + ~PriorityThreadPool() { stop(); } + template auto enqueue(int priority, F&& f) { + + using R = std::invoke_result_t; + + auto task = + std::make_shared>(std::forward(f)); + auto fut = task->get_future(); + + { + std::lock_guard lock(m_mtx); + if (m_stopping) + throw std::runtime_error("thread pool stopped"); + m_tasks.emplace(priority, m_sequence++, [task] { (*task)(); }); + } + m_cv.notify_one(); + return fut; + } + + template auto enqueue(F&& f) { + return enqueue(10, std::forward(f)); + } + + void stop() { + if (m_stopping.exchange(true)) { + return; + } + + for (auto& w : m_workers) { + w.request_stop(); + } + + m_cv.notify_all(); + + for (auto& w : m_workers) { + if (w.joinable()) { + w.join(); + } + } + } + size_t thread_sum() const { return m_thread_sum.load(); } +}; + +template +void parallel_do(PriorityThreadPool& pool, Iter first, Iter last, + size_t max_threads, F&& f) { + max_threads = std::max(1, max_threads); + max_threads = std::min(max_threads, pool.thread_sum()); + std::decay_t fn(std::forward(f)); + size_t length = std::distance(first, last); + if (!length) { + return; + } + + constexpr size_t MIN_PER_THREAD = 25; + size_t num_blocks = + std::min(max_threads, (length + MIN_PER_THREAD - 1) / MIN_PER_THREAD); + num_blocks = std::max(1, num_blocks); + size_t block_size = (length + num_blocks - 1) / num_blocks; + + std::vector> futures; + futures.reserve(num_blocks - 1); + Iter block_start = first; + for (size_t i = 0; i < num_blocks - 1; ++i) { + Iter block_end = block_start; + auto remain = std::distance(block_start, last); + std::advance(block_end, std::min(block_size, remain)); + + futures.emplace_back(pool.enqueue([block_start, block_end, &fn]() { + for (auto it = block_start; it != block_end; ++it) { + fn(*it); + } + })); + + block_start = block_end; + } + for (auto it = block_start; it != last; ++it) { + fn(*it); + } + + for (auto& fut : futures) { + fut.get(); + } +}; + +} // namespace Cubed diff --git a/include/Cubed/tools/recent_queue.hpp b/include/Cubed/tools/recent_queue.hpp new file mode 100644 index 0000000..cb0f795 --- /dev/null +++ b/include/Cubed/tools/recent_queue.hpp @@ -0,0 +1,55 @@ +#pragma once +#include +#include +namespace Cubed { +template class RecentQueue { +private: + std::list m_list; + std::unordered_map::iterator> m_map; + +public: + void enqueue(T key) { + auto it = m_map.find(key); + if (it != m_map.end()) { + m_list.splice(m_list.end(), m_list, it->second); + return; + } + m_list.emplace_back(std::move(key)); + auto iter = std::prev(m_list.end()); + m_map.emplace(*iter, iter); + } + + void pop() { + if (m_list.empty()) { + return; + } + m_map.erase(m_list.front()); + m_list.pop_front(); + } + void clear() { + m_list.clear(); + m_map.clear(); + } + + const T& front() const { + assert(!empty()); + return m_list.front(); + } + const T& back() const { + assert(!empty()); + return m_list.back(); + } + [[nodiscard]] + bool empty() const { + return m_list.empty(); + } + [[nodiscard]] + size_t size() const { + return m_list.size(); + } + [[nodiscard]] + bool contains(const T& key) const { + return m_map.find(key) != m_map.end(); + } +}; +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/tools/thread_pool.hpp b/include/Cubed/tools/thread_pool.hpp index 5b53eaa..d55c009 100644 --- a/include/Cubed/tools/thread_pool.hpp +++ b/include/Cubed/tools/thread_pool.hpp @@ -31,7 +31,7 @@ public: { std::unique_lock lock(m_mtx); m_cv.wait(lock, stoken, - [this, stoken] { return !m_tasks.empty(); }); + [this] { return !m_tasks.empty(); }); if (stoken.stop_requested() && m_tasks.empty()) { return; } @@ -62,7 +62,9 @@ public: return fut; } void stop() { - m_stopping = true; + if (m_stopping.exchange(true)) { + return; + } for (auto& w : m_workers) { w.request_stop(); } diff --git a/include/Cubed/tools/toml.utils.hpp b/include/Cubed/tools/toml.utils.hpp new file mode 100644 index 0000000..97a2984 --- /dev/null +++ b/include/Cubed/tools/toml.utils.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include "Cubed/tools/log.hpp" + +#include +namespace Cubed { +namespace TOML { + +template +concept TomlValueType = + std::same_as, int> || std::same_as, bool> || + std::same_as, double> || + std::same_as, char> || + std::same_as, toml::date> || + std::same_as, toml::time> || + std::same_as, toml::date_time> || + std::same_as, std::string>; + +template +std::optional safe_get_value(const toml::table& table, std::string_view key, + const T& default_value) { + auto value = table[key].value(); + if (value == std::nullopt) { + Logger::warn("Key {} Is Not Find, Wiil Set the Default Value {}", key, + default_value); + value = default_value; + } + return value; +} + +} // namespace TOML + +} // namespace Cubed diff --git a/include/Cubed/tools/uuid.hpp b/include/Cubed/tools/uuid.hpp new file mode 100644 index 0000000..8956586 --- /dev/null +++ b/include/Cubed/tools/uuid.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +namespace Cubed { +inline std::string generate_uuid() { + + static std::mt19937_64 rng( + std::chrono::steady_clock::now().time_since_epoch().count() ^ + (std::random_device{}())); + std::uniform_int_distribution dist(0, UINT64_MAX); + + uint64_t a = dist(rng); + uint64_t b = dist(rng); + + std::array bytes{}; + for (int i = 0; i < 8; ++i) { + bytes[i] = (a >> (56 - 8 * i)) & 0xFF; + bytes[8 + i] = (b >> (56 - 8 * i)) & 0xFF; + } + + bytes[6] = (bytes[6] & 0x0F) | 0x40; + + bytes[8] = (bytes[8] & 0x3F) | 0x80; + + std::ostringstream ss; + ss << std::hex << std::setfill('0'); + for (size_t i = 0; i < bytes.size(); ++i) { + if (i == 4 || i == 6 || i == 8 || i == 10) { + ss << '-'; + } + ss << std::setw(2) << static_cast(bytes[i]); + } + return ss.str(); +} +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/window.hpp b/include/Cubed/window.hpp index 6f8f95c..4d23be0 100644 --- a/include/Cubed/window.hpp +++ b/include/Cubed/window.hpp @@ -23,6 +23,7 @@ public: private: bool m_mouse_enable = false; + bool m_imgui_init = false; float m_aspect; GLFWwindow* m_window; int m_width; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..2b3a0a9 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,46 @@ +target_sources(${PROJECT_NAME} + PRIVATE + main.cpp + app.cpp + debug_collector.cpp + camera.cpp + config.cpp + dev_panel.cpp + gameplay/biome.cpp + gameplay/chunk_generator.cpp + gameplay/tree.cpp + input.cpp + map_table.cpp + renderer.cpp + shader.cpp + texture_manager.cpp + tools/cubed_random.cpp + tools/shader_tools.cpp + tools/font.cpp + tools/perlin_noise.cpp + ui/text.cpp + window.cpp + gameplay/builders/biome_builder.cpp + gameplay/builders/plain_builder.cpp + gameplay/builders/mountain_builder.cpp + gameplay/builders/river_builder.cpp + gameplay/builders/desert_builder.cpp + gameplay/builders/forest_builder.cpp + gameplay/cave_carver.cpp + gameplay/cave_path.cpp + gameplay/builders/snowy_plain_builder.cpp + gameplay/river_worm.cpp + gameplay/river_path.cpp + gameplay/block.cpp + gameplay/vertex_data.cpp + gameplay/builders/ocean_builder.cpp + gameplay/network_server.cpp + gameplay/server_world.cpp + gameplay/client_world.cpp + gameplay/server_chunk.cpp + gameplay/client_chunk.cpp + gameplay/server_player.cpp + gameplay/client_player.cpp + gameplay/session.cpp + gameplay/network_client.cpp +) \ No newline at end of file diff --git a/src/app.cpp b/src/app.cpp index ad0b45c..0929dba 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -2,19 +2,23 @@ #include "Cubed/config.hpp" #include "Cubed/debug_collector.hpp" -#include "Cubed/gameplay/player.hpp" +#include "Cubed/tools/arg_parser.hpp" #include "Cubed/tools/cubed_assert.hpp" #include "Cubed/tools/log.hpp" #include "Cubed/tools/system_info.hpp" +#include "version.hpp" #include #include - namespace Cubed { App::App() {} -App::~App() {} +App::~App() { + if (m_client) { + m_client->stop(); + } +} void App::cursor_position_callback(GLFWwindow* window, double xpos, double ypos) { ImGuiIO& io = ImGui::GetIO(); @@ -30,9 +34,13 @@ void App::cursor_position_callback(GLFWwindow* window, double xpos, app->m_camera.update_cursor_position_camera(xpos, ypos); } } -void App::init() { +void App::init(int argc, char** argv) { + handle_toml(); + handle_argument(argc, argv); + m_window.init(); m_window.imgui_init(); + Logger::info("Window Init Success"); glfwSetWindowUserPointer(m_window.get_glfw_window(), this); @@ -50,6 +58,7 @@ void App::init() { glfwSetCursorEnterCallback(m_window.get_glfw_window(), cursor_enter_callback); glfwSetCharCallback(m_window.get_glfw_window(), char_callback); + ChunkGenerator::init(); BlockManager::init(); m_renderer.init(); @@ -58,13 +67,94 @@ void App::init() { // MapTable::init_map(); m_texture_manager.init_texture(); Logger::info("Texture Load Success"); - m_world.init_world(); + if (!m_argument.is_client) { + m_server.start_server(m_argument.port); + } + + m_client = std::make_shared(m_client_world); + + m_client->start(m_argument.ip, m_argument.port); + // init will send packet + m_client_world.init(m_argument.player, m_client); + Logger::info("World Init Success"); - m_camera.camera_init(&m_world.get_player("TestPlayer")); + m_camera.camera_init(&m_client_world.get_player()); m_dev_panel.init(); } +void App::handle_argument(int argc, char** argv) { + + static const std::unordered_map> + HANDLERS{ + + {"--client", [&](ArgParser&) { m_argument.is_client = true; }}, + + {"--host", [&](ArgParser&) { m_argument.is_client = false; }}, + + {"-p", + [&](ArgParser& p) { + auto arg = p.require_next("-p"); + + auto r = std::from_chars(arg.data(), arg.data() + arg.size(), + m_argument.port); + + if (r.ec != std::errc{} || r.ptr != arg.data() + arg.size()) { + throw std::runtime_error( + std::format("Invalid port: {}", arg)); + } + + if (m_argument.port > 65535) { + throw std::runtime_error( + std::format("Port {} out of range", m_argument.port)); + } + }}, + + {"--ip", + [&](ArgParser& p) { + auto arg = p.require_next("--ip"); + m_argument.ip = arg; + }}, + {"--player", + [&](ArgParser& p) { + auto arg = p.require_next("--player"); + m_argument.player = arg; + }}, + {"-V", + [&](ArgParser) { + std::cout << CUBED_VERSION << "\n"; + exit(EXIT_SUCCESS); + }} + + }; + ArgParser parser(argc, argv); + + while (parser.has_next()) { + auto arg = parser.next(); + if (auto it = HANDLERS.find(arg); it != HANDLERS.end()) { + it->second(parser); + } else { + Logger::warn("Unknown argument: {}", arg); + } + } +} + +void App::handle_toml() { + toml::table server; + try { + server = toml::parse_file("server.toml"); + } catch (const toml::parse_error& e) { + // Logger::warn("Ip toml parse error {}", e.what()); + return; + } + + m_argument.ip = + *TOML::safe_get_value(server, "ip", std::string("127.0.01")); + m_argument.port = *TOML::safe_get_value(server, "port", 25530); + m_argument.is_client = *TOML::safe_get_value(server, "client", false); +} + void App::key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) { ImGuiIO& io = ImGui::GetIO(); @@ -111,7 +201,7 @@ void App::key_callback(GLFWwindow* window, int key, int scancode, int action, break; } - app->m_world.get_player("TestPlayer").update_player_move_state(key, action); + app->m_client_world.get_player().update_player_move_state(key, action); } void App::mouse_button_callback(GLFWwindow* window, int button, int action, @@ -162,8 +252,7 @@ void App::window_focus_callback(GLFWwindow* window, int focused) { } } -void App::window_reshape_callback(GLFWwindow* window, int new_width, - int new_height) { +void App::window_reshape_callback(GLFWwindow* window, int, int) { App* app = static_cast(glfwGetWindowUserPointer(window)); ASSERT_MSG(app, "nullptr"); @@ -180,7 +269,7 @@ void App::mouse_scroll_callback(GLFWwindow* window, double xoffset, ImGui_ImplGlfw_ScrollCallback(window, xoffset, yoffset); return; } - auto& player = app->m_world.get_player("TestPlayer"); + auto& player = app->m_client_world.get_player(); player.update_scroll(yoffset); } @@ -218,10 +307,16 @@ void App::run() { last_time = glfwGetTime(); while (!glfwWindowShouldClose(m_window.get_glfw_window())) { - + if (m_client_world.is_receive_exit()) { + break; + } update(); render(); } + m_client_world.request_exit(); + if (!m_argument.is_client) { + m_server.server_world().stop(); + } } static Gait player_gait = Gait::WALK; void App::update() { @@ -244,9 +339,9 @@ void App::update() { std::format("RSS: {}mb", Tools::get_current_rss() / (1024 * 1024))); } m_texture_manager.update(); - m_world.update(delta_time); + m_client_world.update(delta_time); m_camera.update_move_camera(); - const auto& player = m_world.get_player("TestPlayer"); + const auto& player = m_client_world.get_player(); if (player_gait != player.get_gait()) { player_gait = player.get_gait(); float fov = static_cast(Config::get().get("player.fov")); @@ -265,7 +360,7 @@ int App::start_cubed_application(int argc, char** argv) { App app; try { - app.init(); + app.init(argc, argv); Logger::info("Game Init Finish Start Run..."); app.run(); @@ -288,6 +383,7 @@ DevPanel& App::dev_panel() { return m_dev_panel; } Renderer& App::renderer() { return m_renderer; } TextureManager& App::texture_manager() { return m_texture_manager; } Window& App::window() { return m_window; } -World& App::world() { return m_world; } - +ClientWorld& App::client_world() { return m_client_world; } +ServerWorld& App::server_world() { return m_server.server_world(); } +const App::Argument& App::argument() const { return m_argument; } } // namespace Cubed \ No newline at end of file diff --git a/src/camera.cpp b/src/camera.cpp index 22562a2..74d4e7a 100644 --- a/src/camera.cpp +++ b/src/camera.cpp @@ -1,7 +1,7 @@ #include "Cubed/camera.hpp" -#include "Cubed/gameplay/player.hpp" -#include "Cubed/gameplay/world.hpp" +#include "Cubed/gameplay/client_player.hpp" +#include "Cubed/gameplay/client_world.hpp" #include "Cubed/tools/cubed_assert.hpp" namespace Cubed { @@ -22,7 +22,7 @@ void Camera::update_move_camera() { } } -void Camera::camera_init(Player* player) { +void Camera::camera_init(ClientPlayer* player) { m_player = player; update_move_camera(); reset_camera(); diff --git a/src/config.cpp b/src/config.cpp index 01cf6e1..239ed28 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -24,7 +24,6 @@ toml::table& Config::table() { return m_tbl; } void Config::create_config() { static constexpr auto SOURCE = R"( - version = "0.0.1" [window] width = 800 diff --git a/src/debug_collector.cpp b/src/debug_collector.cpp index 3a399ed..f579538 100644 --- a/src/debug_collector.cpp +++ b/src/debug_collector.cpp @@ -1,8 +1,8 @@ #include "Cubed/debug_collector.hpp" -#include "Cubed/config.hpp" #include "Cubed/tools/cubed_hash.hpp" #include "Cubed/tools/system_info.hpp" +#include "version.hpp" namespace Cubed { @@ -24,10 +24,16 @@ void DebugCollector::init_text() { Text opengl_version_text("opengl_version"); Text biome_text("biome"); Text speed_text("speed"); + std::string version{"Version: " CUBED_VERSION}; +#ifdef DEBUG_MODE + version.append("-debug"); +#else + version.append("-release"); +#endif version_text.position(0.0f, 100.0f) .scale(0.8f) .color(Color::WHITE) - .text("Version: " + Config::get().get("version")); + .text(version); fps_text.position(0.0f, 50.0f).text("FPS: 0"); player_pos_text.position(0.0f, 150.0f) .scale(0.8f) diff --git a/src/dev_panel.cpp b/src/dev_panel.cpp index 49feada..a646159 100644 --- a/src/dev_panel.cpp +++ b/src/dev_panel.cpp @@ -3,7 +3,7 @@ #include "Cubed/app.hpp" #include "Cubed/config.hpp" #include "Cubed/gameplay/cave_path.hpp" -#include "Cubed/gameplay/player.hpp" +#include "Cubed/gameplay/client_player.hpp" #include "Cubed/gameplay/river.path.hpp" #include "Cubed/tools/log.hpp" @@ -16,7 +16,7 @@ namespace Cubed { static constexpr const char* THEMES[] = {"Dark", "Light"}; static constexpr const char* GAITS[] = {"Walk", "Run"}; static constexpr const char* GAME_MODES[] = {"Creative", "Spectator"}; -static char perlin_noise_input_buffer[64]; +static constexpr const char* CHUNK_LOAD_STYLE[] = {"Random", "Center"}; constexpr float TEMP_MIN = 0.0f; constexpr float TEMP_MAX = 1.0f; @@ -46,20 +46,10 @@ constexpr float DELTA_ANGLE_MAX = 30.0f; constexpr int PATH_STEP_MIN = 1; constexpr int PATH_STEP_MAX = 1000; -static int filter_unsigned(ImGuiInputTextCallbackData* data) { - if (data->EventFlag == ImGuiInputTextFlags_CallbackCharFilter) { - char c = data->EventChar; - if (c < '0' || c > '9') { - return 1; - } - } - return 0; -} - DevPanel::DevPanel(App& app) : m_app(app) {} void DevPanel::init() { - m_player = &m_app.world().get_player("TestPlayer"); + m_player = &m_app.client_world().get_player(); update_config_view(); update_player_profile(); } @@ -111,6 +101,9 @@ void DevPanel::show_about_table_bar() { ImGui::Text("toml++"); ImGui::Text("Dear ImGui"); ImGui::Text("Tbb"); + ImGui::Text("Asio"); + ImGui::Text("protobuf"); + ImGui::Text("zstd"); ImGui::Separator(); ImGui::Text("Special Thanks"); ImGui::Text("TANGERIME"); @@ -268,7 +261,7 @@ void DevPanel::show_biome_table_bar() { } void DevPanel::show_time_table_bar() { - World& world = m_app.world(); + ServerWorld& world = m_app.server_world(); ImGui::Text("Game Tick %llu", world.game_tick()); ImGui::SameLine(); ImGui::Text("Day Tick %llu", world.day_tick()); @@ -341,17 +334,17 @@ void DevPanel::show_river_table_bar() { } void DevPanel::show_chunk_table_bar() { - auto& world = m_app.world(); - auto& player = world.get_player("TestPlayer"); - auto info = world.get_chunk_info(player.get_player_pos()); + /* + auto& world = m_app.client_world(); + auto& player = world.get_player(); - ImGui::Text("Chunk X: %d Z: %d Info", info.pos.x, info.pos.z); - ImGui::Text("Seed: %u", info.seed); - ImGui::Text("%s", ("Biome " + get_biome_str(info.biome)).c_str()); - ImGui::Text("First Random %u", info.first_random); - ImGui::Text("%s", - std::format("Has Cave Start {}", info.has_cave_start).c_str()); - ImGui::Text("%s", std::format("Has Cave {}", info.has_cave).c_str()); + ImGui::Text("Chunk X: %d Z: %d Info", info.pos.x, info.pos.z); + ImGui::Text("Seed: %u", info.seed); + ImGui::Text("%s", ("Biome " + get_biome_str(info.biome)).c_str()); + ImGui::Text("First Random %u", info.first_random); + ImGui::Text("%s", + std::format("Has Cave Start {}", info.has_cave_start).c_str()); + ImGui::Text("%s", std::format("Has Cave {}", info.has_cave).c_str());*/ } void DevPanel::show_settings_tab_item() { @@ -384,7 +377,7 @@ void DevPanel::show_settings_tab_item() { 128)) { Config::get().set("world.rendering_distance", m_config.rendering_distance); - m_app.world().hot_reload(); + m_app.client_world().hot_reload(); } if (ImGui::Checkbox("Fullscreen", &m_config.fullscreen)) { Config::get().set("window.fullscreen", m_config.fullscreen); @@ -455,106 +448,116 @@ void DevPanel::show_settings_tab_item() { void DevPanel::show_world_tab_item() { if (ImGui::BeginTabItem("world")) { - if (m_text_editing.perlin_seed) { - if (ImGui::InputText("ChunkGenerator Seed", - perlin_noise_input_buffer, - sizeof(perlin_noise_input_buffer), - ImGuiInputTextFlags_CallbackCharFilter | - ImGuiInputTextFlags_EnterReturnsTrue, - filter_unsigned)) { - ChunkGenerator::seed(static_cast( - std::strtoul(perlin_noise_input_buffer, nullptr, 10))); - m_text_editing.perlin_seed = false; - m_player->set_player_pos({0.0f, 255.0f, 0.0f}); - m_app.world().rebuild_world(); - } - } - if (!m_text_editing.perlin_seed) { - ImGui::Text("ChunkGenerator Seed: %u", ChunkGenerator::seed()); - if (ImGui::IsItemClicked()) { - m_text_editing.perlin_seed = true; - } - } - static int rendering_distance = m_app.world().rendering_distance(); - if (ImGui::SliderInt("Render Distance", &rendering_distance, 2, 128)) { - m_app.world().rendering_distance(rendering_distance); - } - ImGui::Text( - "Pool Threads %d Max Support Threads %d Reserved Threads %d", - m_app.world().pool_threads(), m_app.world().max_threads(), - RESERVED_THREADS); - ImGui::SliderInt("Set Pool Threads", &m_threads, 1, - m_app.world().max_threads()); - ImGui::SameLine(); - if (ImGui::Button("Set")) { - m_app.world().change_pool_threads(m_threads); - } - if (m_threads > m_app.world().max_threads() - RESERVED_THREADS) { - ImGui::TextColored( - ImVec4(1.0f, 1.0f, 0.0f, 1.0f), - "Waring: When the threads in the thread pool exceed \n(maximum " - "threads minus reserved threads), \nit may cause stuttering."); - } - static const char* chunk_load_style[] = {"Random", "Center"}; - m_chunk_style = m_app.world().chunk_load_style(); - if (ImGui::Combo("ChunkLoadStyle", &m_chunk_style, chunk_load_style, - IM_ARRAYSIZE(chunk_load_style))) { - m_app.world().set_chunk_load_style(m_chunk_style); - } - if (ImGui::Button("Rebuild World")) { - m_app.world().rebuild_world(); - } - ImGui::SameLine(); - if (ImGui::Button("Request Chunk Build")) { - m_app.world().need_gen(); - } - ImGui::SameLine(); - if (ImGui::Button("Spawn Point")) { - m_player->set_player_pos({0.0f, 255.0f, 0.0f}); - } - ImGui::SameLine(); - if (ImGui::Checkbox("Gen Thread", &m_gen_thread_running)) { - if (m_gen_thread_running) { - m_app.world().start_gen_thread(); - } else { - m_app.world().stop_gen_thread(); + if (ImGui::BeginTabBar("World Kind")) { + if (!m_app.argument().is_client) { + if (ImGui::BeginTabItem("ServerWorld")) { + show_server_world_table_bar(); + ImGui::EndTabItem(); + } } - } - // ImGui::Text("Chunk Build Progress\n"); - // ImGui::ProgressBar(m_app.world().chunk_gen_fraction()); - show_chunk_table_bar(); - if (ImGui::BeginTabBar("World Settings")) { - if (ImGui::BeginTabItem("Time")) { - show_time_table_bar(); + if (ImGui::BeginTabItem("Client World")) { + show_client_world_table_bar(); ImGui::EndTabItem(); } - /* - if (ImGui::BeginTabItem("Cave")) { - show_cave_table_bar(); - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("River")) { - show_river_table_bar(); - ImGui::EndTabItem(); - }*/ - if (ImGui::BeginTabItem("Biome")) { - show_biome_table_bar(); - ImGui::EndTabItem(); - } - ImGui::EndTabBar(); } + + // ImGui::Text("Chunk Build Progress\n"); + // ImGui::ProgressBar(m_app.world().chunk_gen_fraction()); + // show_chunk_table_bar(); + ImGui::EndTabItem(); } } +void DevPanel::show_server_world_table_bar() { + + ImGui::Text("ChunkGenerator Seed: %u", ChunkGenerator::seed()); + + ImGui::Text("Pool Threads %d Max Support Threads %d Reserved Threads %d", + m_app.server_world().gen_pool_threads(), + m_app.server_world().max_threads(), RESERVED_THREADS); + ImGui::SliderInt("Set Pool Threads", &m_threads, 1, + m_app.server_world().max_threads()); + ImGui::SameLine(); + if (ImGui::Button("Set")) { + m_app.server_world().change_pool_threads( + ServerWorld::ThreadPoolKind::GEN, m_threads); + } + if (m_threads > m_app.server_world().max_threads() - RESERVED_THREADS) { + ImGui::TextColored( + ImVec4(1.0f, 1.0f, 0.0f, 1.0f), + "Waring: When the threads in the thread pool exceed \n(maximum " + "threads minus reserved threads), \nit may cause stuttering."); + } + + m_chunk_style = m_app.server_world().chunk_load_style(); + if (ImGui::Combo("ChunkLoadStyle", &m_chunk_style, CHUNK_LOAD_STYLE, + IM_ARRAYSIZE(CHUNK_LOAD_STYLE))) { + m_app.server_world().set_chunk_load_style(m_chunk_style); + } + + if (ImGui::Button("Request Chunk Build")) { + m_app.server_world().need_gen(m_player->get_uuid()); + } + ImGui::SameLine(); + if (ImGui::Checkbox("Gen Thread", &m_gen_thread_running)) { + if (m_gen_thread_running) { + m_app.server_world().start_gen_thread(); + } else { + m_app.server_world().stop_gen_thread(); + } + } + ImGui::Text("Server Chunk Size %d", m_app.server_world().chunk_size()); + + if (ImGui::BeginTabBar("World Settings")) { + if (ImGui::BeginTabItem("Time")) { + show_time_table_bar(); + ImGui::EndTabItem(); + } + /* + if (ImGui::BeginTabItem("Cave")) { + show_cave_table_bar(); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("River")) { + show_river_table_bar(); + ImGui::EndTabItem(); + }*/ + if (ImGui::BeginTabItem("Biome")) { + show_biome_table_bar(); + ImGui::EndTabItem(); + } + + ImGui::EndTabBar(); + } +} +void DevPanel::show_client_world_table_bar() { + + static int rendering_distance = m_app.client_world().rendering_distance(); + if (ImGui::SliderInt("Render Distance", &rendering_distance, 2, 128)) { + m_app.client_world().rendering_distance(rendering_distance); + // Config::get().set("world.rendering_distance", rendering_distance); + } + if (ImGui::Button("Rebuild World")) { + m_app.client_world().rebuild_world(); + } + ImGui::SameLine(); + if (ImGui::Button("Spawn Point")) { + m_player->set_player_pos({0.0f, 255.0f, 0.0f}); + } + ImGui::Text("Chunk Task Id %d", m_app.client_world().get_chunk_task_id()); + ImGui::Text("Client World Chunk %d", m_app.client_world().chunk_size()); +} + void DevPanel::show_player_tab_item() { if (!m_player) { Logger::error("Player is Nullptr"); return; } if (ImGui::BeginTabItem("player")) { + ImGui::Text("Player %s", m_player->get_name().c_str()); if (ImGui::Combo("GameMode", &m_player_profile.game_mode, GAME_MODES, IM_ARRAYSIZE(GAME_MODES))) { if (m_player_profile.game_mode == 0) { @@ -667,8 +670,9 @@ void DevPanel::show_shader_tab_item() { ImGui::Checkbox("Flip Y", &m_app.renderer().flip_y()); if (ImGui::SliderFloat("AmbientStrength", &m_app.renderer().ambient_strength(), 0.0f, - 0.35f)) - ; + 0.35f)) { + } + ImGui::SliderFloat("SpecularStrength", &m_app.renderer().specular_strength(), 0.0f, 2.0f); ImGui::Checkbox("Discard Transparent", diff --git a/src/block.cpp b/src/gameplay/block.cpp similarity index 92% rename from src/block.cpp rename to src/gameplay/block.cpp index 9be8c94..4f4d80b 100644 --- a/src/block.cpp +++ b/src/gameplay/block.cpp @@ -1,31 +1,18 @@ #include "Cubed/gameplay/block.hpp" -#include "Cubed/config.hpp" #include "Cubed/tools/cubed_assert.hpp" #include "Cubed/tools/log.hpp" +#include "Cubed/tools/toml.utils.hpp" #include -#include namespace fs = std::filesystem; using namespace std::string_literals; - +using namespace Cubed::TOML; namespace { std::string block_data_dir = ASSETS_PATH + "data/block"s; -template -std::optional safe_get_value(const toml::table& table, std::string_view key, - const T& default_value) { - auto value = table[key].value(); - if (value == std::nullopt) { - Cubed::Logger::warn("Key {} Is Not Find, Wiil Set the Default Value {}", - key, default_value); - value = default_value; - } - return value; -} - } // namespace namespace Cubed { diff --git a/src/gameplay/builders/biome_builder.cpp b/src/gameplay/builders/biome_builder.cpp index 9c7b901..788b514 100644 --- a/src/gameplay/builders/biome_builder.cpp +++ b/src/gameplay/builders/biome_builder.cpp @@ -1,39 +1,39 @@ #include "Cubed/gameplay/builders/biome_builder.hpp" -#include "Cubed/gameplay/chunk.hpp" #include "Cubed/gameplay/chunk_generator.hpp" +#include "Cubed/gameplay/server_chunk.hpp" namespace Cubed { void BiomeBuilder::build_bottom() { ChunkGenerator& chunk_generator = get_chunk_generator(); - Chunk& chunk = chunk_generator.chunk(); + ServerChunk& chunk = chunk_generator.chunk(); auto& m_blocks = chunk.blocks(); for (int x = 0; x < CHUNK_SIZE; x++) { for (int y = 0; y < 5; y++) { for (int z = 0; z < CHUNK_SIZE; z++) { - m_blocks[Chunk::index(x, y, z)] = 3; + m_blocks[ServerChunk::index(x, y, z)] = 3; } } } } void BiomeBuilder::place_grass() { ChunkGenerator& chunk_generator = get_chunk_generator(); - Chunk& chunk = chunk_generator.chunk(); + ServerChunk& chunk = chunk_generator.chunk(); auto& blocks = chunk.blocks(); const auto& heightmap = chunk.get_heightmap(); auto& random = chunk_generator.random(); for (int x = 0; x < SIZE_X; ++x) { for (int z = 0; z < SIZE_Z; ++z) { int y = heightmap[x][z]; - BlockType top_id = blocks[Chunk::index(x, y, z)]; + BlockType top_id = blocks[ServerChunk::index(x, y, z)]; if (top_id != 1) { continue; } - if (blocks[Chunk::index(x, y + 1, z)] != 0) { + if (blocks[ServerChunk::index(x, y + 1, z)] != 0) { continue; } if (random.random_bool(0.2)) { if (y + 1 < SIZE_Y) { - blocks[Chunk::index(x, y + 1, z)] = 9; + blocks[ServerChunk::index(x, y + 1, z)] = 9; } } } @@ -42,7 +42,7 @@ void BiomeBuilder::place_grass() { void BiomeBuilder::ocean_water_build() { ChunkGenerator& chunk_generator = get_chunk_generator(); - Chunk& chunk = chunk_generator.chunk(); + ServerChunk& chunk = chunk_generator.chunk(); auto& blocks = chunk.blocks(); const auto& heightmap = chunk.get_heightmap(); @@ -51,7 +51,7 @@ void BiomeBuilder::ocean_water_build() { int height = heightmap[x][z]; if (height <= SEA_LEVEL) { for (int y = height; y <= SEA_LEVEL; y++) { - blocks[Chunk::index(x, y, z)] = 7; + blocks[ServerChunk::index(x, y, z)] = 7; } } } diff --git a/src/gameplay/builders/desert_builder.cpp b/src/gameplay/builders/desert_builder.cpp index 2b60cfa..fe90ee6 100644 --- a/src/gameplay/builders/desert_builder.cpp +++ b/src/gameplay/builders/desert_builder.cpp @@ -1,7 +1,7 @@ #include "Cubed/gameplay/builders/desert_builder.hpp" -#include "Cubed/gameplay/chunk.hpp" #include "Cubed/gameplay/chunk_generator.hpp" +#include "Cubed/gameplay/server_chunk.hpp" namespace Cubed { DesertBuilder::DesertBuilder(ChunkGenerator& chunk_generator) : m_chunk_generator(chunk_generator) {} @@ -19,11 +19,11 @@ void DesertBuilder::build_blocks() { for (int z = 0; z < CHUNK_SIZE; z++) { int height = static_cast(m_heightmap[x][z]); for (int y = 5; y < height - 5; y++) { - m_blocks[Chunk::index(x, y, z)] = 3; + m_blocks[ServerChunk::index(x, y, z)] = 3; } for (int y = height - 5; y <= height; y++) { - m_blocks[Chunk::index(x, y, z)] = 4; + m_blocks[ServerChunk::index(x, y, z)] = 4; } } } diff --git a/src/gameplay/builders/forest_builder.cpp b/src/gameplay/builders/forest_builder.cpp index 8607b02..deb1a5d 100644 --- a/src/gameplay/builders/forest_builder.cpp +++ b/src/gameplay/builders/forest_builder.cpp @@ -1,7 +1,7 @@ #include "Cubed/gameplay/builders/forest_builder.hpp" -#include "Cubed/gameplay/chunk.hpp" #include "Cubed/gameplay/chunk_generator.hpp" +#include "Cubed/gameplay/server_chunk.hpp" #include "Cubed/gameplay/tree.hpp" #include @@ -24,12 +24,12 @@ void ForestBuilder::build_blocks() { for (int z = 0; z < CHUNK_SIZE; z++) { int height = static_cast(m_heightmap[x][z]); for (int y = 5; y < height - 5; y++) { - m_blocks[Chunk::index(x, y, z)] = 3; + m_blocks[ServerChunk::index(x, y, z)] = 3; } for (int y = height - 5; y < height; y++) { - m_blocks[Chunk::index(x, y, z)] = 2; + m_blocks[ServerChunk::index(x, y, z)] = 2; } - m_blocks[Chunk::index(x, height, z)] = 1; + m_blocks[ServerChunk::index(x, height, z)] = 1; } } } diff --git a/src/gameplay/builders/mountain_builder.cpp b/src/gameplay/builders/mountain_builder.cpp index 0c8c379..2640626 100644 --- a/src/gameplay/builders/mountain_builder.cpp +++ b/src/gameplay/builders/mountain_builder.cpp @@ -1,7 +1,7 @@ #include "Cubed/gameplay/builders/mountain_builder.hpp" -#include "Cubed/gameplay/chunk.hpp" #include "Cubed/gameplay/chunk_generator.hpp" +#include "Cubed/gameplay/server_chunk.hpp" namespace Cubed { MountainBuilder::MountainBuilder(ChunkGenerator& chunk_generator) : m_chunk_generator(chunk_generator) {} @@ -19,7 +19,7 @@ void MountainBuilder::build_blocks() { for (int z = 0; z < CHUNK_SIZE; z++) { int height = static_cast(m_heightmap[x][z]); for (int y = 5; y <= height; y++) { - m_blocks[Chunk::index(x, y, z)] = 3; + m_blocks[ServerChunk::index(x, y, z)] = 3; } } } diff --git a/src/gameplay/builders/ocean_builder.cpp b/src/gameplay/builders/ocean_builder.cpp index 123461e..8eae6e6 100644 --- a/src/gameplay/builders/ocean_builder.cpp +++ b/src/gameplay/builders/ocean_builder.cpp @@ -1,7 +1,7 @@ #include "Cubed/gameplay/builders/ocean_builder.hpp" -#include "Cubed/gameplay/chunk.hpp" #include "Cubed/gameplay/chunk_generator.hpp" +#include "Cubed/gameplay/server_chunk.hpp" namespace Cubed { OceanBuilder::OceanBuilder(ChunkGenerator& chunk_generator) : m_chunk_generator(chunk_generator) {} @@ -19,7 +19,7 @@ void OceanBuilder::build_blocks() { for (int z = 0; z < CHUNK_SIZE; z++) { int height = static_cast(m_heightmap[x][z]); for (int y = 5; y <= height; y++) { - m_blocks[Chunk::index(x, y, z)] = 3; + m_blocks[ServerChunk::index(x, y, z)] = 3; } } } diff --git a/src/gameplay/builders/plain_builder.cpp b/src/gameplay/builders/plain_builder.cpp index a900905..cb50760 100644 --- a/src/gameplay/builders/plain_builder.cpp +++ b/src/gameplay/builders/plain_builder.cpp @@ -1,7 +1,7 @@ #include "Cubed/gameplay/builders/plain_builder.hpp" -#include "Cubed/gameplay/chunk.hpp" #include "Cubed/gameplay/chunk_generator.hpp" +#include "Cubed/gameplay/server_chunk.hpp" namespace Cubed { PlainBuilder::PlainBuilder(ChunkGenerator& chunk_generator) : m_chunk_generator(chunk_generator) {} @@ -19,12 +19,12 @@ void PlainBuilder::build_blocks() { for (int z = 0; z < CHUNK_SIZE; z++) { int height = static_cast(m_heightmap[x][z]); for (int y = 5; y < height - 5; y++) { - m_blocks[Chunk::index(x, y, z)] = 3; + m_blocks[ServerChunk::index(x, y, z)] = 3; } for (int y = height - 5; y < height; y++) { - m_blocks[Chunk::index(x, y, z)] = 2; + m_blocks[ServerChunk::index(x, y, z)] = 2; } - m_blocks[Chunk::index(x, height, z)] = 1; + m_blocks[ServerChunk::index(x, height, z)] = 1; } } } diff --git a/src/gameplay/builders/snowy_plain_builder.cpp b/src/gameplay/builders/snowy_plain_builder.cpp index 189ce46..3f9d455 100644 --- a/src/gameplay/builders/snowy_plain_builder.cpp +++ b/src/gameplay/builders/snowy_plain_builder.cpp @@ -1,7 +1,7 @@ #include "Cubed/gameplay/builders/snowy_plain_builder.hpp" -#include "Cubed/gameplay/chunk.hpp" #include "Cubed/gameplay/chunk_generator.hpp" +#include "Cubed/gameplay/server_chunk.hpp" namespace Cubed { SnowyPlainBuilder::SnowyPlainBuilder(ChunkGenerator& chunk_generator) : m_chunk_generator(chunk_generator) {} @@ -19,12 +19,12 @@ void SnowyPlainBuilder::build_blocks() { for (int z = 0; z < CHUNK_SIZE; z++) { int height = static_cast(m_heightmap[x][z]); for (int y = 5; y < height - 5; y++) { - m_blocks[Chunk::index(x, y, z)] = 3; + m_blocks[ServerChunk::index(x, y, z)] = 3; } for (int y = height - 5; y < height; y++) { - m_blocks[Chunk::index(x, y, z)] = 2; + m_blocks[ServerChunk::index(x, y, z)] = 2; } - m_blocks[Chunk::index(x, height, z)] = 8; + m_blocks[ServerChunk::index(x, height, z)] = 8; } } } diff --git a/src/gameplay/chunk_generator.cpp b/src/gameplay/chunk_generator.cpp index 0868a13..d7b15c6 100644 --- a/src/gameplay/chunk_generator.cpp +++ b/src/gameplay/chunk_generator.cpp @@ -8,10 +8,10 @@ #include "Cubed/gameplay/builders/river_builder.hpp" #include "Cubed/gameplay/builders/snowy_plain_builder.hpp" #include "Cubed/gameplay/cave_path.hpp" -#include "Cubed/gameplay/chunk.hpp" #include "Cubed/gameplay/river.path.hpp" +#include "Cubed/gameplay/server_chunk.hpp" +#include "Cubed/gameplay/server_world.hpp" #include "Cubed/gameplay/tree.hpp" -#include "Cubed/gameplay/world.hpp" #include "Cubed/tools/cubed_assert.hpp" #include "Cubed/tools/cubed_hash.hpp" #include "Cubed/tools/math_tools.hpp" @@ -99,7 +99,7 @@ void carve_worm(const std::vector& points, const ChunkPos& chunk_pos, using enum BiomeType; constexpr int BLEND_RADIUS = 8; -ChunkGenerator::ChunkGenerator(Chunk& chunk) : m_chunk(chunk) { +ChunkGenerator::ChunkGenerator(ServerChunk& chunk) : m_chunk(chunk) { ASSERT_MSG(is_init, "ChunksGenerator is not init"); ChunkPos pos = m_chunk.get_chunk_pos(); unsigned seed = HASH::chunk_seed_hash(pos.x, pos.z, m_generator_seed); @@ -157,7 +157,7 @@ void ChunkGenerator::assign_chunk_biome() { } void ChunkGenerator::resolve_biome_adjacency_conflict( - const std::array& adj_chunks) { + const std::array& adj_chunks) { auto m_biome = m_chunk.biome(); for (int i = 0; i < 8; i++) { auto& chunk = adj_chunks[i]; @@ -555,7 +555,8 @@ void ChunkGenerator::blend_surface_blocks_borders( int nx, int nz) -> BlockType { // Search from topmost y downwards for the first non-zero block for (int y = WORLD_HEIGHT - 1; y >= 0; --y) { - int idx = Chunk::index(nx, y, + int idx = + ServerChunk::index(nx, y, nz); // linear index: y * area + z * size + x if (idx >= 0 && idx < static_cast(blocks.size())) { BlockType neighbor_type = blocks[idx]; @@ -574,7 +575,7 @@ void ChunkGenerator::blend_surface_blocks_borders( BlockType type_self = 0; int top_y = -1; top_y = m_heightmap[x][z]; - type_self = m_blocks[Chunk::index(x, top_y, z)]; + type_self = m_blocks[ServerChunk::index(x, top_y, z)]; if (top_y == -1) continue; // no block? skip @@ -665,7 +666,7 @@ void ChunkGenerator::blend_surface_blocks_borders( if (final_type != type_self) { // top block BlockType new_surface = final_type; - m_blocks[Chunk::index(x, top_y, z)] = new_surface; + m_blocks[ServerChunk::index(x, top_y, z)] = new_surface; // bottom block unsigned fill_type = 2; if (final_type == 1 || final_type == 8) { @@ -674,7 +675,7 @@ void ChunkGenerator::blend_surface_blocks_borders( fill_type = final_type; } for (int y = std::max(0, top_y - 5); y < top_y; y++) { - m_blocks[Chunk::index(x, y, z)] = fill_type; + m_blocks[ServerChunk::index(x, y, z)] = fill_type; } } } @@ -741,12 +742,12 @@ void ChunkGenerator::generate_cave() { carve_worm(path.points(), chunk_pos, [&](int x, int y, int z) -> void { - int idx = Chunk::index(x, y, z); + int idx = ServerChunk::index(x, y, z); m_chunk.has_cave() = true; if (blocks[idx] == 7) return; if (y < WORLD_SIZE_Y - 1 && - blocks[Chunk::index(x, y + 1, z)] == 7) + blocks[ServerChunk::index(x, y + 1, z)] == 7) return; blocks[idx] = 0; }); @@ -780,7 +781,7 @@ void ChunkGenerator::generate_river() { carve_worm(path.points(), chunk_pos, [&](int x, int y, int z) -> void { - int idx = Chunk::index(x, y, z); + int idx = ServerChunk::index(x, y, z); if (y > SEA_LEVEL) { blocks[idx] = 0; return; @@ -799,7 +800,7 @@ void ChunkGenerator::generate_river() { } } -Chunk& ChunkGenerator::chunk() { return m_chunk; } +ServerChunk& ChunkGenerator::chunk() { return m_chunk; } Random& ChunkGenerator::random() { return m_random; } const std::array& ChunkGenerator::neighbor_biome() const { diff --git a/src/gameplay/chunk.cpp b/src/gameplay/client_chunk.cpp similarity index 50% rename from src/gameplay/chunk.cpp rename to src/gameplay/client_chunk.cpp index 7710e66..79ad810 100644 --- a/src/gameplay/chunk.cpp +++ b/src/gameplay/client_chunk.cpp @@ -1,10 +1,6 @@ -#include "Cubed/gameplay/chunk.hpp" +#include "Cubed/gameplay/client_chunk.hpp" -#include "Cubed/gameplay/world.hpp" #include "Cubed/tools/cubed_assert.hpp" -#include "Cubed/tools/log.hpp" - -#include namespace Cubed { using OptionalBlockVectorArray = @@ -39,14 +35,14 @@ get_block_safe(int lx, int ly, int lz, ChunkPos& chunk_pos, const OptionalBlockVectorArray& neighbor_block) { if (lx >= 0 && lx < CHUNK_SIZE && ly >= 0 && ly < WORLD_SIZE_Y && lz >= 0 && lz < CHUNK_SIZE) { - return blocks[Chunk::index(lx, ly, lz)]; + return blocks[ClientChunk::index(lx, ly, lz)]; } // Out of bounds: check neighbors int world_x = lx + chunk_pos.x * CHUNK_SIZE; int world_z = lz + chunk_pos.z * CHUNK_SIZE; - auto [nb_cx, nb_cz] = World::get_chunk_pos(world_x, world_z); + auto [nb_cx, nb_cz] = get_chunk_pos(world_x, world_z); const std::optional>* nb = nullptr; if (nb_cx == chunk_pos.x + 1) @@ -69,7 +65,7 @@ get_block_safe(int lx, int ly, int lz, ChunkPos& chunk_pos, nby >= WORLD_SIZE_Y || nbz >= CHUNK_SIZE) return 0; - int idx = Chunk::index(nbx, nby, nbz); + int idx = ClientChunk::index(nbx, nby, nbz); if (static_cast(idx) >= (*nb)->size()) { return 0; } @@ -100,32 +96,28 @@ inline int choose_buf(BlockType id) { } } // namespace - -Chunk::Chunk(World& world, ChunkPos chunk_pos, bool temp_chunk) - : m_temp_chunk(temp_chunk), m_chunk_pos(chunk_pos), m_world(world) { +ClientChunk::ClientChunk(ClientWorld& world) : m_world(world) { for (int i = 0; i < VERTEX_DATA_SUM; i++) { m_vertex_data.emplace_back(m_world); } } +ClientChunk::~ClientChunk() {} -Chunk::~Chunk() {} - -Chunk::Chunk(Chunk&& other) noexcept +ClientChunk::ClientChunk(ClientChunk&& other) noexcept : m_dirty(other.is_dirty()), m_need_upload(other.m_need_upload.load()), m_is_on_gen_vertex_data(other.m_is_on_gen_vertex_data.load()), m_biome(other.m_biome.load()), m_chunk_pos(std::move(other.m_chunk_pos)), - m_world(other.m_world), m_heightmap(std::move(other.m_heightmap)), - m_blocks(std::move(other.m_blocks)), - m_vertex_data(std::move(other.m_vertex_data)), m_seed(other.m_seed), - m_conditions(other.m_conditions), m_info(std::move(other.m_info)) {} + m_world(other.m_world), m_blocks(std::move(other.m_blocks)), + m_vertex_data(std::move(other.m_vertex_data)), m_seed(other.m_seed) {} -Chunk& Chunk::operator=(Chunk&& other) noexcept { +ClientChunk& ClientChunk::operator=(ClientChunk&& other) noexcept { // Logger::info("other Chunk pos {} {} in Chunk& Chunk::operator=(Chunk&& // other) this {}", other.m_chunk_pos.x, other.m_chunk_pos.z, // static_cast(&other)); - + if (this == &other) { + return *this; + } m_chunk_pos = std::move(other.m_chunk_pos); - m_heightmap = std::move(other.m_heightmap); m_blocks = std::move(other.m_blocks); m_dirty = other.is_dirty(); m_vertex_data = std::move(other.m_vertex_data); @@ -133,55 +125,10 @@ Chunk& Chunk::operator=(Chunk&& other) noexcept { m_is_on_gen_vertex_data = other.m_is_on_gen_vertex_data.load(); m_need_upload = other.m_need_upload.load(); m_seed = other.m_seed; - m_conditions = other.m_conditions; - m_info = std::move(other.m_info); return *this; } -std::tuple Chunk::world_to_block(int world_x, int world_y, - int world_z, int chunk_x, - int chunk_z) { - int x, y, z; - y = world_y; - x = world_x - chunk_x * CHUNK_SIZE; - z = world_z - chunk_z * CHUNK_SIZE; - return {x, y, z}; -} - -std::tuple Chunk::world_to_block(const glm::ivec3& block_pos, - ChunkPos chunk_pos) { - return world_to_block(block_pos.x, block_pos.y, block_pos.z, chunk_pos.x, - chunk_pos.z); -} - -std::tuple Chunk::block_to_world(int x, int y, int z, - int chunk_x, int chunk_z) { - int world_x = x + chunk_x * CHUNK_SIZE; - int world_z = z + chunk_z * CHUNK_SIZE; - int world_y = y; - return {world_x, world_y, world_z}; -} -std::tuple Chunk::block_to_world(const glm::ivec3& block_pos, - ChunkPos chunk_pos) { - return block_to_world(block_pos.x, block_pos.y, block_pos.z, chunk_pos.x, - chunk_pos.z); -} - -BiomeType Chunk::get_biome() const { return m_biome.load(); } - -ChunkPos Chunk::get_chunk_pos() const { return m_chunk_pos; } - -const std::vector& Chunk::get_chunk_blocks() const { - return m_blocks; -} - -HeightMapArray Chunk::get_heightmap() const { - // Logger::info("Chunk pos {} {} in get_heightmap this {}", m_chunk_pos.x, - // m_chunk_pos.z, static_cast(this)); - return m_heightmap; -} - -int Chunk::index(int x, int y, int z) { +int ClientChunk::index(int x, int y, int z) { ASSERT(!(x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y || z >= CHUNK_SIZE)); if ((x * WORLD_SIZE_Y + y) * CHUNK_SIZE + z < 0 || @@ -193,15 +140,50 @@ int Chunk::index(int x, int y, int z) { return (x * WORLD_SIZE_Y + y) * CHUNK_SIZE + z; } -int Chunk::index(const glm::vec3& pos) { - return Chunk::index(pos.x, pos.y, pos.z); +int ClientChunk::index(const glm::vec3& pos) { + return ClientChunk::index(pos.x, pos.y, pos.z); +} +std::tuple ClientChunk::world_to_block(int world_x, int world_y, + int world_z, int chunk_x, + int chunk_z) { + int x, y, z; + y = world_y; + x = world_x - chunk_x * CHUNK_SIZE; + z = world_z - chunk_z * CHUNK_SIZE; + return {x, y, z}; } -void Chunk::gen_vertex_data(const OptionalBlockVectorArray& neighbor_block) { - if (m_is_on_gen_vertex_data) { +std::tuple +ClientChunk::world_to_block(const glm::ivec3& block_pos, ChunkPos chunk_pos) { + return world_to_block(block_pos.x, block_pos.y, block_pos.z, chunk_pos.x, + chunk_pos.z); +} + +std::tuple +ClientChunk::block_to_world(int x, int y, int z, int chunk_x, int chunk_z) { + int world_x = x + chunk_x * CHUNK_SIZE; + int world_z = z + chunk_z * CHUNK_SIZE; + int world_y = y; + return {world_x, world_y, world_z}; +} +std::tuple +ClientChunk::block_to_world(const glm::ivec3& block_pos, ChunkPos chunk_pos) { + return block_to_world(block_pos.x, block_pos.y, block_pos.z, chunk_pos.x, + chunk_pos.z); +} +BiomeType ClientChunk::get_biome() const { return m_biome.load(); } + +ChunkPos ClientChunk::get_chunk_pos() const { return m_chunk_pos; } + +const std::vector& ClientChunk::get_chunk_blocks() const { + return m_blocks; +} + +void ClientChunk::gen_vertex_data( + const OptionalBlockVectorArray& neighbor_block) { + if (m_is_on_gen_vertex_data.exchange(true)) { return; } - m_is_on_gen_vertex_data = true; std::lock_guard lk(m_vertexs_data_mutex); for (auto& data : m_vertex_data) { @@ -216,108 +198,44 @@ void Chunk::gen_vertex_data(const OptionalBlockVectorArray& neighbor_block) { m_is_on_gen_vertex_data = false; } -GLuint Chunk::get_normal_vao() const { return m_vertex_data[0].m_vao; } +GLuint ClientChunk::get_normal_vao() const { return m_vertex_data[0].m_vao; } -size_t Chunk::get_normal_vertices_sum() const { +size_t ClientChunk::get_normal_vertices_sum() const { if (m_vertex_data[0].m_sum == 0) { Logger::warn("m_normal_vertices_sum is 0"); } return m_vertex_data[0].m_sum.load(); } -GLuint Chunk::get_cross_vao() const { return m_vertex_data[1].m_vao; } -size_t Chunk::get_cross_vertices_sum() const { +GLuint ClientChunk::get_cross_vao() const { return m_vertex_data[1].m_vao; } +size_t ClientChunk::get_cross_vertices_sum() const { return m_vertex_data[1].m_sum.load(); } -GLuint Chunk::get_normal_discard_vao() const { return m_vertex_data[2].m_vao; } -size_t Chunk::get_normal_discard_vertices_sum() const { +GLuint ClientChunk::get_normal_discard_vao() const { + return m_vertex_data[2].m_vao; +} +size_t ClientChunk::get_normal_discard_vertices_sum() const { return m_vertex_data[2].m_sum.load(); } -GLuint Chunk::get_normal_blend_vao() const { return m_vertex_data[3].m_vao; } -size_t Chunk::get_normal_blend_vertices_sum() const { +GLuint ClientChunk::get_normal_blend_vao() const { + return m_vertex_data[3].m_vao; +} +size_t ClientChunk::get_normal_blend_vertices_sum() const { return m_vertex_data[3].m_sum.load(); } -GLuint Chunk::get_water_vao() const { return m_vertex_data[4].m_vao; } -size_t Chunk::get_water_vertices_sum() const { +GLuint ClientChunk::get_water_vao() const { return m_vertex_data[4].m_vao; } +size_t ClientChunk::get_water_vertices_sum() const { return m_vertex_data[4].m_sum.load(); } -void Chunk::gen_phase_one() { - m_generator = std::make_unique(*this); - if (!m_generator) { - Logger::error("ChunkGenerator is Nullptr"); +void ClientChunk::upload_to_gpu() { + + if (!is_need_upload()) { return; } - m_generator->assign_chunk_biome(); - m_seed = m_generator->chunk_seed(); -} - -void Chunk::gen_phase_two(const std::array& adj_chunks) { - if (!m_generator) { - Logger::error("ChunkGenerator is Nullptr"); - return; - } - // m_generator->resolve_biome_adjacency_conflict(adj_chunks); -} - -void Chunk::gen_phase_three() { - if (!m_generator) { - Logger::error("ChunkGenerator is Nullptr"); - return; - } - m_generator->generate_heightmap(); -} - -void Chunk::gen_phase_four( - const std::array, 8>& neighbor_heightmap, - const std::array& neighbor_biome) { - if (!m_generator) { - Logger::error("ChunkGenerator is Nullptr"); - return; - } - // m_generator->blend_heightmap_boundaries(neighbor_heightmap, - // neighbor_biome); -} - -void Chunk::gen_phase_five() { - if (!m_generator) { - Logger::error("ChunkGenerator is Nullptr"); - return; - } - m_generator->generate_terrain_blocks(); -} - -void Chunk::gen_phase_six( - const std::array>, 4>& - neighbor_block) { - if (!m_generator) { - Logger::error("ChunkGenerator is Nullptr"); - return; - } - // This must be fully completed before any other operations can proceed! - m_generator->blend_surface_blocks_borders(neighbor_block); -} - -void Chunk::gen_phase_seven() { - if (!m_generator) { - Logger::error("ChunkGenerator is Nullptr"); - return; - } - m_generator->ocean_build(); - m_generator->generate_river(); - m_generator->generate_cave(); - - m_generator->generate_vegetation(); - mark_dirty(); - m_generator = nullptr; -} - -void Chunk::upload_to_gpu() { - - ASSERT(is_need_upload()); std::lock_guard lk(m_vertexs_data_mutex); @@ -327,206 +245,64 @@ void Chunk::upload_to_gpu() { // after fininshed it, can use clear_dirty(); + + m_render_snapshot = { + get_normal_vao(), + get_normal_vertices_sum(), + get_cross_vao(), + get_cross_vertices_sum(), + get_normal_discard_vao(), + get_normal_discard_vertices_sum(), + get_normal_blend_vao(), + get_normal_blend_vertices_sum(), + get_water_vao(), + get_water_vertices_sum(), + glm::vec3(static_cast(m_chunk_pos.x * CHUNK_SIZE) + + static_cast(CHUNK_SIZE / 2), + static_cast(WORLD_SIZE_Y / 2), + static_cast(m_chunk_pos.z * CHUNK_SIZE) + + static_cast(CHUNK_SIZE / 2)), + glm::vec3(static_cast(CHUNK_SIZE / 2), + static_cast(WORLD_SIZE_Y / 2), + static_cast(CHUNK_SIZE / 2))}; + m_need_upload = false; } -bool Chunk::is_dirty() const { return m_dirty.load(); } +bool ClientChunk::is_dirty() const { return m_dirty.load(); } -void Chunk::mark_dirty() { m_dirty = true; } +void ClientChunk::mark_dirty() { m_dirty = true; } -void Chunk::clear_dirty() { m_dirty = false; } +void ClientChunk::clear_dirty() { m_dirty = false; } -bool Chunk::is_need_upload() const { return m_need_upload.load(); } +bool ClientChunk::is_need_upload() const { return m_need_upload.load(); } -void Chunk::need_upload() { m_need_upload = true; } +void ClientChunk::need_upload() { m_need_upload = true; } -void Chunk::set_chunk_block(int index, unsigned id) { +void ClientChunk::set_chunk_block(int index, unsigned id) { m_blocks[index] = id; - mark_dirty(); } -ChunkPos Chunk::chunk_pos() const { return m_chunk_pos; } +ChunkPos ClientChunk::chunk_pos() const { return m_chunk_pos; } -BiomeType Chunk::biome() const { return m_biome; } +BiomeType ClientChunk::biome() const { return m_biome; } -void Chunk::biome(BiomeType b) { m_biome = b; } +void ClientChunk::biome(BiomeType b) { m_biome = b; } -HeightMapArray& Chunk::heightmap() { return m_heightmap; } -std::vector& Chunk::blocks() { return m_blocks; } -World& Chunk::world() { return m_world; } -unsigned Chunk::seed() const { +std::vector& ClientChunk::blocks() { return m_blocks; } +ClientWorld& ClientChunk::world() { return m_world; } +unsigned ClientChunk::seed() const { if (m_seed == 0) { Logger::warn("Seed Not Generator"); } return m_seed; } -BiomeConditions& Chunk::conditions() { return m_conditions; } - -ChunkInfo Chunk::get_info() const { - if (m_gening) { - return ChunkInfo{}; - } - return m_info; +const ChunkRenderSnapshot* ClientChunk::get_render_snapshot() const { + return &m_render_snapshot; } -/* -void Chunk::gen_vertices(const OptionalBlockVectorArray& neighbor_block) { - static const glm::ivec3 DIR[6] = {{0, 0, 1}, {1, 0, 0}, {0, 0, -1}, - {-1, 0, 0}, {0, 1, 0}, {0, -1, 0}}; - for (int x = 0; x < SIZE_X; x++) { - for (int y = 0; y < SIZE_Y; y++) { - for (int z = 0; z < SIZE_Z; z++) { - int world_x = x + m_chunk_pos.x * CHUNK_SIZE; - int world_z = z + m_chunk_pos.z * CHUNK_SIZE; - int world_y = y; - int cur_id = m_blocks[index(x, y, z)]; - // air - if (cur_id == 0) { - continue; - } - - for (int face = 0; face < 6; face++) { - int nx = x + DIR[face].x; - int ny = y + DIR[face].y; - int nz = z + DIR[face].z; - bool neighbor_culled = false; - - if (nx < 0 || nx >= SIZE_X || ny < 0 || ny >= SIZE_Y || - nz < 0 || nz >= SIZE_Z) { - - int world_nx = world_x + DIR[face].x; - int world_ny = world_y + DIR[face].y; - int world_nz = world_z + DIR[face].z; - - auto [neighbor_x, neighbor_z] = - World::get_chunk_pos(world_nx, world_nz); - - auto is_culled = - [&](const std::optional>& - chunk_blocks) { - if (chunk_blocks == std::nullopt) { - return true; - } - int x, y, z; - y = world_ny; - x = world_nx - neighbor_x * CHUNK_SIZE; - z = world_nz - neighbor_z * CHUNK_SIZE; - if (x < 0 || y < 0 || z < 0 || - x >= CHUNK_SIZE || y >= WORLD_SIZE_Y || - z >= CHUNK_SIZE) { - return false; - } - - int idx = Chunk::index(x, y, z); - // not init - if (static_cast(idx) >= - chunk_blocks->size()) { - // Logger::warn("not init"); - return true; - } - auto id = (*chunk_blocks)[idx]; - // transparent - if (BlockManager::is_transparent(id)) { - if (id == cur_id) { - return true; - } else { - return false; - } - - } else { - return true; - } - }; - - if (m_chunk_pos.x + 1 == neighbor_x) { - neighbor_culled = is_culled(neighbor_block[0]); - } else if (m_chunk_pos.x - 1 == neighbor_x) { - neighbor_culled = is_culled(neighbor_block[1]); - } else if (m_chunk_pos.z + 1 == neighbor_z) { - neighbor_culled = is_culled(neighbor_block[2]); - } else if (m_chunk_pos.z - 1 == neighbor_z) { - neighbor_culled = is_culled(neighbor_block[3]); - } - // neighbor_cull = m_world.is_block(glm::ivec3(world_x, - // world_y, world_z) + DIR[face]); - } else { - auto neighbor_id = m_blocks[index(nx, ny, nz)]; - // transparent block - if (!BlockManager::is_transparent(neighbor_id)) { - neighbor_culled = true; - } else { - if (neighbor_id == cur_id) { - neighbor_culled = true; - } else { - neighbor_culled = false; - } - } - } - - if (neighbor_culled) { - continue; - } - if (BlockManager::is_cross_plane(cur_id)) { - gen_cross_plane_vertices(world_x, world_y, world_z, - cur_id); - } - for (int i = 0; i < 6; i++) { - Vertex3D vex = { - VERTICES_POS[face][i][0] + (float)world_x * 1.0f, - VERTICES_POS[face][i][1] + (float)world_y * 1.0f, - VERTICES_POS[face][i][2] + (float)world_z * 1.0f, - TEX_COORDS[face][i][0], - TEX_COORDS[face][i][1], - - static_cast(cur_id * 6 + face), - - NORMALS[face][i][0], - NORMALS[face][i][1], - NORMALS[face][i][2], - BlockManager::roughness(cur_id), - TANGENTS[face][i][0], - TANGENTS[face][i][1], - TANGENTS[face][i][2] - - }; - if (BlockManager::is_transparent(cur_id)) { - if (BlockManager::is_discard(cur_id) && - BlockManager::is_blend(cur_id)) { - Logger::warn( - "Block id {} is both discard and blend is " - "must only one can true !!!", - cur_id); - } - if (BlockManager::is_discard(cur_id)) { - m_vertex_data[2].m_vertices.emplace_back(vex); - } else if (BlockManager::is_blend(cur_id)) { - if (cur_id == 7) { - m_vertex_data[4].m_vertices.emplace_back( - vex); - } else { - m_vertex_data[3].m_vertices.emplace_back( - vex); - } - - } else { - Logger::warn("Id {} is transparent but not " - "discard or blend", - cur_id); - m_vertex_data[3].m_vertices.emplace_back(vex); - } - - } else { - m_vertex_data[0].m_vertices.emplace_back(vex); - } - } - } - } - } - } -} -*/ -void Chunk::gen_vertices(const OptionalBlockVectorArray& neighbor_block) { +void ClientChunk::gen_vertices(const OptionalBlockVectorArray& neighbor_block) { // SIZE_X=SIZE_Z=CHUNK_SIZE=16, SIZE_Y=WORLD_SIZE_Y=256 // Axis order: axis 0=X, 1=Y, 2=Z @@ -648,8 +424,8 @@ void Chunk::gen_vertices(const OptionalBlockVectorArray& neighbor_block) { } } } -void Chunk::emit_quad(int axis, int face_dir, int layer, int i, int j, int w, - int h, int u_axis, int v_axis, FaceKey key) { +void ClientChunk::emit_quad(int axis, int face_dir, int layer, int i, int j, + int w, int h, int u_axis, int v_axis, FaceKey key) { float axis_val = (float)(layer + (face_dir > 0 ? 1 : 0)); float wx_base = (float)(m_chunk_pos.x * CHUNK_SIZE); float wz_base = (float)(m_chunk_pos.z * CHUNK_SIZE); @@ -720,8 +496,8 @@ void Chunk::emit_quad(int axis, int face_dir, int layer, int i, int j, int w, } } -void Chunk::gen_cross_plane_vertices(int world_x, int world_y, int world_z, - BlockType id) { +void ClientChunk::gen_cross_plane_vertices(int world_x, int world_y, + int world_z, BlockType id) { if (!BlockManager::is_cross_plane(id)) { Logger::warn("Block {} {} {} id {} is not cross plane", world_x, @@ -751,55 +527,47 @@ void Chunk::gen_cross_plane_vertices(int world_x, int world_y, int world_z, } } -void Chunk::gen_chunk() { - if (m_gening.exchange(true)) +void ClientChunk::receive_chunk(const ChunkDataRsp& data) { + OptionalBlockVectorArray neighbor; + + m_chunk_pos.x = data.pos().x(); + m_chunk_pos.z = data.pos().z(); + m_seed = data.chunk_seed(); + m_biome = get_biome_from_id(data.biome_type()); + + for (int i = 0; i < 4; i++) { + neighbor[i] = std::nullopt; + } + + if (data.chunk_blocks_size() != BLOCK_SIZE) { + Logger::error("Bad Chunk, size {}", data.chunk_blocks_size()); return; - m_gening = true; - if (m_blocks.size() != 0) { - Logger::warn( - "Request Generator Chunk {} {} ,but the Blocks size is Not 0", - m_chunk_pos.x, m_chunk_pos.z); } - std::vector neighbor; - for (int i = 0; i < 4; i++) { - neighbor.emplace_back(m_world, m_chunk_pos + CHUNK_DIR[i], true); - } - for (auto& chunk : neighbor) { - chunk.gen_phase_one(); - chunk.gen_phase_three(); - chunk.gen_phase_five(); - chunk.gen_phase_seven(); - } - gen_phase_one(); - gen_phase_three(); - gen_phase_five(); + m_blocks.reserve(BLOCK_SIZE); - OptionalBlockVectorArray neightbor_blocks; - for (int i = 0; i < 4; i++) { - neightbor_blocks[i] = neighbor[i].get_chunk_blocks(); + for (const auto& b : data.chunk_blocks()) { + m_blocks.push_back(static_cast(b)); } - gen_phase_six(neightbor_blocks); - gen_phase_seven(); - for (int i = 0; i < 4; i++) { - neightbor_blocks[i] = neighbor[i].get_chunk_blocks(); - } - gen_vertex_data(neightbor_blocks); + // temp neighbor block data + auto load_neighbor = [&](int idx, const auto& blocks) { + if (blocks.size() != BLOCK_SIZE) + return; - // collect chunk info for debugging - m_info.biome = m_biome; - m_info.pos = m_chunk_pos; - m_info.seed = m_seed; - Random r(m_seed); - unsigned first = r.engine()(); - m_info.first_random = first; - r.init(m_seed); - m_info.has_cave_start = r.random_bool(DEFAULT_CAVE_PROBABILITY); - m_info.has_cave = m_has_cave; + neighbor[idx].emplace(); + neighbor[idx]->reserve(BLOCK_SIZE); + + for (auto b : blocks) { + neighbor[idx]->push_back(static_cast(b)); + } + }; + + load_neighbor(0, data.neighbor_blocks_1()); + load_neighbor(1, data.neighbor_blocks_2()); + load_neighbor(2, data.neighbor_blocks_3()); + load_neighbor(3, data.neighbor_blocks_4()); + + gen_vertex_data(neighbor); + mark_dirty(); } -// Logger::info("Cross Sum {}", m_cross_vertices_sum.load()); -bool Chunk::is_temp_chunk() const { return m_temp_chunk.load(); } - -bool& Chunk::has_cave() { return m_has_cave; } - -} // namespace Cubed +} // namespace Cubed \ No newline at end of file diff --git a/src/gameplay/player.cpp b/src/gameplay/client_player.cpp similarity index 69% rename from src/gameplay/player.cpp rename to src/gameplay/client_player.cpp index 9bad20d..595e44e 100644 --- a/src/gameplay/player.cpp +++ b/src/gameplay/client_player.cpp @@ -1,48 +1,41 @@ -#include "Cubed/gameplay/player.hpp" +#include "Cubed/gameplay/client_player.hpp" #include "Cubed/config.hpp" #include "Cubed/debug_collector.hpp" -#include "Cubed/gameplay/world.hpp" -#include "Cubed/tools/log.hpp" - -#include +#include "Cubed/gameplay/client_world.hpp" namespace Cubed { +ClientPlayer::ClientPlayer(ClientWorld& world) : m_world(world) {} +ClientPlayer::~ClientPlayer() {} -Player::Player(World& world, const std::string& name) - : m_name(name), m_world(world) { - hot_reload(); -} -Player::~Player() {} +AABB ClientPlayer::get_aabb(const glm::vec3& pos) { + float half_width = M_SIZE.x / 2.0f; + float half_depth = M_SIZE.z / 2.0f; -AABB Player::get_aabb() const { - float half_width = m_size.x / 2.0f; - float half_depth = m_size.z / 2.0f; + glm::vec3 min{pos.x - half_width, pos.y, pos.z - half_depth}; - glm::vec3 min{m_player_pos.x - half_width, m_player_pos.y, - m_player_pos.z - half_depth}; - - glm::vec3 max{m_player_pos.x + half_width, m_player_pos.y + m_size.y, - m_player_pos.z + half_depth}; + glm::vec3 max{pos.x + half_width, pos.y + M_SIZE.y, pos.z + half_depth}; return AABB{min, max}; } +const glm::vec3& ClientPlayer::get_front() const { return m_front; } -const glm::vec3& Player::get_front() const { return m_front; } +const Gait& ClientPlayer::get_gait() const { return m_gait; } -const Gait& Player::get_gait() const { return m_gait; } - -const std::optional& Player::get_look_block_pos() const { +const std::optional& ClientPlayer::get_look_block_pos() const { return m_look_block; } +glm::vec3 ClientPlayer::get_player_pos() const { -const glm::vec3& Player::get_player_pos() const { return m_player_pos; } + std::shared_lock lock(m_player_pos_mutex); + return m_player_pos; +} -const MoveState& Player::get_move_state() const { return m_move_state; } +const MoveState& ClientPlayer::get_move_state() const { return m_move_state; } -bool Player::ray_cast(const glm::vec3& start, const glm::vec3& front, - glm::ivec3& block_pos, glm::vec3& normal, - float distance) { +bool ClientPlayer::ray_cast(const glm::vec3& start, const glm::vec3& front, + glm::ivec3& block_pos, glm::vec3& normal, + float distance) { glm::vec3 dir = glm::normalize(front); // float step = 0.1f; glm::ivec3 cur = glm::floor(start); @@ -113,7 +106,7 @@ bool Player::ray_cast(const glm::vec3& start, const glm::vec3& front, return false; } -void Player::change_mode(GameMode mode) { +void ClientPlayer::change_mode(GameMode mode) { m_game_mode = mode; Logger::info("Change GameMode to {}", to_str(mode)); if (mode == CREATIVE) { @@ -125,22 +118,19 @@ void Player::change_mode(GameMode mode) { m_max_speed = m_max_run_speed; } } - -void Player::hot_reload() { +void ClientPlayer::hot_reload() { auto& config = Config::get(); m_sensitivity = static_cast(config.get("player.mouse_sensitivity")); } +void ClientPlayer::set_player_pos(const glm::vec3& pos) { m_player_pos = pos; } -void Player::set_player_pos(const glm::vec3& pos) { m_player_pos = pos; } +void ClientPlayer::set_place_block(unsigned id) { m_place_block = id; } -void Player::set_place_block(unsigned id) { m_place_block = id; } - -void Player::update(float delta_time) { +void ClientPlayer::update(float delta_time) { update_move(delta_time); update_lookup_block(); - check_player_chunk_transition(); DebugCollector::get().report("player_pos", std::format("x: {:.2f} y: {:.2f} z: {:.2f}", @@ -150,8 +140,7 @@ void Player::update(float delta_time) { DebugCollector::get().report("speed", std::format("Speed: {:.2} m/s", m_xz_speed)); } - -void Player::update_player_move_state(int key, int action) { +void ClientPlayer::update_player_move_state(int key, int action) { switch (key) { case GLFW_KEY_W: if (action == GLFW_PRESS) { @@ -231,7 +220,7 @@ void Player::update_player_move_state(int key, int action) { } } -void Player::update_front_vec(float offset_x, float offset_y) { +void ClientPlayer::update_front_vec(float offset_x, float offset_y) { m_yaw += offset_x * m_sensitivity; m_pitch += offset_y * m_sensitivity; @@ -246,15 +235,7 @@ void Player::update_front_vec(float offset_x, float offset_y) { m_front = glm::normalize(m_front); } -void Player::check_player_chunk_transition() { - ChunkPos cur_pos = m_world.get_chunk_pos(m_player_pos.x, m_player_pos.z); - if (cur_pos != m_player_chunk_pos) { - m_world.need_gen(); - m_player_chunk_pos = cur_pos; - } -} - -void Player::update_direction() { +void ClientPlayer::update_direction() { m_right = glm::normalize(glm::cross(m_front, glm::vec3(0.0f, 1.0f, 0.0f))); glm::vec3 move_dir_front = glm::vec3(0.0f); @@ -279,7 +260,7 @@ void Player::update_direction() { } } -void Player::update_lookup_block() { +void ClientPlayer::update_lookup_block() { // calculate the block that is looked glm::ivec3 block_pos; glm::vec3 block_normal; @@ -294,25 +275,17 @@ void Player::update_lookup_block() { if (m_look_block != std::nullopt) { if (Input::get_input_state().mouse_state.left) { if (m_world.is_solid(m_look_block->pos)) { - m_world.set_block(m_look_block->pos, 0); + m_world.report_block_change(m_look_block->pos, 0); } Input::get_input_state().mouse_state.left = false; } if (Input::get_input_state().mouse_state.right) { glm::ivec3 near_pos = m_look_block->pos + m_look_block->normal; if (!m_world.is_solid(near_pos)) { - auto x = near_pos.x; - auto y = near_pos.y; - auto z = near_pos.z; - AABB block_box = {glm::vec3{static_cast(x), - static_cast(y), - static_cast(z)}, - glm::vec3{static_cast(x + 1), - static_cast(y + 1), - static_cast(z + 1)}}; - AABB player_box = get_aabb(); + AABB block_box = ClientWorld::get_block_aabb(near_pos); + AABB player_box = get_aabb(get_player_pos()); if (!player_box.intersects(block_box)) { - m_world.set_block(near_pos, m_place_block); + m_world.report_block_change(near_pos, m_place_block); } } Input::get_input_state().mouse_state.right = false; @@ -320,11 +293,19 @@ void Player::update_lookup_block() { } } -void Player::update_move(float delta_time) { +void ClientPlayer::update_move(float delta_time) { // if frame rate less than 1 frame per second, don't update if (delta_time > 1.0f) { return; } + // ensure the thread safe + glm::vec3 player_pos; + + { + std::shared_lock lock(m_player_pos_mutex); + player_pos = m_player_pos; + } + if (m_game_mode != SPECTATOR) { if (m_gait == Gait::RUN) { m_max_speed = m_max_run_speed; @@ -386,24 +367,30 @@ void Player::update_move(float delta_time) { move_distance.y = m_y_speed * delta_time; // y - update_y_move(); + update_y_move(player_pos); // x - update_x_move(); + update_x_move(player_pos); - update_z_move(); + update_z_move(player_pos); - if (m_player_pos.y < -15.0f) { + if (player_pos.y < -15.0f) { Logger::warn("y is tow low"); - m_player_pos += glm::vec3(1.0f, 100.0f, 1.0f); + player_pos += glm::vec3(1.0f, 100.0f, 1.0f); } + + { + std::lock_guard lock(m_player_pos_mutex); + m_player_pos = player_pos; + } + update_player_chunk(); } -void Player::update_x_move() { - m_player_pos.x += move_distance.x; +void ClientPlayer::update_x_move(glm::vec3& player_pos) { + player_pos.x += move_distance.x; if (m_game_mode == SPECTATOR) { return; } - AABB player_box = get_aabb(); + AABB player_box = get_aabb(player_pos); int minx = std::floor(player_box.min.x); int maxx = std::floor(player_box.max.x); int miny = std::floor(player_box.min.y); @@ -414,16 +401,12 @@ void Player::update_x_move() { for (int x = minx; x <= maxx; ++x) { for (int y = miny; y <= maxy; ++y) { for (int z = minz; z <= maxz; ++z) { - if (!m_world.can_pass_block(glm::ivec3{x, y, z})) { - AABB block_box = {glm::vec3{static_cast(x), - static_cast(y), - static_cast(z)}, - glm::vec3{static_cast(x + 1), - static_cast(y + 1), - static_cast(z + 1)}}; + glm::ivec3 block_pos{x, y, z}; + if (!m_world.can_pass_block(block_pos)) { + AABB block_box = ClientWorld::get_block_aabb(block_pos); if (player_box.intersects(block_box)) { m_gait = Gait::WALK; - m_player_pos.x -= move_distance.x; + player_pos.x -= move_distance.x; return; } } @@ -432,12 +415,12 @@ void Player::update_x_move() { } } -void Player::update_y_move() { - m_player_pos.y += move_distance.y; +void ClientPlayer::update_y_move(glm::vec3& player_pos) { + player_pos.y += move_distance.y; if (m_game_mode == SPECTATOR) { return; } - AABB player_box = get_aabb(); + AABB player_box = get_aabb(player_pos); int minx = std::floor(player_box.min.x); int maxx = std::floor(player_box.max.x); int miny = std::floor(player_box.min.y); @@ -448,15 +431,11 @@ void Player::update_y_move() { for (int x = minx; x <= maxx; ++x) { for (int y = miny; y <= maxy; ++y) { for (int z = minz; z <= maxz; ++z) { - if (!m_world.can_pass_block(glm::ivec3{x, y, z})) { - AABB block_box = {glm::vec3{static_cast(x), - static_cast(y), - static_cast(z)}, - glm::vec3{static_cast(x + 1), - static_cast(y + 1), - static_cast(z + 1)}}; + glm::ivec3 block_pos{x, y, z}; + if (!m_world.can_pass_block(block_pos)) { + AABB block_box = ClientWorld::get_block_aabb(block_pos); if (player_box.intersects(block_box)) { - m_player_pos.y -= move_distance.y; + player_pos.y -= move_distance.y; m_y_speed = 0.0f; if (move_distance.y < 0) { can_up = true; @@ -470,12 +449,12 @@ void Player::update_y_move() { } } -void Player::update_z_move() { - m_player_pos.z += move_distance.z; +void ClientPlayer::update_z_move(glm::vec3& player_pos) { + player_pos.z += move_distance.z; if (m_game_mode == SPECTATOR) { return; } - AABB player_box = get_aabb(); + AABB player_box = get_aabb(player_pos); int minx = std::floor(player_box.min.x); int maxx = std::floor(player_box.max.x); int miny = std::floor(player_box.min.y); @@ -486,16 +465,12 @@ void Player::update_z_move() { for (int x = minx; x <= maxx; ++x) { for (int y = miny; y <= maxy; ++y) { for (int z = minz; z <= maxz; ++z) { - if (!m_world.can_pass_block(glm::ivec3{x, y, z})) { - AABB block_box = {glm::vec3{static_cast(x), - static_cast(y), - static_cast(z)}, - glm::vec3{static_cast(x + 1), - static_cast(y + 1), - static_cast(z + 1)}}; + glm::ivec3 block_pos{x, y, z}; + if (!m_world.can_pass_block(block_pos)) { + AABB block_box = ClientWorld::get_block_aabb(block_pos); if (player_box.intersects(block_box)) { m_gait = Gait::WALK; - m_player_pos.z -= move_distance.z; + player_pos.z -= move_distance.z; return; } } @@ -504,7 +479,23 @@ void Player::update_z_move() { } } -void Player::update_scroll(double yoffset) { +void ClientPlayer::update_player_chunk() { + float x, z; + { + std::shared_lock lock(m_player_pos_mutex); + x = m_player_pos.x; + z = m_player_pos.z; + } + ChunkPos chunk_pos = get_chunk_pos(x, z); + float dist = distance2(chunk_pos, m_last_chunk_pos); + if (dist > 2) { + Logger::info("Player request new chunk"); + m_world.request_chunk(); + m_last_chunk_pos = chunk_pos; + } +} + +void ClientPlayer::update_scroll(double yoffset) { if (m_game_mode == SPECTATOR) { if (yoffset > 0) { if (m_max_speed < 500.0f) { @@ -531,15 +522,36 @@ void Player::update_scroll(double yoffset) { } } -float& Player::max_walk_speed() { return m_max_walk_speed; } -float& Player::max_run_speed() { return m_max_run_speed; } -float& Player::max_speed() { return m_max_speed; } -float& Player::acceleration() { return m_acceleration; } -float& Player::deceleration() { return m_deceleration; } -float& Player::g() { return m_g; } -float& Player::fly_y_speed() { return m_fly_y_speed; } -unsigned Player::place_block() const { return m_place_block; }; -Gait& Player::gait() { return m_gait; } -GameMode& Player::game_mode() { return m_game_mode; } -const World& Player::get_world() const { return m_world; } -} // namespace Cubed +void ClientPlayer::update_chunk_set(const ChunkPosSet& set) { + std::lock_guard lock(m_chunk_pos_mutex); + m_player_chunk_pos_set.clear(); + m_player_chunk_pos_set.insert(set.begin(), set.end()); +} + +const ClientPlayer::ChunkPosSet& ClientPlayer::get_chunk_pos_set() const { + std::shared_lock lock(m_chunk_pos_mutex); + return m_player_chunk_pos_set; +} + +ClientPlayer::ChunkPosSet& ClientPlayer::get_chunk_pos_set() { + std::lock_guard lock(m_chunk_pos_mutex); + return m_player_chunk_pos_set; +} + +float& ClientPlayer::max_walk_speed() { return m_max_walk_speed; } +float& ClientPlayer::max_run_speed() { return m_max_run_speed; } +float& ClientPlayer::max_speed() { return m_max_speed; } +float& ClientPlayer::acceleration() { return m_acceleration; } +float& ClientPlayer::deceleration() { return m_deceleration; } +float& ClientPlayer::g() { return m_g; } +float& ClientPlayer::fly_y_speed() { return m_fly_y_speed; } +unsigned ClientPlayer::place_block() const { return m_place_block; }; +Gait& ClientPlayer::gait() { return m_gait; } +GameMode& ClientPlayer::game_mode() { return m_game_mode; } +const ClientWorld& ClientPlayer::get_world() const { return m_world; } + +void ClientPlayer::set_uuid(std::string_view uuid) { m_uuid = uuid; } +const std::string& ClientPlayer::get_uuid() const { return m_uuid; } +const std::string& ClientPlayer::get_name() const { return m_name; } +void ClientPlayer::init(std::string_view name) { m_name = name; } +} // namespace Cubed \ No newline at end of file diff --git a/src/gameplay/client_world.cpp b/src/gameplay/client_world.cpp new file mode 100644 index 0000000..f000cd1 --- /dev/null +++ b/src/gameplay/client_world.cpp @@ -0,0 +1,746 @@ +#include "Cubed/gameplay/client_world.hpp" + +#include "Cubed/config.hpp" +#include "Cubed/gameplay/game_time.hpp" +#include "Cubed/gameplay/packet.hpp" +#include "Cubed/tools/math_tools.hpp" + +#include +#include + +using namespace std::chrono; +using namespace std::chrono_literals; +using namespace google::protobuf; +namespace Cubed { + +namespace { +struct ChunkRenderData { + std::array*, 4> neighbor_block; + ClientChunk* chunk; +}; +} // namespace + +ClientWorld::ClientWorld() : m_player(*this) {} + +ClientWorld::~ClientWorld() { + stop_client_thread(); + stop_thread_pool(); + + m_chunks.clear(); + + { + std::lock_guard lk(m_delete_vbo_mutex); + for (auto x : m_pending_delete_vbo) { + glDeleteBuffers(1, &x); + } + m_pending_delete_vbo.clear(); + } + { + std::lock_guard lk(m_delete_vao_mutex); + for (auto x : m_pending_delete_vao) { + glDeleteVertexArrays(1, &x); + } + m_pending_delete_vao.clear(); + } + m_timers.clear(); +} + +const std::optional& ClientWorld::get_look_block_pos() const { + + return m_player.get_look_block_pos(); +} + +ClientPlayer& ClientWorld::get_player() { return m_player; } + +int ClientWorld::get_block(const glm::ivec3& block_pos) const { + auto [chunk_x, chunk_z] = get_chunk_pos(block_pos.x, block_pos.z); + chunk_cacc cacc; + + if (!m_chunks.find(cacc, ChunkPos{chunk_x, chunk_z})) { + return 0; + } + + const auto& chunk_blocks = cacc->second->get_chunk_blocks(); + auto [x, y, z] = ClientChunk::world_to_block(block_pos, {chunk_x, chunk_z}); + if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y || + z >= CHUNK_SIZE) { + return 0; + } + return chunk_blocks[ClientChunk::index(x, y, z)]; +} +bool ClientWorld::is_solid(const glm::ivec3& block_pos) const { + auto [chunk_x, chunk_z] = get_chunk_pos(block_pos.x, block_pos.z); + chunk_cacc cacc; + + if (!m_chunks.find(cacc, ChunkPos{chunk_x, chunk_z})) { + return false; + } + const auto& chunk_blocks = cacc->second->get_chunk_blocks(); + auto [x, y, z] = ClientChunk::world_to_block(block_pos, {chunk_x, chunk_z}); + if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y || + z >= CHUNK_SIZE) { + return false; + } + auto id = chunk_blocks[ClientChunk::index(x, y, z)]; + if (BlockManager::is_gas(id) || BlockManager::is_liquid(id)) { + return false; + } else { + return true; + } +} +bool ClientWorld::can_pass_block(const glm::ivec3& block_pos) const { + auto [chunk_x, chunk_z] = get_chunk_pos(block_pos.x, block_pos.z); + chunk_cacc cacc; + + if (!m_chunks.find(cacc, ChunkPos{chunk_x, chunk_z})) { + return true; + } + const auto& chunk_blocks = cacc->second->get_chunk_blocks(); + auto [x, y, z] = ClientChunk::world_to_block(block_pos, {chunk_x, chunk_z}); + if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y || + z >= CHUNK_SIZE) { + return true; + } + auto id = chunk_blocks[ClientChunk::index(x, y, z)]; + return BlockManager::is_passable(id); +} + +void ClientWorld::rebuild_world() { + if (m_is_rebuilding.exchange(true)) { + return; + } + + stop_client_thread(); + stop_thread_pool(); + + m_chunks.clear(); + + m_pending_upload_queue.clear(); + + start_thread_pool(); + start_client_thread(m_player.get_uuid()); + request_chunk(); + m_is_rebuilding = false; +} + +BlockType ClientWorld::get_block_tpye(const glm::ivec3& block_pos) const { + auto [chunk_x, chunk_z] = get_chunk_pos(block_pos.x, block_pos.z); + chunk_cacc cacc; + ; + + if (!m_chunks.find(cacc, ChunkPos{chunk_x, chunk_z})) { + // Logger::error("Can't Find Block {} {} {}", block_pos.x, block_pos.y, + // block_pos.z); + return 0; + } + const auto& chunk_blocks = cacc->second->get_chunk_blocks(); + auto [x, y, z] = ClientChunk::world_to_block(block_pos, {chunk_x, chunk_z}); + if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y || + z >= CHUNK_SIZE) { + // Logger::error("Can't Find Block {} {} {}", block_pos.x, block_pos.y, + // block_pos.z); + return 0; + } + return chunk_blocks[ClientChunk::index(x, y, z)]; +} +void ClientWorld::set_block(const glm::ivec3& block_pos, unsigned id) { + int world_x, world_y, world_z; + world_x = block_pos.x; + world_y = block_pos.y; + world_z = block_pos.z; + + auto [chunk_x, chunk_z] = get_chunk_pos(world_x, world_z); + ChunkPos pos{chunk_x, chunk_z}; + { + chunk_acc acc; + + if (!m_chunks.find(acc, pos)) { + return; + } + + auto [x, y, z] = ClientChunk::world_to_block(world_x, world_y, world_z, + chunk_x, chunk_z); + if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y || + z >= CHUNK_SIZE) { + return; + } + + acc->second->set_chunk_block(ClientChunk::index(x, y, z), id); + acc->second->mark_dirty(); + } + + auto pool = m_thread_pool.load(); + + pool->enqueue(0, [this, pos]() { + std::shared_ptr chunk; + + { + chunk_acc acc; + if (m_chunks.find(acc, pos)) { + chunk = acc->second; + } + } + + if (!chunk) { + return; + } + + OptionalBlockVectorArray neighbor_block; + for (int i = 0; i < 4; i++) { + chunk_cacc cacc; + if (m_chunks.find(cacc, pos + CHUNK_DIR[i])) { + neighbor_block[i] = (cacc->second->get_chunk_blocks()); + } else { + neighbor_block[i] = std::nullopt; + } + } + + chunk->gen_vertex_data(neighbor_block); + m_dirty_chunk_queue.emplace(pos); + }); + + static const glm::ivec3 NEIGHBOR_DIRS[] = { + {1, 0, 0}, {-1, 0, 0}, {0, 0, -1}, {0, 0, 1}}; + static constexpr int NPOS_SUM = sizeof(NEIGHBOR_DIRS); + + absl::InlinedVector nposes; + + for (const auto& dir : NEIGHBOR_DIRS) { + glm::ivec3 neighbor = block_pos + dir; + + auto [cx, cz] = get_chunk_pos(neighbor.x, neighbor.z); + { + chunk_acc acc; + if (m_chunks.find(acc, {cx, cz})) { + if (acc->second->is_dirty()) { + continue; + } + nposes.emplace_back(acc->first); + } + } + } + + for (auto& npos : nposes) { + pool->enqueue(0, [this, npos]() { + std::shared_ptr chunk; + + { + chunk_acc acc; + if (m_chunks.find(acc, npos)) { + chunk = acc->second; + } + } + + if (!chunk) { + return; + } + + OptionalBlockVectorArray neighbor_block; + for (int i = 0; i < 4; i++) { + chunk_cacc cacc; + if (m_chunks.find(cacc, npos + CHUNK_DIR[i])) { + neighbor_block[i] = (cacc->second->get_chunk_blocks()); + } else { + neighbor_block[i] = std::nullopt; + } + } + + chunk->gen_vertex_data(neighbor_block); + + m_dirty_chunk_queue.emplace(npos); + }); + } +} +void ClientWorld::push_delete_vbo(GLuint vbo) { + std::lock_guard lk(m_delete_vbo_mutex); + m_pending_delete_vbo.push_back(vbo); +} +void ClientWorld::push_delete_vao(GLuint vao) { + std::lock_guard lk(m_delete_vao_mutex); + m_pending_delete_vao.push_back(vao); +} + +void ClientWorld::report_block_change(const glm::ivec3& pos, + unsigned id) const { + { + AABB block_box = get_block_aabb(pos); + std::shared_lock lock(m_other_players_mutex); + + for (auto& [uuid, player] : m_other_players) { + AABB box = ClientPlayer::get_aabb(player.target_pos); + if (box.intersects(block_box)) { + return; + } + } + } + + Arena arena; + auto* req = Arena::Create(&arena); + req->set_uuid(m_player.get_uuid()); + req->set_block(id); + auto* p = req->mutable_pos(); + p->set_x(pos.x); + p->set_y(pos.y); + p->set_z(pos.z); + m_client->send(make_packet(*req), 0); +} + +void ClientWorld::receive_block_change(const BlockChangeRsp& rsp) { + glm::vec3 pos{rsp.pos().x(), rsp.pos().y(), rsp.pos().z()}; + set_block(pos, rsp.block()); +} + +void ClientWorld::receive_time(const UpdateTime& rsp) { + m_game_ticks = rsp.game_tick(); + m_day_tick = rsp.day_tick(); +} + +void ClientWorld::receive_remote_player(const PlayerInfoRsp& rsp) { + { + std::lock_guard lock(m_other_players_mutex); + glm::vec3 pos{rsp.pos().x(), rsp.pos().y(), rsp.pos().z()}; + auto it = m_other_players.find(rsp.uuid()); + if (it == m_other_players.end()) { + m_other_players.emplace( + std::piecewise_construct, std::forward_as_tuple(rsp.uuid()), + std::forward_as_tuple(rsp.name(), pos, pos)); + } else { + it->second.target_pos = pos; + } + // Logger::info("Player {} pos Update", rsp.name()); + } +} + +void ClientWorld::receive_player_logout(const LogoutRsp& rsp) { + if (rsp.server_stop()) { + m_receive_exit = true; + return; + } + if (rsp.uuid() == m_player.get_uuid()) { + m_receive_exit = true; + return; + } + { + std::lock_guard lock(m_other_players_mutex); + int sum = m_other_players.erase(rsp.uuid()); + if (sum == 0) { + Logger::warn("Player {} not find", rsp.uuid()); + } else { + Logger::info("Player {} erase", rsp.uuid()); + } + } +} + +void ClientWorld::init(std::string_view player_name, + std::shared_ptr client) { + m_player.init(player_name); + m_client = client; + // timer + register_timer("player_pos", 1, [this]() { report_player_pos(); }); + LoginReq req; + req.set_name(m_player.get_name()); + while (!client->is_connected()) { + if (client->is_connect_error()) { + throw std::runtime_error("Can't connect to the server"); + } + std::this_thread::sleep_for(milliseconds(200)); + } + start_thread_pool(); + // request login + Logger::info("Send Login Request"); + m_client->send(make_packet(req), 0); +} + +void ClientWorld::start_client_thread(std::string_view uuid) { + if (m_game_running) { + Logger::error("Game Already Running"); + return; + } + // response + m_player.set_uuid(uuid); + m_client_thread = std::jthread([this](std::stop_token token) { + m_game_running = true; + client_run(token); + }); + + // Wait for 20 ticks, after the server's central chunk is generated, then + // request chunks + + std::this_thread::sleep_for(milliseconds(20 * DEFAULT_PER_TICK_TIME)); + + request_chunk(); +} + +void ClientWorld::stop_client_thread() { + m_client_thread.request_stop(); + if (m_client_thread.joinable()) { + m_client_thread.join(); + } + m_game_running = false; +} +void ClientWorld::start_thread_pool() { + int max_threads = std::thread::hardware_concurrency(); + int threads = std::min(max_threads, 4); + change_pool_threads(threads); +} +void ClientWorld::stop_thread_pool() { + auto pool_ptr = m_thread_pool.load(); + if (pool_ptr) { + pool_ptr->stop(); + } + m_thread_pool.store(nullptr); + Logger::info("Thread Pool Stopped"); +} + +void ClientWorld::change_pool_threads(int threads) { + int m_max_threads = std::thread::hardware_concurrency(); + if (m_max_threads < 1) { + Logger::warn("Can't Get Max Support Threads, Set Max Threads to 4"); + m_max_threads = 1; + } + int used_thread = std::clamp(threads, 1, m_max_threads); + Logger::info("Create New Thread Pool Use {} Threads", used_thread); + m_thread_pool.store(std::make_shared(used_thread)); +} + +void ClientWorld::hot_reload() { + auto& config = Config::get(); + int dist = config.get("world.rendering_distance"); + Logger::info("Get Config Randering dist {}", dist); + m_rendering_distance = dist <= MAX_DISTANCE ? dist : MAX_DISTANCE; + request_chunk(); +} + +void ClientWorld::client_run(std::stop_token stoken) { + Logger::info("Client Thread Started"); + using Clock = std::chrono::steady_clock; + + constexpr auto TICK = std::chrono::milliseconds(DEFAULT_PER_TICK_TIME); + + auto next = Clock::now(); + while (!stoken.stop_requested()) { + next += TICK; + for (auto& x : m_timers) { + x.second.update(); + } + std::this_thread::sleep_until(next); + } +} + +void ClientWorld::report_player_pos() { + if (!m_client) { + return; + } + Arena arena; + auto* pos = Arena::Create(&arena); + pos->set_uuid(m_player.get_uuid()); + glm::vec3 player_pos = m_player.get_player_pos(); + auto* v3 = pos->mutable_pos(); + v3->set_x(player_pos.x); + v3->set_y(player_pos.y); + v3->set_z(player_pos.z); + m_client->send(make_packet(*pos), 0); +} + +void ClientWorld::update_chunk(const ChunkPosSet& old, const ChunkPosSet& now) { + + // Elements in the old set that are not contained in now are not needed by + // the current player. + + for (auto& pos : old) { + if (!now.contains(pos)) { + + chunk_acc acc; + if (!m_chunks.find(acc, pos)) { + Logger::warn("Update Ref Count Error, can't Find old pos " + "in m_chunks"); + continue; + } + + m_chunks.erase(acc); + } + } +} + +void ClientWorld::request_chunk() { + if (m_requesting_chunk.exchange(true)) { + Logger::warn("It is requesting new chunk!"); + return; + } + ChunkPosSet required_chunks; + + glm::vec3 player_pos = m_player.get_player_pos(); + + int x = std::floor(player_pos.x); + int z = std::floor(player_pos.z); + auto [chunk_x, chunk_z] = get_chunk_pos(x, z); + int radius = m_rendering_distance; + Logger::info("Client Chunk Radius {}", radius); + int r2 = radius * radius; + required_chunks.reserve(radius * radius); + + for (int dx = -radius; dx <= radius; ++dx) { + for (int dz = -radius; dz <= radius; ++dz) { + if (dx * dx + dz * dz <= r2) { + required_chunks.emplace(chunk_x + dx, chunk_z + dz); + } + } + } + + ChunkPosSet old = std::move(m_player.get_chunk_pos_set()); + m_player.update_chunk_set(required_chunks); + + ChunkPosVector need_send_pos; + + for (auto pos : required_chunks) { + chunk_cacc cacc; + if (!m_chunks.find(cacc, pos)) { + need_send_pos.emplace_back(pos); + } + } + + update_chunk(old, required_chunks); + + if (need_send_pos.empty()) { + m_requesting_chunk = false; + return; + } + using enum ChunkLoadStyle; + switch (m_chunk_load_style) { + case RANDOM: + + break; + case CENTER: { + + glm::vec3 player_pos = m_player.get_player_pos(); + ChunkPos player_chunk_pos = get_chunk_pos(player_pos.x, player_pos.z); + auto dist2 = [player_chunk_pos](ChunkPos chunk_pos) { + float dx = player_chunk_pos.x - chunk_pos.x; + float dz = player_chunk_pos.z - chunk_pos.z; + return dx * dx + dz * dz; + }; + + std::sort(need_send_pos.begin(), need_send_pos.end(), + [&dist2](const auto& a, const auto& b) { + return dist2(a) < dist2(b); + }); + } + } + auto uuid = m_player.get_uuid(); + Arena arena; + ++m_chunk_task_id; + auto* req = Arena::Create(&arena); + for (const auto& pos : need_send_pos) { + req->set_task_id(m_chunk_task_id.load()); + req->set_uuid(uuid); + auto* p = req->mutable_pos(); + p->set_x(pos.x); + p->set_z(pos.z); + m_client->send(make_packet(*req)); + } + Logger::info("Send Chunk Request Success"); + m_requesting_chunk = false; +} + +void ClientWorld::receive_chunk(std::vector raw_data, + PacketHeader header) { + + // vertex data will genrator in client thread pool instead of net thread; + auto pool = m_thread_pool.load(); + if (!pool) { + Logger::error("Client Thread Pool is nullptr"); + return; + } + pool->enqueue( + [this, raw_data = std::move(raw_data), header = std::move(header)]() { + Arena arena; + auto* data = Arena::Create(&arena); + if (!decode_packet(*data, raw_data, header)) { + return; + } + + if (data->task_id() < m_chunk_task_id) { + return; + } + + { + chunk_cacc cacc; + ChunkPos pos{data->pos().x(), data->pos().z()}; + if (m_chunks.find(cacc, pos)) { + Logger::warn("Chunk {} {} has already in client world", + pos.x, pos.z); + return; + } + } + + std::unique_ptr chunk = + std::make_unique(*this); + chunk->receive_chunk(*data); + + m_pending_upload_queue.emplace(std::move(chunk)); + }); +} +bool ClientWorld::is_receive_exit() { return m_receive_exit; } + +int ClientWorld::chunk_size() const { return m_chunks.size(); } + +AABB ClientWorld::get_block_aabb(const glm::ivec3& pos) { + auto x = pos.x; + auto y = pos.y; + auto z = pos.z; + return {glm::vec3{static_cast(x), static_cast(y), + static_cast(z)}, + glm::vec3{static_cast(x + 1), static_cast(y + 1), + static_cast(z + 1)}}; +} + +void ClientWorld::request_exit() { + if (m_receive_exit) { + return; + } + Arena arena; + auto* req = Arena::Create(&arena); + req->set_uuid(m_player.get_uuid()); + m_client->send(make_packet(*req)); + int cnt = 0; + while (!m_receive_exit) { + std::this_thread::sleep_for(milliseconds(DEFAULT_PER_TICK_TIME)); + ++cnt; + if (cnt >= WORLD_EXIT_TIMEOUT) { + Logger::warn("Can't Receive Server Exit Sign"); + break; + } + } +} + +void ClientWorld::update(float delta_time) { + m_player.update(delta_time); + { + std::lock_guard lk(m_delete_vbo_mutex); + for (auto x : m_pending_delete_vbo) { + glDeleteBuffers(1, &x); + } + m_pending_delete_vbo.clear(); + } + + { + std::lock_guard lk(m_delete_vao_mutex); + for (auto x : m_pending_delete_vao) { + glDeleteVertexArrays(1, &x); + } + m_pending_delete_vao.clear(); + } + + std::vector> new_chunks; + { + std::unique_ptr chunk; + int sum = 0; + while (m_pending_upload_queue.try_pop(chunk)) { + new_chunks.emplace_back(std::move(chunk)); + ++sum; + if (sum >= MAX_UPLOAD_CHUNK_SUM) { + break; // Limit the maximum number of uploads per frame to + // improve frame rate performance + } + } + } + + for (auto& c : new_chunks) { + c->upload_to_gpu(); + } + + for (auto& c : new_chunks) { + m_chunks.emplace(c->get_chunk_pos(), std::move(c)); + } + m_render_snapshots.clear(); + + ChunkPos pos; + + while (m_dirty_chunk_queue.try_pop(pos)) { + std::shared_ptr chunk; + { + chunk_acc acc; + if (m_chunks.find(acc, pos)) { + chunk = acc->second; + } + } + if (!chunk) { + continue; + } + + chunk->upload_to_gpu(); + } + + auto chunk_pos_set = m_player.get_chunk_pos_set(); + + for (auto& pos : chunk_pos_set) { + std::shared_ptr chunk; + { + chunk_acc acc; + if (m_chunks.find(acc, pos)) { + chunk = acc->second; + } + } + if (!chunk) { + continue; + } + + m_render_snapshots.push_back(chunk->get_render_snapshot()); + } + + m_render_player_data.clear(); + { + std::lock_guard lock(m_other_players_mutex); + for (auto& [uuid, player] : m_other_players) { + player.render_pos = + glm::mix(player.render_pos, player.target_pos, 0.15f); + if (Math::distance2(player.render_pos, m_player.get_player_pos()) > + m_rendering_distance * CHUNK_SIZE * m_rendering_distance * + CHUNK_SIZE) { + continue; + } + m_render_player_data.emplace_back(player.name, player.render_pos); + } + } +} + +glm::vec3 ClientWorld::sunlight_dir() const { + float altitude = sin((m_day_tick - 6 * PER_HOUR) / + static_cast(DAY_TIME / 2) * std::numbers::pi) * + 90.0f; + + float t = static_cast(m_day_tick) / DAY_TIME; + float azimuth = 90.0f - 360.0f * (t - 0.25f); + + float alt = glm::radians(altitude); + float az = glm::radians(azimuth); + glm::vec3 dir; + dir.x = cos(alt) * sin(az); + dir.y = sin(alt); + dir.z = cos(alt) * cos(az); + + return glm::normalize(-dir); +} +int ClientWorld::rendering_distance() const { + return m_rendering_distance.load(); +} + +void ClientWorld::rendering_distance(int rendering_distance) { + m_rendering_distance = rendering_distance; + Logger::info("Set Rendering dist {} , the value is {}", rendering_distance, + m_rendering_distance.load()); + request_chunk(); +} + +int ClientWorld::get_chunk_task_id() const { return m_chunk_task_id.load(); } + +const std::vector& +ClientWorld::render_snapshots() const { + return m_render_snapshots; +}; +const std::vector& +ClientWorld::render_player_data() const { + return m_render_player_data; +} +std::vector& ClientWorld::planes() { return m_planes; } +} // namespace Cubed \ No newline at end of file diff --git a/src/gameplay/network_client.cpp b/src/gameplay/network_client.cpp new file mode 100644 index 0000000..e74b441 --- /dev/null +++ b/src/gameplay/network_client.cpp @@ -0,0 +1,205 @@ +#include "Cubed/gameplay/network_client.hpp" + +#include "Cubed/gameplay/client_world.hpp" +#include "Cubed/tools/log.hpp" + +#include + +using namespace google::protobuf; +namespace Cubed { +NetworkClient::NetworkClient(ClientWorld& world) + : m_socket(m_io), m_strand(asio::make_strand(m_io)), m_world(world) {} + +NetworkClient::~NetworkClient() { close(); } + +void NetworkClient::start(std::string ip, int port) { + if (m_net_thread.joinable()) { + return; + } + m_net_thread = std::thread([self = shared_from_this(), ip, port]() { + asio::co_spawn(self->m_strand, self->connect(ip, port), asio::detached); + self->m_io.run(); + }); + Logger::info("NetworkClient Started"); +} + +bool NetworkClient::is_connected() const { return m_connected.load(); } +bool NetworkClient::is_connect_error() const { return m_connect_error.load(); } +asio::awaitable NetworkClient::connect(std::string ip, int port) { + Logger::info("Connect Begin"); + try { + auto ex = co_await asio::this_coro::executor; + tcp::resolver resolver(ex); + auto eps = co_await resolver.async_resolve(ip, std::to_string(port), + asio::use_awaitable); + Logger::info("Resolve Success"); + co_await async_connect(m_socket, eps, asio::use_awaitable); + Logger::info("Connect Success, Server ip {} port {}", ip, port); + asio::co_spawn(m_strand, read_loop(), asio::detached); + Logger::info("NetworkClient Read Loop Started"); + m_connected = true; + co_return; + + } catch (const std::exception& e) { + Logger::error("Client Error {}", e.what()); + m_connect_error = true; + } +} + +asio::awaitable NetworkClient::read_loop() { + try { + while (true) { + std::array header_buffer; + co_await asio::async_read(m_socket, asio::buffer(header_buffer), + asio::use_awaitable); + auto header = decode_packet_header(header_buffer); + uint32_t total_len = HEADER_LEN + header.compressed_size; + + if (total_len < HEADER_LEN || total_len > MAX_PACKET_SIZE) { + + throw std::runtime_error("invalid packet length"); + } + // maybe move, don't use it after switch! + std::vector body_data(header.compressed_size); + if (header.compressed_size > 0) { + co_await asio::async_read(m_socket, asio::buffer(body_data), + asio::use_awaitable); + } + + using std::to_underlying; + Arena arena; + switch (header.cmd) { + case std::to_underlying(PacketEnum::LOGIN_RSP): { + auto* rsp = Arena::Create(&arena); + Logger::info("Client: Receive Login rsp"); + if (decode_packet(*rsp, body_data, header)) { + if (rsp->success()) { + m_world.start_client_thread(rsp->uuid()); + } else { + Logger::error("Connected Server Fail"); + } + } + } break; + case std::to_underlying(PacketEnum::CHUNK_DATA_RSP): { + // Logger::info("Client: Receive Chunk Data rsp, size {}mb", + // body_data.size() / 1024.0f / 1024); + m_world.receive_chunk(std::move(body_data), header); + } break; + case std::to_underlying(PacketEnum::BLOCK_CHANGE_RSP): { + auto* rsp = Arena::Create(&arena); + Logger::info("Client: Receive Block Change rsp"); + if (decode_packet(*rsp, body_data, header)) { + m_world.receive_block_change(*rsp); + } + } break; + case std::to_underlying(PacketEnum::UPDATE_TIME): { + auto* rsp = Arena::Create(&arena); + if (decode_packet(*rsp, body_data, header)) { + m_world.receive_time(*rsp); + } + } break; + case std::to_underlying(PacketEnum::PLAYER_INFO_RSP): { + auto* rsp = Arena::Create(&arena); + if (decode_packet(*rsp, body_data, header)) { + m_world.receive_remote_player(*rsp); + } + } break; + case std::to_underlying(PacketEnum::LOGOUT_RSP): { + auto* rsp = Arena::Create(&arena); + if (decode_packet(*rsp, body_data, header)) { + m_world.receive_player_logout(*rsp); + } + } break; + case std::to_underlying(PacketEnum::S2C_CLEAR_ALL_CHUNKS): { + auto* rsp = Arena::Create(&arena); + if (decode_packet(*rsp, body_data, header)) { + if (rsp->clear()) { + Logger::info("Client Clear All Chunk"); + m_world.rebuild_world(); + } + } + } break; + } + } + } catch (const asio::system_error& e) { + auto ec = e.code(); + + if (ec == asio::error::eof || ec == asio::error::operation_aborted) { + + Logger::info("Client disconnected"); + } else { + Logger::warn("Asio Error {}", e.what()); + } + + close(); + } catch (const std::exception& e) { + Logger::error("Session Error {}", e.what()); + close(); + } catch (...) { + Logger::error("Unknow Error"); + close(); + } + co_return; +} + +void NetworkClient::send(Packet packet, int priority) { + if (m_closed.load()) { + return; + } + asio::post(m_strand, [self = shared_from_this(), packet = std::move(packet), + priority]() mutable { + bool idle = self->m_write_queue.empty(); + self->m_write_queue.emplace(priority, self->m_sequence++, + std::move(packet)); + if (idle) { + self->do_write(); + } + }); +} + +void NetworkClient::do_write() { + if (m_closed.load()) { + return; + } + + auto self = shared_from_this(); + auto packet = std::move(m_write_queue.top().packet); + asio::async_write( + m_socket, asio::buffer(*packet), + asio::bind_executor(m_strand, [self](std::error_code ec, size_t) { + if (ec) { + Logger::warn("Write Ec {}", ec.message()); + self->close(); + return; + } + self->m_write_queue.pop(); + if (!self->m_write_queue.empty()) { + self->do_write(); + } + })); +} + +void NetworkClient::close() { + if (m_closed.exchange(true)) { + return; + } + + std::error_code ec; + + m_socket.shutdown(tcp::socket::shutdown_both, ec); + + m_socket.close(ec); + Logger::info("NetworkClient Closed"); + m_connected = false; + m_io.stop(); +} + +void NetworkClient::stop() { + close(); + + if (m_net_thread.joinable()) { + m_net_thread.join(); + } +} + +} // namespace Cubed \ No newline at end of file diff --git a/src/gameplay/network_server.cpp b/src/gameplay/network_server.cpp new file mode 100644 index 0000000..9e696c8 --- /dev/null +++ b/src/gameplay/network_server.cpp @@ -0,0 +1,94 @@ +#include "Cubed/gameplay/network_server.hpp" + +#include "Cubed/tools/log.hpp" +using asio::ip::tcp; +namespace Cubed { + +NetworkServer::NetworkServer(int port) : m_port(port) {} + +NetworkServer::~NetworkServer() { stop(); } + +void NetworkServer::stop() { + if (!m_started) { + return; + } + if (m_stopped.exchange(true)) { + return; + } + + m_io.stop(); + + std::vector> sessions; + + { + std::lock_guard lock(m_session_mutex); + + for (auto& [id, s] : m_session) { + sessions.push_back(s); + } + + m_session.clear(); + } + + for (auto& s : sessions) { + s->close(); + } + + if (m_net_thread.joinable()) { + Logger::info("Server join thread={}, current={}", m_net_thread.get_id(), + std::this_thread::get_id()); + m_net_thread.join(); + } + Logger::info("Server Net Thread Stopped!"); +} + +asio::awaitable NetworkServer::listen() { + + try { + tcp::acceptor acceptor(m_io, tcp::endpoint(tcp::v4(), m_port)); + while (!m_stopped) { + tcp::socket socket = + co_await acceptor.async_accept(asio::use_awaitable); + + std::shared_ptr s = + std::make_shared(std::move(socket), m_world, m_io); + { + std::lock_guard lock(m_session_mutex); + m_session.emplace(s->uuid(), s); + } + s->start(); + } + } catch (const std::exception& e) { + if (!m_stopped) { + Logger::error("accept error {}", e.what()); + } + } catch (...) { + if (!m_stopped) { + Logger::error("Network Server: Unknown Error"); + } + } + + co_return; +} + +void NetworkServer::net_run() { + if (m_net_thread.joinable()) { + return; + } + m_net_thread = std::thread([this]() { + asio::co_spawn(m_io, listen(), asio::detached); + m_io.run(); + }); + Logger::info("Server Started!"); +} + +void NetworkServer::start_server(int port) { + m_port = port; + m_world.init_world(); + net_run(); + m_started = true; +} + +int NetworkServer::port() const { return m_port; } +ServerWorld& NetworkServer::server_world() { return m_world; } +} // namespace Cubed \ No newline at end of file diff --git a/src/gameplay/server_chunk.cpp b/src/gameplay/server_chunk.cpp new file mode 100644 index 0000000..ee7f711 --- /dev/null +++ b/src/gameplay/server_chunk.cpp @@ -0,0 +1,211 @@ +#include "Cubed/gameplay/server_chunk.hpp" + +#include "Cubed/tools/cubed_assert.hpp" + +namespace Cubed { +ServerChunk::ServerChunk(ServerWorld& world, ChunkPos chunk_pos, + bool temp_chunk) + : m_temp_chunk(temp_chunk), m_chunk_pos(chunk_pos), m_world(world) {} + +ServerChunk::ServerChunk(ServerChunk&& other) noexcept + : m_gening(other.m_gening.load()), m_has_cave(other.m_has_cave), + m_biome(other.m_biome.load()), m_chunk_pos(std::move(other.m_chunk_pos)), + m_world(other.m_world), m_heightmap(std::move(other.m_heightmap)), + m_blocks(std::move(other.m_blocks)), + m_neightbor_blocks(std::move(other.m_neightbor_blocks)), + m_seed(other.m_seed), m_conditions(other.m_conditions) { + ASSERT_MSG(!other.m_gening, "Other is Gening Can't Move"); +} + +ServerChunk& ServerChunk::operator=(ServerChunk&& other) noexcept { + // Logger::info("other Chunk pos {} {} in Chunk& Chunk::operator=(Chunk&& + // other) this {}", other.m_chunk_pos.x, other.m_chunk_pos.z, + // static_cast(&other)); + if (this == &other) { + return *this; + } + ASSERT_MSG(!other.m_gening, "Other is Gening Can't Move"); + m_chunk_pos = std::move(other.m_chunk_pos); + m_heightmap = std::move(other.m_heightmap); + m_blocks = std::move(other.m_blocks); + m_biome = other.m_biome.load(); + m_seed = other.m_seed; + m_conditions = other.m_conditions; + m_neightbor_blocks = std::move(other.m_neightbor_blocks); + m_has_cave = other.m_has_cave; + m_gening = other.m_gening.load(); + return *this; +} + +std::tuple ServerChunk::world_to_block(int world_x, int world_y, + int world_z, int chunk_x, + int chunk_z) { + int x, y, z; + y = world_y; + x = world_x - chunk_x * CHUNK_SIZE; + z = world_z - chunk_z * CHUNK_SIZE; + return {x, y, z}; +} + +std::tuple +ServerChunk::world_to_block(const glm::ivec3& block_pos, ChunkPos chunk_pos) { + return world_to_block(block_pos.x, block_pos.y, block_pos.z, chunk_pos.x, + chunk_pos.z); +} + +std::tuple +ServerChunk::block_to_world(int x, int y, int z, int chunk_x, int chunk_z) { + int world_x = x + chunk_x * CHUNK_SIZE; + int world_z = z + chunk_z * CHUNK_SIZE; + int world_y = y; + return {world_x, world_y, world_z}; +} +std::tuple +ServerChunk::block_to_world(const glm::ivec3& block_pos, ChunkPos chunk_pos) { + return block_to_world(block_pos.x, block_pos.y, block_pos.z, chunk_pos.x, + chunk_pos.z); +} + +BiomeType ServerChunk::get_biome() const { return m_biome.load(); } + +ChunkPos ServerChunk::get_chunk_pos() const { return m_chunk_pos; } + +const std::vector& ServerChunk::get_chunk_blocks() const { + return m_blocks; +} + +HeightMapArray ServerChunk::get_heightmap() const { + // Logger::info("Chunk pos {} {} in get_heightmap this {}", m_chunk_pos.x, + // m_chunk_pos.z, static_cast(this)); + return m_heightmap; +} + +int ServerChunk::index(int x, int y, int z) { + ASSERT(!(x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y || + z >= CHUNK_SIZE)); + if ((x * WORLD_SIZE_Y + y) * CHUNK_SIZE + z < 0 || + (x * WORLD_SIZE_Y + y) * CHUNK_SIZE + z >= + CHUNK_SIZE * CHUNK_SIZE * WORLD_SIZE_Y) { + Logger::error("block pos x {} y {} z {} range error", x, y, z); + ASSERT(0); + } + return (x * WORLD_SIZE_Y + y) * CHUNK_SIZE + z; +} + +int ServerChunk::index(const glm::vec3& pos) { + return ServerChunk::index(pos.x, pos.y, pos.z); +} + +void ServerChunk::gen_phase_one() { + m_generator = std::make_unique(*this); + if (!m_generator) { + Logger::error("ChunkGenerator is Nullptr"); + return; + } + m_generator->assign_chunk_biome(); + m_seed = m_generator->chunk_seed(); +} +void ServerChunk::gen_phase_two() { + if (!m_generator) { + Logger::error("ChunkGenerator is Nullptr"); + return; + } + m_generator->generate_heightmap(); +} +void ServerChunk::gen_phase_three() { + if (!m_generator) { + Logger::error("ChunkGenerator is Nullptr"); + return; + } + m_generator->generate_terrain_blocks(); +} + +void ServerChunk::gen_phase_four( + const std::array>, 4>& + neighbor_block) { + if (!m_generator) { + Logger::error("ChunkGenerator is Nullptr"); + return; + } + // This must be fully completed before any other operations can proceed! + m_generator->blend_surface_blocks_borders(neighbor_block); +} + +void ServerChunk::gen_phase_five() { + if (!m_generator) { + Logger::error("ChunkGenerator is Nullptr"); + return; + } + m_generator->ocean_build(); + m_generator->generate_river(); + m_generator->generate_cave(); + + m_generator->generate_vegetation(); + m_generator = nullptr; +} + +void ServerChunk::gen_chunk() { + if (m_gening.exchange(true)) + return; + m_gening = true; + ASSERT_MSG(m_blocks.empty(), + "Blocks isn't Empty, chunk already generated!"); + if (m_blocks.size() != 0) { + Logger::warn( + "Request Generator Chunk {} {} ,but the Blocks size is Not 0", + m_chunk_pos.x, m_chunk_pos.z); + return; + } + std::vector neighbor; + for (int i = 0; i < 4; i++) { + neighbor.emplace_back(m_world, m_chunk_pos + CHUNK_DIR[i], true); + } + for (auto& chunk : neighbor) { + chunk.gen_phase_one(); + chunk.gen_phase_two(); + chunk.gen_phase_three(); + chunk.gen_phase_five(); + } + gen_phase_one(); + gen_phase_two(); + gen_phase_three(); + + for (int i = 0; i < 4; i++) { + m_neightbor_blocks[i] = neighbor[i].get_chunk_blocks(); + } + gen_phase_four(m_neightbor_blocks); + gen_phase_five(); + m_gening = false; +} +// Logger::info("Cross Sum {}", m_cross_vertices_sum.load()); + +bool ServerChunk::is_temp_chunk() const { return m_temp_chunk.load(); } + +bool& ServerChunk::has_cave() { return m_has_cave; } + +const OptionalBlockVectorArray& ServerChunk::get_neightbor_blocks() const { + return m_neightbor_blocks; +} + +void ServerChunk::set_chunk_block(int index, unsigned id) { + m_blocks[index] = id; +} +ChunkPos ServerChunk::chunk_pos() const { return m_chunk_pos; } + +BiomeType ServerChunk::biome() const { return m_biome; } + +void ServerChunk::biome(BiomeType b) { m_biome = b; } + +HeightMapArray& ServerChunk::heightmap() { return m_heightmap; } +std::vector& ServerChunk::blocks() { return m_blocks; } +ServerWorld& ServerChunk::world() { return m_world; } +unsigned ServerChunk::seed() const { + if (m_seed == 0) { + Logger::warn("Seed Not Generator"); + } + return m_seed; +} + +BiomeConditions& ServerChunk::conditions() { return m_conditions; } + +} // namespace Cubed \ No newline at end of file diff --git a/src/gameplay/server_player.cpp b/src/gameplay/server_player.cpp new file mode 100644 index 0000000..c8ee2bf --- /dev/null +++ b/src/gameplay/server_player.cpp @@ -0,0 +1,56 @@ +#include "Cubed/gameplay/server_player.hpp" + +#include "Cubed/gameplay/server_world.hpp" +namespace Cubed { +ServerPlayer::ServerPlayer(std::string_view name, std::string_view uuid, + ServerWorld& world, std::shared_ptr session, + TickType gametick) + : m_name(name), m_uuid(uuid), m_world(world), m_session(session), + m_last_gametick(gametick) {} +const glm::vec3& ServerPlayer::get_pos() const { return m_pos; } +const std::string& ServerPlayer::get_name() const { return m_name; } +const std::string& ServerPlayer::get_uuid() const { return m_uuid; } +std::shared_ptr ServerPlayer::get_session() const { return m_session; } +void ServerPlayer::update_pos(float x, float y, float z) { + m_pos = glm::vec3{x, y, z}; + ChunkPos chunk_pos = get_chunk_pos(x, z); + float dist = distance2(chunk_pos, m_last_chunk_pos); + if (dist > 2) { + m_world.need_gen(m_uuid); + m_last_chunk_pos = chunk_pos; + } +} + +void ServerPlayer::update_sync_gametick(TickType gametick) { + m_last_gametick = gametick; +} +bool ServerPlayer::is_disconnect(TickType current_gametick) const { + if (current_gametick - m_last_gametick > TIMEOUT) { + return true; + } + return false; +} + +int ServerPlayer::task_id() const { return m_chunk_task_id.load(); } +void ServerPlayer::task_id(int id) { m_chunk_task_id = id; } + +bool ServerPlayer::has_player(ChunkPos pos) const { + std::shared_lock lock(m_chunk_pos_mutex); + return m_player_chunk_pos_set.find(pos) != m_player_chunk_pos_set.end(); +} +void ServerPlayer::update_chunk_set(const ChunkPosSet& set) { + std::lock_guard lock(m_chunk_pos_mutex); + m_player_chunk_pos_set.clear(); + m_player_chunk_pos_set.insert(set.begin(), set.end()); +} + +const ServerPlayer::ChunkPosSet& ServerPlayer::get_chunk_pos_set() const { + std::shared_lock lock(m_chunk_pos_mutex); + return m_player_chunk_pos_set; +} + +ServerPlayer::ChunkPosSet& ServerPlayer::get_chunk_pos_set() { + std::lock_guard lock(m_chunk_pos_mutex); + return m_player_chunk_pos_set; +} +} // namespace Cubed \ No newline at end of file diff --git a/src/gameplay/server_world.cpp b/src/gameplay/server_world.cpp new file mode 100644 index 0000000..cbee30f --- /dev/null +++ b/src/gameplay/server_world.cpp @@ -0,0 +1,829 @@ +#include "Cubed/gameplay/server_world.hpp" + +#include "Cubed/config.hpp" +#include "Cubed/gameplay/packet.hpp" +#include "Cubed/gameplay/session.hpp" +#include "Cubed/tools/cubed_assert.hpp" +#include "Cubed/tools/log.hpp" +#include "Cubed/tools/uuid.hpp" + +#include +#include +using namespace std::chrono; +using namespace std::chrono_literals; +using namespace google::protobuf; + +namespace Cubed { +ServerWorld::ServerWorld() {} + +ServerWorld::~ServerWorld() { stop(); } + +void ServerWorld::stop() { + if (!m_init) { + return; + } + if (m_stopped.exchange(true)) { + return; + } + send_server_stop(); + stop_gen_thread(); + stop_server_thread(); + // wait_all_chunk_tasks(); + stop_thread_pool(); + + m_finished_queue.clear(); + m_chunks.clear(); +} + +void ServerWorld::update_ref_count(const ChunkPosSet& old, + const ChunkPosSet& now) { + + // Elements in the old set that are not contained in now are not needed by + // the current player. + + for (auto& pos : old) { + if (!now.contains(pos)) { + + chunk_acc acc; + if (!m_chunks.find(acc, pos)) { + Logger::warn("Update Ref Count Error, can't Find old pos " + "in m_chunks"); + continue; + } + if (acc->second.ref_count == 0) { + Logger::error("Chunk {} {} error, ref count is 0", pos.x, + pos.z); + m_chunks.erase(acc); + continue; + } + if (--acc->second.ref_count == 0) { + m_chunks.erase(acc); + } + } + } + + for (auto& pos : now) { + + chunk_acc acc; + if (!m_chunks.find(acc, pos)) { + Logger::warn( + "Update Ref Count Error, can't Find now pos in m_chunks"); + continue; + } + if (!old.contains(pos)) { + ++acc->second.ref_count; + } + } +} + +void ServerWorld::send_time() { + Arena arena; + auto* rsp = Arena::Create(&arena); + + rsp->set_day_tick(m_day_tick); + rsp->set_game_tick(m_game_ticks); + + for (auto& [uuid, player] : m_players) { + player.get_session()->send(make_packet(*rsp), 3); + } +} + +void ServerWorld::send_chunk(int task_id, const std::string& uuid, + ChunkPos pos) { + + { + std::shared_lock lock(m_player_mutex); + auto it = m_players.find(uuid); + if (it == m_players.end()) { + return; + } + if (task_id < it->second.task_id()) { + // Old chunk requests are simply discarded + return; + } + } + + Arena arean; + ChunkDataRsp* rsp = Arena::Create(&arean); + auto* rsq_pos = rsp->mutable_pos(); + rsq_pos->set_x(pos.x); + rsq_pos->set_z(pos.z); + { + chunk_caac cacc; + if (!m_chunks.find(cacc, pos)) { + // No chunk found and not generating + Logger::error("Chunk {} {} neither pending nor ready", pos.x, + pos.z); + return; + } + + if (cacc->second.state == ChunkState::GENERATING) { + + m_waiting_chunk_requests.emplace(uuid, task_id, pos); + return; + } + if (cacc->second.state != ChunkState::READY) { + Logger::error("Chunk {} {} is invaild", pos.x, pos.z); + return; + } + + rsp->set_chunk_seed(cacc->second.chunk->seed()); + rsp->set_biome_type(std::to_underlying(cacc->second.chunk->biome())); + auto* blocks = rsp->mutable_chunk_blocks(); + auto& chunk_blocks = cacc->second.chunk->get_chunk_blocks(); + blocks->Assign(chunk_blocks.begin(), chunk_blocks.end()); + auto& neighbor_blocks = cacc->second.chunk->get_neightbor_blocks(); + + auto assign = [](auto* nb, + const std::optional>& blocks) { + if (!blocks) { + return; + } + if (!nb) { + return; + } + nb->Assign(blocks->begin(), blocks->end()); + }; + auto* nb1 = rsp->mutable_neighbor_blocks_1(); + auto* nb2 = rsp->mutable_neighbor_blocks_2(); + auto* nb3 = rsp->mutable_neighbor_blocks_3(); + auto* nb4 = rsp->mutable_neighbor_blocks_4(); + assign(nb1, neighbor_blocks[0]); + assign(nb2, neighbor_blocks[1]); + assign(nb3, neighbor_blocks[2]); + assign(nb4, neighbor_blocks[3]); + } + std::shared_ptr s; + { + std::shared_lock lock(m_player_mutex); + auto it = m_players.find(uuid); + if (it != m_players.end()) { + s = it->second.get_session(); + it->second.update_sync_gametick(m_game_ticks); + } + } + if (!s) { + Logger::error("Player {} session not exist", uuid); + return; + } + rsp->set_task_id(task_id); + s->send(make_packet(*rsp)); +} + +void ServerWorld::init_world() { + + register_timer("player disconnect", 5, [this]() { + std::vector disconnect; + { + std::shared_lock lock(m_player_mutex); + for (auto& [uuid, player] : m_players) { + if (player.is_disconnect(m_game_ticks)) { + disconnect.emplace_back(uuid); + } + } + } + for (auto& uuid : disconnect) { + handle_player_exit(uuid); + } + }); + // Periodically process pending players + register_timer("player chunk send", 1, [this]() { + PendingRequest request; + if (m_waiting_chunk_requests.try_pop(request)) { + handle_chunk_req(request.task_id, request.uuid, request.pos); + } + }); + + m_cave_carcer.init(ChunkGenerator::seed()); + m_river_worm.init(ChunkGenerator::seed()); + // m_chunks.reserve(MAX_DISTANCE * MAX_DISTANCE * 4); + start_thread_pool(); + + auto t1 = std::chrono::system_clock::now(); + + start_gen_thread(); + init_chunks(); + auto t2 = std::chrono::system_clock::now(); + auto d = std::chrono::duration_cast(t2 - t1); + Logger::info("Chunk Block Init Finish, Time Consuming: {}", d); + + start_server_thread(); + m_init = true; +} + +void ServerWorld::init_chunks() { hot_reload(); } + +void ServerWorld::gen_chunks_internal(const std::string& uuid) { + // Logger::info("gen_chunks_internal"); + m_chunk_gen_finished = false; + + ChunkPosSet required_chunks_set; + compute_required_chunks(required_chunks_set, uuid); + std::vector need_gen_chunks_pos; + + ChunkPosSet old_set; + sync_and_collect_missing_chunks(need_gen_chunks_pos, required_chunks_set); + { + std::lock_guard lock(m_player_mutex); + auto it = m_players.find(uuid); + if (it == m_players.end()) { + return; + } + old_set = std::move(it->second.get_chunk_pos_set()); + it->second.update_chunk_set(required_chunks_set); + } + + update_ref_count(old_set, required_chunks_set); + ASSERT_MSG(!required_chunks_set.empty(), "required chunks is empty!!"); + + Logger::info("New Gen Chunks Sum: {}", need_gen_chunks_pos.size()); + + if (need_gen_chunks_pos.empty()) { + m_could_gen = true; + + return; + } + NewChunkVector new_chunks; + + // Create new chunk + + for (auto& pos : need_gen_chunks_pos) { + new_chunks.emplace_back( + pos, std::make_unique(ServerChunk(*this, pos))); + } + + submit_new_chunks(uuid, new_chunks); + m_chunk_gen_finished = true; +} + +void ServerWorld::compute_required_chunks( + ChunkPosSet& required_chunks, const std::optional& uuid) { + glm::vec3 player_pos; + if (uuid == std::nullopt) { + player_pos = glm::vec3{0.0f}; + } else { + player_pos = get_player_pos(uuid.value()); + } + int x = std::floor(player_pos.x); + int z = std::floor(player_pos.z); + auto [chunk_x, chunk_z] = get_chunk_pos(x, z); + int radius = m_rendering_distance; + int r2 = radius * radius; + required_chunks.reserve(radius * radius); + + for (int dx = -radius; dx <= radius; ++dx) { + for (int dz = -radius; dz <= radius; ++dz) { + if (dx * dx + dz * dz <= r2) { + required_chunks.emplace(chunk_x + dx, chunk_z + dz); + } + } + } +} + +void ServerWorld::sync_and_collect_missing_chunks( + std::vector& need_gen_chunks_pos, + const ChunkPosSet& required_chunks) { + + for (auto pos : required_chunks) { + chunk_acc acc; + if (m_chunks.insert(acc, pos)) { + need_gen_chunks_pos.push_back(pos); + acc->second = ChunkEntity{ChunkState::GENERATING, nullptr, 0}; + } + } +} + +void ServerWorld::submit_new_chunks(const std::string& uuid, + NewChunkVector& new_chunks) { + using enum ChunkLoadStyle; + auto pool_ptr = m_gen_thread_pool.load(); + if (!pool_ptr) { + return; + } + switch (m_chunk_load_style) { + case RANDOM: + // Enqueue directly in random order + for (auto& task : new_chunks) { + + pool_ptr->enqueue([&task, this]() { + std::unique_ptr chunk{std::move(task.chunk)}; + chunk->gen_chunk(); + m_finished_queue.push(std::move(chunk)); + }); + } + break; + case CENTER: { + std::vector> tasks; + for (auto& task : new_chunks) { + + tasks.emplace_back(task.pos, &task); + } + glm::vec3 player_pos = get_player_pos(uuid); + ChunkPos player_chunk_pos = get_chunk_pos(player_pos.x, player_pos.z); + auto dist2 = [player_chunk_pos](ChunkPos chunk_pos) { + float dx = player_chunk_pos.x - chunk_pos.x; + float dz = player_chunk_pos.z - chunk_pos.z; + return dx * dx + dz * dz; + }; + + std::sort(tasks.begin(), tasks.end(), + [&dist2](const auto& a, const auto& b) { + return dist2(a.first) < dist2(b.first); + }); + + const int CHUNKS_PER_PRIORITY = m_gen_pool_threads; + + for (size_t i = 0; i < tasks.size(); ++i) { + int priority = 10 + static_cast(i / CHUNKS_PER_PRIORITY); + auto* task = tasks[i].second; + pool_ptr->enqueue(priority, + [this, chunk = std::move(task->chunk)]() mutable { + chunk->gen_chunk(); + m_finished_queue.push(std::move(chunk)); + }); + } + } break; + } +} + +void ServerWorld::start_gen_thread() { + m_gen_running = true; + Logger::info("Gen Thread Started"); + m_gen_thread = std::jthread([this](std::stop_token token) { + while (!token.stop_requested()) { + std::unique_lock lk(m_need_gen_queue_mutex); + + m_gen_cv.wait(lk, token, [this]() { + return m_need_gen_chunk.load() || !m_gen_running || + !m_need_gen_queue.empty(); + }); + if (!m_gen_running) { + break; + } + if (token.stop_requested()) { + break; + } + m_need_gen_chunk = false; + std::string uuid; + if (!m_need_gen_queue.empty()) { + uuid = m_need_gen_queue.front(); + m_need_gen_queue.pop(); + } + lk.unlock(); + gen_chunks_internal(uuid); + } + }); +} + +void ServerWorld::start_server_thread() { + m_server_thread = + std::jthread([this](std::stop_token token) { serever_run(token); }); +} + +void ServerWorld::start_thread_pool() { + int max_thread = std::thread::hardware_concurrency(); + if (m_gen_pool_threads == 0) { + m_gen_pool_threads = change_pool_threads(m_gen_thread_pool, + max_thread - RESERVED_THREADS); + } else { + m_gen_pool_threads = + change_pool_threads(m_gen_thread_pool, m_gen_pool_threads); + } + + if (m_net_pool_threads == 0) { + m_net_pool_threads = change_pool_threads(m_net_thread_pool, 4); + } else { + m_net_pool_threads = + change_pool_threads(m_net_thread_pool, m_net_pool_threads); + } +} + +void ServerWorld::stop_gen_thread() { + m_gen_running = false; + m_gen_cv.notify_all(); + m_gen_thread.request_stop(); + if (m_gen_thread.joinable()) { + m_gen_thread.join(); + } + Logger::info("Gen Thread Stopped"); +} + +void ServerWorld::stop_server_thread() { + m_server_thread.request_stop(); + if (m_server_thread.joinable()) { + m_server_thread.join(); + } +} + +void ServerWorld::stop_thread_pool() { + auto pool_ptr = m_gen_thread_pool.load(); + if (pool_ptr) { + pool_ptr->stop(); + } + m_gen_thread_pool.store(nullptr); + Logger::info("Gen Thread Pool Stopped"); + + auto p = m_net_thread_pool.load(); + if (p) { + p->stop(); + } + m_net_thread_pool.store(nullptr); + Logger::info("Net Thread Pool Stopped"); +} + +void ServerWorld::serever_run(std::stop_token stoken) { + Logger::info("Server Thread Started!"); + + using Clock = std::chrono::steady_clock; + constexpr auto TICK = std::chrono::milliseconds(DEFAULT_PER_TICK_TIME); + + auto next = Clock::now(); + while (!stoken.stop_requested()) { + next += TICK; + if (m_tick_running) { + ++m_game_ticks; + m_day_tick = (m_day_tick + 1) % DAY_TIME; + } + update(); + std::this_thread::sleep_until(next); + } + Logger::info("Server Thread Stopped!"); +} + +void ServerWorld::need_gen(std::string uuid) { + + // if (!m_could_gen) { + // Logger::warn("It is generating or consuming new chunks"); + // return; + // } + + m_could_gen = false; + + { + std::lock_guard lock(m_need_gen_queue_mutex); + m_need_gen_queue.enqueue(std::move(uuid)); + } + + // m_gen_player_pos = get_player("TestPlayer").get_player_pos(); + + m_need_gen_chunk = true; + + m_gen_cv.notify_one(); +} + +bool ServerWorld::set_block(const glm::ivec3& block_pos, unsigned id) { + + int world_x, world_y, world_z; + world_x = block_pos.x; + world_y = block_pos.y; + world_z = block_pos.z; + + auto [chunk_x, chunk_z] = get_chunk_pos(world_x, world_z); + chunk_acc acc; + + if (!m_chunks.find(acc, ChunkPos{chunk_x, chunk_z})) { + return false; + } + if (acc->second.state != ChunkState::READY) { + return false; + } + auto [x, y, z] = ServerChunk::world_to_block(world_x, world_y, world_z, + chunk_x, chunk_z); + if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y || + z >= CHUNK_SIZE) { + return false; + } + + acc->second.chunk->set_chunk_block(ServerChunk::index(x, y, z), id); + return true; +} + +void ServerWorld::hot_reload() { + auto& config = Config::get(); + int dist = config.get("world.rendering_distance"); + m_rendering_distance = dist <= MAX_DISTANCE ? dist : MAX_DISTANCE; +} + +void ServerWorld::update() { + // poll_finished_chunks(); + { + bool consumed = false; + std::unique_ptr chunk; + while (m_finished_queue.try_pop(chunk)) { + if (!chunk) { + Logger::error("Finished Queue has nullptr Chunk"); + return; + } + chunk_acc acc; + auto pos = chunk->get_chunk_pos(); + if (!m_chunks.find(acc, pos)) { + Logger::error( + "New Chunk {} {} not Find, don't move to m_chunks", pos.x, + pos.z); + continue; + } + acc->second.chunk = std::move(chunk); + acc->second.state = ChunkState::READY; + consumed = true; + } + if (consumed) { + m_could_gen = true; + } + } + + send_time(); + for (auto& [id, timer] : m_timers) { + timer.update(); + } +} + +void ServerWorld::sync_player_pos(const std::string& uuid, float x, float y, + float z) { + std::string name; + { + std::lock_guard lock(m_player_mutex); + auto it = m_players.find(uuid); + if (it == m_players.end()) { + Logger::warn("Player {} is not in this Server", uuid); + return; + } + it->second.update_pos(x, y, z); + it->second.update_sync_gametick(m_game_ticks); + name = it->second.get_name(); + } + ChunkPos pos = get_chunk_pos(x, z); + // update other player pos; + std::vector> other; + { + std::shared_lock lock(m_player_mutex); + for (auto& [o_uuid, player] : m_players) { + if (o_uuid == uuid) { + continue; + } + if (player.has_player(pos)) { + other.emplace_back(player.get_session()); + } + } + } + + for (auto& session : other) { + if (!session) { + continue; + } + Arena arena; + auto* rsp = Arena::Create(&arena); + rsp->set_uuid(uuid); + rsp->set_name(name); + auto* pos = rsp->mutable_pos(); + pos->set_x(x); + pos->set_y(y); + pos->set_z(z); + session->send(make_packet(*rsp), 0); + } +} + +void ServerWorld::handle_player_login(const std::string& name, + std::shared_ptr session) { + std::string uuid = generate_uuid(); + Logger::info("Player {} (uuid {}) join the world", name, uuid); + bool sucess = true; + { + std::lock_guard lock(m_player_mutex); + auto [_, inserted] = m_players.emplace( + std::piecewise_construct, std::forward_as_tuple(std::string(uuid)), + std::forward_as_tuple(name, uuid, *this, session, m_game_ticks)); + if (!inserted) { + Logger::error("Player insert Fail"); + } + sucess = inserted; + } + + Arena arena; + if (!sucess) { + auto* rsp = Arena::Create(&arena); + rsp->set_success(false); + session->send(make_packet(*rsp), 0); + return; + } + + m_uuid_to_name.emplace(uuid, name); + // Pre-insert into new_chunks to ensure correct addition to waiting_player + /*ChunkPosSet required_chunks; + compute_required_chunks(required_chunks, uuid); + std::vector need_gen_chunks_pos; + + sync_and_collect_missing_chunks(need_gen_chunks_pos, required_chunks); + + { + std::lock_guard lock(m_new_chunk_mutex); + for (auto& pos : need_gen_chunks_pos) { + m_new_chunks.emplace(pos, ServerChunk(*this, pos)); + } + } + */ + need_gen(uuid); + + auto* rsp = Arena::Create(&arena); + rsp->set_success(true); + rsp->set_uuid(uuid); + session->send(make_packet(*rsp), 0); +} + +void ServerWorld::handle_player_exit(const std::string& uuid) { + std::shared_ptr exit_session; + ChunkPosSet old_set; + { + std::lock_guard lock(m_player_mutex); + auto it = m_players.find(uuid); + if (it != m_players.end()) { + Logger::info("Player {} Exit the Server", it->second.get_name()); + exit_session = it->second.get_session(); + old_set = std::move(it->second.get_chunk_pos_set()); + m_players.erase(it); + } else { + Logger::error("Player {} isn't in Server", uuid); + return; + } + } + + m_uuid_to_name.erase(uuid); + + update_ref_count(old_set, {}); + + Arena arena; + auto* rsp = Arena::Create(&arena); + rsp->set_uuid(uuid); + rsp->set_server_stop(false); + exit_session->send(make_packet(*rsp), 0); + + std::vector> sessions; + { + std::shared_lock lock(m_player_mutex); + for (auto& [uuid, player] : m_players) { + sessions.emplace_back(player.get_session()); + } + } + + for (auto& s : sessions) { + if (s) { + s->send(make_packet(*rsp), 0); + } + } +} + +glm::vec3 ServerWorld::get_player_pos(const std::string& uuid) const { + std::shared_lock lock(m_player_mutex); + auto it = m_players.find(uuid); + if (it == m_players.end()) { + Logger::error("Can't find player uuid {}", uuid); + return glm::vec3{0.0f}; + } + return it->second.get_pos(); +} + +void ServerWorld::handle_chunk_req(int task_id, const std::string& uuid, + ChunkPos pos) { + { + std::shared_lock lock(m_player_mutex); + auto it = m_players.find(uuid); + if (it == m_players.end()) { + return; + } + if (it->second.task_id() < task_id) { + // task_id is an atomic variable, can be operated on directly + it->second.task_id(task_id); + } + } + auto pool = m_net_thread_pool.load(); + pool->enqueue( + [task_id, uuid, pos, this]() { send_chunk(task_id, uuid, pos); }); +} + +void ServerWorld::handle_block_change(const BlockChangeReq& req) { + float x = std::floor(req.pos().x()); + float y = std::floor(req.pos().y()); + float z = std::floor(req.pos().z()); + if (!set_block(glm::ivec3(x, y, z), req.block())) { + return; + } + + Arena arena; + BlockChangeRsp* rsp = Arena::Create(&arena); + auto* pos = rsp->mutable_pos(); + pos->set_x(x); + pos->set_y(y); + pos->set_z(z); + rsp->set_block(req.block()); + std::vector> sessions; + auto chunk_pos = get_chunk_pos(x, z); + { + std::shared_lock lock(m_player_mutex); + for (auto& [uuid, player] : m_players) { + if (player.has_player(chunk_pos)) { + auto session = player.get_session(); + sessions.emplace_back(std::move(session)); + } + } + } + + for (auto& x : sessions) { + if (x) { + x->send(make_packet(*rsp), 1); + } + } +} + +int ServerWorld::rendering_distance() const { + return m_rendering_distance.load(); +} + +void ServerWorld::rendering_distance(int rendering_distance) { + m_rendering_distance = rendering_distance; +} + +CaveCarver& ServerWorld::cave_carcer() { return m_cave_carcer; } +RiverWorm& ServerWorld::river_worm() { return m_river_worm; } + +TickType ServerWorld::game_tick() const { return m_game_ticks.load(); } +TickType ServerWorld::day_tick() const { return m_day_tick.load(); } +void ServerWorld::day_tick(TickType tick) { + tick %= DAY_TIME; + m_day_tick = tick; +} +int ServerWorld::per_tick_time() const { return m_per_tick_time.load(); } +void ServerWorld::per_tick_time(int ms) { m_per_tick_time = ms; } + +bool ServerWorld::is_tick_running() const { return m_tick_running.load(); } +void ServerWorld::tick_running(bool run) { m_tick_running = run; } +int ServerWorld::gen_pool_threads() const { return m_gen_pool_threads.load(); } +int ServerWorld::max_threads() const { return m_max_threads.load(); } + +void ServerWorld::change_pool_threads(ThreadPoolKind kind, int threads) { + switch (kind) { + case ThreadPoolKind::NET: + m_net_pool_threads = change_pool_threads(m_net_thread_pool, threads); + break; + case ThreadPoolKind::GEN: + m_gen_pool_threads = change_pool_threads(m_gen_thread_pool, threads); + break; + } +} + +int ServerWorld::change_pool_threads( + std::atomic>& thread_pool, int threads) { + m_max_threads = std::thread::hardware_concurrency(); + if (m_max_threads < 1) { + Logger::warn("Can't Get Max Support Threads, Set Max Threads to 4"); + m_max_threads = 1; + } + int used_thread = std::clamp(threads, 1, m_max_threads.load()); + Logger::info("Create New Thread Pool Use {} Threads", used_thread); + thread_pool.store(std::make_shared(used_thread)); + return used_thread; +} + +int ServerWorld::change_pool_threads( + std::atomic>& thread_pool, + int threads) { + m_max_threads = std::thread::hardware_concurrency(); + if (m_max_threads < 1) { + Logger::warn("Can't Get Max Support Threads, Set Max Threads to 4"); + m_max_threads = 1; + } + int used_thread = std::clamp(threads, 1, m_max_threads.load()); + Logger::info("Create New Thread Pool Use {} Threads", used_thread); + thread_pool.store(std::make_shared(used_thread)); + return used_thread; +} + +void ServerWorld::send_server_stop() { + Arena arena; + auto* rsp = Arena::Create(&arena); + rsp->set_server_stop(true); + std::shared_lock lock(m_player_mutex); + for (auto& [uuid, player] : m_players) { + player.get_session()->send(make_packet(*rsp), 0); + } + Logger::info("Send Server Mesaage Success"); +} + +int ServerWorld::chunk_load_style() const { + return std::to_underlying(m_chunk_load_style.load()); +} +void ServerWorld::set_chunk_load_style(int id) { + using enum ChunkLoadStyle; + + switch (id) { + case std::to_underlying(RANDOM): + m_chunk_load_style = RANDOM; + return; + case std::to_underlying(CENTER): + m_chunk_load_style = CENTER; + return; + } + Logger::error("Can,t Find Chunk Load Style Id {}, Nothing Will Do", id); +} + +int ServerWorld::chunk_size() const { return m_chunks.size(); } + +} // namespace Cubed \ No newline at end of file diff --git a/src/gameplay/session.cpp b/src/gameplay/session.cpp new file mode 100644 index 0000000..496aca1 --- /dev/null +++ b/src/gameplay/session.cpp @@ -0,0 +1,150 @@ +#include "Cubed/gameplay/session.hpp" + +#include "Cubed/gameplay/server_world.hpp" +#include "Cubed/tools/log.hpp" +#include "Cubed/tools/uuid.hpp" +using asio::ip::tcp; +using namespace google::protobuf; +namespace Cubed { +Session::Session(tcp::socket socket, ServerWorld& server_world, + asio::io_context& io) + : m_socket(std::move(socket)), m_strand(asio::make_strand(io)), + m_uuid(generate_uuid()), m_server_world(server_world) {} + +Session::~Session() {} + +void Session::start() { + auto self = shared_from_this(); + asio::co_spawn( + m_strand, + [self]() -> asio::awaitable { co_await self->read_loop(); }, + asio::detached); +} + +void Session::send(std::shared_ptr> packet, int priority) { + asio::post(m_strand, [self = shared_from_this(), packet = std::move(packet), + priority]() mutable { + bool idle = self->m_write_queue.empty(); + self->m_write_queue.emplace(priority, self->m_sequence++, + std::move(packet)); + if (idle) { + self->do_write(); + } + }); +} + +const std::string& Session::uuid() const { return m_uuid; } + +asio::awaitable Session::read_loop() { + try { + while (true) { + std::array header_buffer; + co_await asio::async_read(m_socket, asio::buffer(header_buffer), + asio::use_awaitable); + + auto header = decode_packet_header(header_buffer); + uint32_t total_len = HEADER_LEN + header.compressed_size; + + if (total_len < HEADER_LEN || total_len > MAX_PACKET_SIZE) { + + throw std::runtime_error("invalid packet length"); + } + std::vector body_data(header.compressed_size); + if (header.compressed_size > 0) { + co_await asio::async_read(m_socket, asio::buffer(body_data), + asio::use_awaitable); + } + auto cmd_id = header.cmd; + Arena arena; + if (cmd_id == std::to_underlying(PacketEnum::LOGIN_REQ)) { + auto* req = Arena::Create(&arena); + Logger::info("Session: Receive Login req"); + if (decode_packet(*req, body_data, header)) { + m_server_world.handle_player_login(req->name(), + shared_from_this()); + } + } + if (cmd_id == std::to_underlying(PacketEnum::PLAYER_POS)) { + auto* pos = Arena::Create(&arena); + if (decode_packet(*pos, body_data, header)) { + m_server_world.sync_player_pos(pos->uuid(), pos->pos().x(), + pos->pos().y(), + pos->pos().z()); + } + } + if (cmd_id == std::to_underlying(PacketEnum::CHUNK_DATA_REQ)) { + auto* req = Arena::Create(&arena); + // Logger::info("Session: Receive Chunk Data req"); + if (decode_packet(*req, body_data, header)) { + m_server_world.handle_chunk_req( + req->task_id(), req->uuid(), + ChunkPos(req->pos().x(), req->pos().z())); + } + } + if (cmd_id == std::to_underlying(PacketEnum::BLOCK_CHANGE_REQ)) { + auto* req = Arena::Create(&arena); + Logger::info("Session: Receive Block Change req"); + if (decode_packet(*req, body_data, header)) { + m_server_world.handle_block_change(*req); + } + } + if (cmd_id == std::to_underlying(PacketEnum::LOGOUT_REQ)) { + auto* req = Arena::Create(&arena); + if (decode_packet(*req, body_data, header)) { + m_server_world.handle_player_exit(req->uuid()); + } + } + } + } catch (const asio::system_error& e) { + auto ec = e.code(); + + if (ec == asio::error::eof || ec == asio::error::operation_aborted) { + + Logger::info("Client disconnected"); + } else { + Logger::warn("Asio Error {}", e.what()); + } + + close(); + } catch (const std::exception& e) { + Logger::error("Session Error {}", e.what()); + close(); + } catch (...) { + Logger::error("Unknow Error"); + close(); + } + co_return; +} + +void Session::do_write() { + + auto self = shared_from_this(); + auto packet = std::move(m_write_queue.top().packet); + asio::async_write( + m_socket, asio::buffer(*packet), + asio::bind_executor(m_strand, [self](std::error_code ec, size_t) { + if (ec) { + Logger::warn("Write Ec {}", ec.message()); + self->close(); + return; + } + self->m_write_queue.pop(); + if (!self->m_write_queue.empty()) { + self->do_write(); + } + })); +} + +void Session::close() { + if (m_closed.exchange(true)) { + return; + } + + std::error_code ec; + + m_socket.shutdown(tcp::socket::shutdown_both, ec); + + m_socket.close(ec); +} + +} // namespace Cubed \ No newline at end of file diff --git a/src/gameplay/tree.cpp b/src/gameplay/tree.cpp index e717ac7..4589bfb 100644 --- a/src/gameplay/tree.cpp +++ b/src/gameplay/tree.cpp @@ -1,6 +1,6 @@ #include "Cubed/gameplay/tree.hpp" -#include "Cubed/gameplay/chunk.hpp" +#include "Cubed/gameplay/server_chunk.hpp" #include @@ -27,10 +27,10 @@ static constexpr std::array TREE{{ {{-1, 3, -2}, 6}, {{-2, 3, -1}, 6}, }}; -bool build_tree(Chunk& chunk, const glm::ivec3& pos) { +bool build_tree(ServerChunk& chunk, const glm::ivec3& pos) { auto& block = chunk.get_chunk_blocks(); - if (block[Chunk::index(pos)] != 1) { + if (block[ServerChunk::index(pos)] != 1) { return false; } for (const auto& d : TREE) { @@ -42,13 +42,13 @@ bool build_tree(Chunk& chunk, const glm::ivec3& pos) { z >= CHUNK_SIZE) { return false; } - if (block[Chunk::index(tree_node)] != 0) { + if (block[ServerChunk::index(tree_node)] != 0) { return false; } } for (const auto& d : TREE) { auto tree_node = pos + d.offset; - chunk.set_chunk_block(Chunk::index(tree_node), d.id); + chunk.set_chunk_block(ServerChunk::index(tree_node), d.id); } return true; } diff --git a/src/gameplay/vertex_data.cpp b/src/gameplay/vertex_data.cpp index 3b3421e..fba1101 100644 --- a/src/gameplay/vertex_data.cpp +++ b/src/gameplay/vertex_data.cpp @@ -1,9 +1,9 @@ #include "Cubed/gameplay/vertex_data.hpp" -#include "Cubed/gameplay/world.hpp" +#include "Cubed/gameplay/client_world.hpp" namespace Cubed { -VertexData::VertexData(World& world) : m_world(world) {} +VertexData::VertexData(ClientWorld& world) : m_world(world) {} VertexData::~VertexData() { if (m_vbo != 0) { m_world.push_delete_vbo(m_vbo); diff --git a/src/gameplay/world.cpp b/src/gameplay/world.cpp deleted file mode 100644 index 338315b..0000000 --- a/src/gameplay/world.cpp +++ /dev/null @@ -1,703 +0,0 @@ -#include "Cubed/gameplay/world.hpp" - -#include "Cubed/config.hpp" -#include "Cubed/gameplay/player.hpp" -#include "Cubed/tools/cubed_assert.hpp" -#include "Cubed/tools/cubed_hash.hpp" - -#include -#include -#include -using namespace std::chrono; -using namespace std::chrono_literals; - -namespace Cubed { - -struct ChunkRenderData { - std::array*, 4> neighbor_block; - Chunk* chunk; -}; - -World::World() {} - -World::~World() { - stop_gen_thread(); - stop_server_thread(); - wait_all_chunk_tasks(); - stop_thread_pool(); - - m_chunks.clear(); - { - std::lock_guard lk(m_delete_vbo_mutex); - for (auto x : m_pending_delete_vbo) { - glDeleteBuffers(1, &x); - } - m_pending_delete_vbo.clear(); - } - { - std::lock_guard lk(m_delete_vao_mutex); - for (auto x : m_pending_delete_vao) { - glDeleteVertexArrays(1, &x); - } - m_pending_delete_vao.clear(); - } -} - -void World::wait_all_chunk_tasks() { - for (auto& [pos, task] : new_chunks) { - task.future.get(); - } -} - -bool World::can_move(const AABB& player_box) const { return true; } - -const std::optional& -World::get_look_block_pos(const std::string& name) const { - static std::optional null_look_block = std::nullopt; - auto it = m_players.find(HASH::str(name)); - if (it == m_players.end()) { - Logger::error("Can't find player {}", name); - ASSERT(0); - return null_look_block; - } - - return it->second.get_look_block_pos(); -} -/* -const Chunk* World::get_chunk(const ChunkPos& pos) const { - std::lock_guard lk(m_chunks_mutex); - auto it = m_chunks.find(pos); - if (it == m_chunks.end()) { - return nullptr; - } - return &it->second; -}*/ - -Player& World::get_player(const std::string& name) { - auto it = m_players.find(HASH::str(name)); - if (it == m_players.end()) { - Logger::error("Can't find player {}", name); - ASSERT(0); - } - - return it->second; -} - -void World::init_world() { - m_cave_carcer.init(ChunkGenerator::seed()); - m_river_worm.init(ChunkGenerator::seed()); - m_chunks.reserve(MAX_DISTANCE * MAX_DISTANCE * 4); - start_thread_pool(); - - auto t1 = std::chrono::system_clock::now(); - - // init players - m_players.emplace(HASH::str("TestPlayer"), Player(*this, "TestPlayer")); - - start_gen_thread(); - init_chunks(); - auto t2 = std::chrono::system_clock::now(); - auto d = std::chrono::duration_cast(t2 - t1); - Logger::info("Chunk Block Init Finish, Time Consuming: {}", d); - - start_server_thread(); - - Logger::info("TestPlayer Create Finish"); -} -void World::init_chunks() { - hot_reload(); - while (!m_chunk_gen_finished) { - // Logger::info("World Spawn: {:.2f}%", m_chunk_gen_fraction.load()); - std::this_thread::sleep_for(std::chrono::microseconds(200)); - } -} - -ChunkPos World::get_chunk_pos(int world_x, int world_z) { - int chunk_x, chunk_z; - if (world_x < 0) { - chunk_x = (world_x + 1) / CHUNK_SIZE - 1; - } - if (world_x >= 0) { - chunk_x = world_x / CHUNK_SIZE; - } - if (world_z < 0) { - chunk_z = (world_z + 1) / CHUNK_SIZE - 1; - } - if (world_z >= 0) { - chunk_z = world_z / CHUNK_SIZE; - } - return {chunk_x, chunk_z}; -} - -#pragma region ChunkGenerate - -void World::gen_chunks_internal() { - // Logger::info("gen_chunks_internal"); - m_chunk_gen_finished = false; - - ChunkPosSet required_chunks; - compute_required_chunks(required_chunks); - - ASSERT_MSG(!required_chunks.empty(), "required chunks is empty!!"); - - std::vector need_gen_chunks_pos; - - sync_and_collect_missing_chunks(need_gen_chunks_pos, required_chunks); - - Logger::info("New Gen Chunks Sum: {}", need_gen_chunks_pos.size()); - - if (need_gen_chunks_pos.empty()) { - m_could_gen = true; - - return; - } - - for (auto& pos : need_gen_chunks_pos) { - new_chunks.emplace(pos, Chunk(*this, pos)); - } - - submit_new_chunks(); - m_chunk_gen_finished = true; -} - -void World::sync_player_pos(glm::vec3& player_pos) { - std::lock_guard lk(m_gen_player_pos_mutex); - player_pos = m_gen_player_pos; -} - -void World::compute_required_chunks(ChunkPosSet& required_chunks) { - glm::vec3 player_pos; - sync_player_pos(player_pos); - - int x = std::floor(player_pos.x); - int z = std::floor(player_pos.z); - auto [chunk_x, chunk_z] = get_chunk_pos(x, z); - int radius = m_rendering_distance; - int r2 = radius * radius; - required_chunks.reserve(radius * radius); - - for (int dx = -radius; dx <= radius; ++dx) { - for (int dz = -radius; dz <= radius; ++dz) { - if (dx * dx + dz * dz <= r2) { - required_chunks.emplace(chunk_x + dx, chunk_z + dz); - } - } - } -} - -void World::sync_and_collect_missing_chunks( - std::vector& need_gen_chunks_pos, - const ChunkPosSet& required_chunks) { - std::lock_guard lk(m_chunks_mutex); - for (auto it = m_chunks.begin(); it != m_chunks.end();) { - if (required_chunks.find(it->first) == required_chunks.end()) { - it = m_chunks.erase(it); - } else { - ++it; - } - } - - for (auto pos : required_chunks) { - auto it = m_chunks.find(pos); - if (it == m_chunks.end()) { - need_gen_chunks_pos.push_back(pos); - } - } -} - -void World::submit_new_chunks() { - using enum ChunkLoadStyle; - std::lock_guard lock(m_new_chunk_mutex); - auto pool_ptr = m_gen_thread_pool.load(); - if (!pool_ptr) { - return; - } - switch (m_chunk_load_style) { - case RANDOM: - for (auto& [pos, task] : new_chunks) { - if (!task.future.valid()) { - task.future = - pool_ptr->enqueue([&task]() { task.chunk.gen_chunk(); }); - } - } - break; - case CENTER: { - std::vector> tasks; - for (auto& [pos, task] : new_chunks) { - if (!task.future.valid()) { - tasks.emplace_back(pos, &task); - } - } - glm::vec3 player_pos; - sync_player_pos(player_pos); - auto dist2 = [player_pos](ChunkPos chunk_pos) { - ChunkPos player_chunk_pos = - get_chunk_pos(player_pos.x, player_pos.z); - float dx = player_chunk_pos.x - chunk_pos.x; - float dz = player_chunk_pos.z - chunk_pos.z; - return dx * dx + dz * dz; - }; - - std::sort(tasks.begin(), tasks.end(), - [&dist2](const auto& a, const auto& b) { - return dist2(a.first) < dist2(b.first); - }); - for (auto& [pos, task] : tasks) { - if (!task->future.valid()) { - task->future = - pool_ptr->enqueue([task]() { task->chunk.gen_chunk(); }); - } - } - } - } -} - -void World::poll_finished_chunks() { - m_new_finished_chunk.clear(); - std::lock_guard lock(m_new_chunk_mutex); - std::erase_if( - new_chunks, [&](std::pair& pair) { - auto& pending = pair.second; - if (!pending.future.valid()) { - return false; - } - if (pending.future.wait_for(0ms) != std::future_status::ready) { - return false; - } - pending.future.get(); - - m_new_finished_chunk.emplace_back(pair.first, - std::move(pending.chunk)); - return true; - }); -} - -#pragma endregion - -void World::start_gen_thread() { - m_gen_running = true; - Logger::info("Gen Thread Started"); - m_gen_thread = std::thread([this]() { - while (m_gen_running) { - std::unique_lock lk(m_gen_signal_mutex); - - m_gen_cv.wait(lk, [this]() { - return m_need_gen_chunk.load() || !m_gen_running; - }); - if (!m_gen_running) { - break; - } - m_need_gen_chunk = false; - lk.unlock(); - - gen_chunks_internal(); - } - }); -} - -void World::start_server_thread() { - m_server_thread = std::thread( - [this]() { serever_run(m_server_stop_source.get_token()); }); -} - -void World::stop_gen_thread() { - m_gen_running = false; - m_gen_cv.notify_all(); - if (m_gen_thread.joinable()) { - m_gen_thread.join(); - } - Logger::info("Gen Thread Stopped"); -} - -void World::stop_server_thread() { - m_server_stop_source.request_stop(); - if (m_server_thread.joinable()) { - m_server_thread.join(); - } -} - -void World::stop_thread_pool() { - auto pool_ptr = m_gen_thread_pool.load(); - if (pool_ptr) { - pool_ptr->stop(); - } - m_gen_thread_pool.store(nullptr); - Logger::info("Thread Pool Stopped"); -} - -void World::start_thread_pool() { - int max_thread = std::thread::hardware_concurrency(); - if (m_pool_threads == 0) { - change_pool_threads(max_thread - RESERVED_THREADS); - } else { - change_pool_threads(m_pool_threads); - } -} - -void World::serever_run(std::stop_token stoken) { - Logger::info("Server Thread Started!"); - while (!stoken.stop_requested()) { - std::this_thread::sleep_for(milliseconds(m_per_tick_time)); - if (m_tick_running) { - ++m_game_ticks; - m_day_tick = (m_day_tick + 1) % DAY_TIME; - } - } - Logger::info("Server Thread Stopped!"); -} - -void World::need_gen() { - - if (!m_could_gen) { - Logger::warn("It is generating or consuming new chunks"); - return; - } - - m_could_gen = false; - { - std::lock_guard lk(m_gen_player_pos_mutex); - m_gen_player_pos = get_player("TestPlayer").get_player_pos(); - } - - m_need_gen_chunk = true; - - m_gen_cv.notify_one(); -} - -int World::get_block(const glm::ivec3& block_pos) const { - auto [chunk_x, chunk_z] = get_chunk_pos(block_pos.x, block_pos.z); - std::shared_lock lk(m_chunks_mutex); - auto it = m_chunks.find(ChunkPos{chunk_x, chunk_z}); - - if (it == m_chunks.end()) { - return 0; - } - - const auto& chunk_blocks = it->second.get_chunk_blocks(); - auto [x, y, z] = Chunk::world_to_block(block_pos, {chunk_x, chunk_z}); - if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y || - z >= CHUNK_SIZE) { - return 0; - } - return chunk_blocks[Chunk::index(x, y, z)]; -} - -bool World::is_solid(const glm::ivec3& block_pos) const { - auto [chunk_x, chunk_z] = get_chunk_pos(block_pos.x, block_pos.z); - std::shared_lock lk(m_chunks_mutex); - auto it = m_chunks.find(ChunkPos{chunk_x, chunk_z}); - - if (it == m_chunks.end()) { - return false; - } - const auto& chunk_blocks = it->second.get_chunk_blocks(); - auto [x, y, z] = Chunk::world_to_block(block_pos, {chunk_x, chunk_z}); - if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y || - z >= CHUNK_SIZE) { - return false; - } - auto id = chunk_blocks[Chunk::index(x, y, z)]; - if (BlockManager::is_gas(id) || BlockManager::is_liquid(id)) { - return false; - } else { - return true; - } -} - -bool World::can_pass_block(const glm::ivec3& block_pos) const { - auto [chunk_x, chunk_z] = get_chunk_pos(block_pos.x, block_pos.z); - std::shared_lock lk(m_chunks_mutex); - auto it = m_chunks.find(ChunkPos{chunk_x, chunk_z}); - - if (it == m_chunks.end()) { - return true; - } - const auto& chunk_blocks = it->second.get_chunk_blocks(); - auto [x, y, z] = Chunk::world_to_block(block_pos, {chunk_x, chunk_z}); - if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y || - z >= CHUNK_SIZE) { - return true; - } - auto id = chunk_blocks[Chunk::index(x, y, z)]; - return BlockManager::is_passable(id); -} - -BlockType World::get_block_tpye(const glm::ivec3& block_pos) const { - auto [chunk_x, chunk_z] = get_chunk_pos(block_pos.x, block_pos.z); - std::shared_lock lk(m_chunks_mutex); - auto it = m_chunks.find(ChunkPos{chunk_x, chunk_z}); - - if (it == m_chunks.end()) { - // Logger::error("Can't Find Block {} {} {}", block_pos.x, block_pos.y, - // block_pos.z); - return 0; - } - const auto& chunk_blocks = it->second.get_chunk_blocks(); - auto [x, y, z] = Chunk::world_to_block(block_pos, {chunk_x, chunk_z}); - if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y || - z >= CHUNK_SIZE) { - // Logger::error("Can't Find Block {} {} {}", block_pos.x, block_pos.y, - // block_pos.z); - return 0; - } - return chunk_blocks[Chunk::index(x, y, z)]; -} - -void World::set_block(const glm::ivec3& block_pos, unsigned id) { - - int world_x, world_y, world_z; - world_x = block_pos.x; - world_y = block_pos.y; - world_z = block_pos.z; - - auto [chunk_x, chunk_z] = get_chunk_pos(world_x, world_z); - std::lock_guard lk(m_chunks_mutex); - auto it = m_chunks.find(ChunkPos{chunk_x, chunk_z}); - - if (it == m_chunks.end()) { - return; - } - - auto [x, y, z] = - Chunk::world_to_block(world_x, world_y, world_z, chunk_x, chunk_z); - if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y || - z >= CHUNK_SIZE) { - return; - } - - it->second.set_chunk_block(Chunk::index(x, y, z), id); - - static const glm::ivec3 NEIGHBOR_DIRS[] = { - {1, 0, 0}, {-1, 0, 0}, {0, 0, -1}, {0, 0, 1}}; - - for (const auto& dir : NEIGHBOR_DIRS) { - glm::ivec3 neighbor = block_pos + dir; - - auto [cx, cz] = get_chunk_pos(neighbor.x, neighbor.z); - auto it = m_chunks.find({cx, cz}); - if (it != m_chunks.end()) { - it->second.mark_dirty(); - } - } -} - -void World::update(float delta_time) { - for (auto& player : m_players) { - player.second.update(delta_time); - } - { - std::lock_guard lk(m_delete_vbo_mutex); - for (auto x : m_pending_delete_vbo) { - glDeleteBuffers(1, &x); - } - m_pending_delete_vbo.clear(); - } - - { - std::lock_guard lk(m_delete_vao_mutex); - for (auto x : m_pending_delete_vao) { - glDeleteVertexArrays(1, &x); - } - m_pending_delete_vao.clear(); - } - - poll_finished_chunks(); - - for (auto& x : m_new_finished_chunk) { - x.second.upload_to_gpu(); - } - - // unified compute vertex data before rendering - { - std::lock_guard lk(m_chunks_mutex); - bool consumed = false; - - for (auto& x : m_new_finished_chunk) { - m_chunks.insert_or_assign(x.first, std::move(x.second)); - consumed = true; - } - if (consumed) { - m_could_gen = true; - } - - m_render_snapshots.clear(); - for (auto& [pos, chunk] : m_chunks) { - if (chunk.is_dirty()) { - // the curial fator influence - OptionalBlockVectorArray neighbor_block; - for (int i = 0; i < 4; i++) { - auto it = m_chunks.find(pos + CHUNK_DIR[i]); - if (it != m_chunks.end()) { - neighbor_block[i] = (it->second.get_chunk_blocks()); - } else { - neighbor_block[i] = std::nullopt; - } - } - chunk.gen_vertex_data(neighbor_block); - chunk.upload_to_gpu(); - } - if (!chunk.is_dirty()) { - if (chunk.is_need_upload()) { - chunk.upload_to_gpu(); - } - m_render_snapshots.push_back( - {chunk.get_normal_vao(), chunk.get_normal_vertices_sum(), - chunk.get_cross_vao(), chunk.get_cross_vertices_sum(), - chunk.get_normal_discard_vao(), - chunk.get_normal_discard_vertices_sum(), - chunk.get_normal_blend_vao(), - chunk.get_normal_blend_vertices_sum(), - chunk.get_water_vao(), chunk.get_water_vertices_sum(), - glm::vec3(static_cast(pos.x * CHUNK_SIZE) + - static_cast(CHUNK_SIZE / 2), - static_cast(WORLD_SIZE_Y / 2), - static_cast(pos.z * CHUNK_SIZE) + - static_cast(CHUNK_SIZE / 2)), - glm::vec3(static_cast(CHUNK_SIZE / 2), - static_cast(WORLD_SIZE_Y / 2), - static_cast(CHUNK_SIZE / 2))}); - } - } - } -} - -void World::push_delete_vbo(GLuint vbo) { - std::lock_guard lk(m_delete_vbo_mutex); - m_pending_delete_vbo.push_back(vbo); -} - -void World::push_delete_vao(GLuint vao) { - std::lock_guard lk(m_delete_vao_mutex); - m_pending_delete_vao.push_back(vao); -} - -void World::hot_reload() { - auto& config = Config::get(); - int dist = config.get("world.rendering_distance"); - m_rendering_distance = dist <= MAX_DISTANCE ? dist : MAX_DISTANCE; - need_gen(); -} - -void World::rebuild_world() { - if (m_is_rebuilding) { - return; - } - m_is_rebuilding = true; - stop_gen_thread(); - stop_thread_pool(); - m_cave_carcer.reload(ChunkGenerator::seed()); - m_river_worm.reload(ChunkGenerator::seed()); - { - std::lock_guard lk(m_chunks_mutex); - m_chunks.clear(); - m_new_finished_chunk.clear(); - } - m_could_gen = true; - ChunkGenerator::reload(); - start_thread_pool(); - start_gen_thread(); - need_gen(); - - m_is_rebuilding = false; -} - -/* -glm::vec3 World::sunlight_dir() const { - float t = static_cast(m_day_tick) / DAY_TIME; - - float azimuth = glm::radians(90.0f - t * 360.0f); - - float altitude = - glm::half_pi() * sin((t - 0.25f) * glm::two_pi()); - - glm::vec3 dir{cos(altitude) * cos(azimuth), sin(altitude), - cos(altitude) * sin(azimuth)}; - - return glm::normalize(-dir); -} -*/ - -glm::vec3 World::sunlight_dir() const { - float altitude = sin((m_day_tick - 6 * PER_HOUR) / - static_cast(DAY_TIME / 2) * std::numbers::pi) * - 90.0f; - - float t = static_cast(m_day_tick) / DAY_TIME; - float azimuth = 90.0f - 360.0f * (t - 0.25f); - - float alt = glm::radians(altitude); - float az = glm::radians(azimuth); - glm::vec3 dir; - dir.x = cos(alt) * sin(az); - dir.y = sin(alt); - dir.z = cos(alt) * cos(az); - - return glm::normalize(-dir); -} - -int World::rendering_distance() const { return m_rendering_distance.load(); } - -void World::rendering_distance(int rendering_distance) { - m_rendering_distance = rendering_distance; -} - -CaveCarver& World::cave_carcer() { return m_cave_carcer; } -RiverWorm& World::river_worm() { return m_river_worm; } -std::vector& World::planes() { return m_planes; } -std::vector& World::render_snapshots() { - return m_render_snapshots; -}; - -TickType World::game_tick() const { return m_game_ticks.load(); } -TickType World::day_tick() const { return m_day_tick.load(); } -void World::day_tick(TickType tick) { - tick %= DAY_TIME; - m_day_tick = tick; -} -int World::per_tick_time() const { return m_per_tick_time.load(); } -void World::per_tick_time(int ms) { m_per_tick_time = ms; } - -bool World::is_tick_running() const { return m_tick_running.load(); } -void World::tick_running(bool run) { m_tick_running = run; } -int World::pool_threads() const { return m_pool_threads.load(); } -int World::max_threads() const { return m_max_threads.load(); } -void World::change_pool_threads(int threads) { - m_max_threads = std::thread::hardware_concurrency(); - if (m_max_threads < 1) { - Logger::warn("Can't Get Max Support Threads, Set Max Threads to 4"); - m_max_threads = 4; - } - int used_thread = std::clamp(threads, 1, m_max_threads.load()); - Logger::info("Create New Thread Pool Use {} Threads", used_thread); - m_gen_thread_pool.store(std::make_shared(used_thread)); - m_pool_threads = used_thread; -} -int World::chunk_load_style() const { - return std::to_underlying(m_chunk_load_style.load()); -} -void World::set_chunk_load_style(int id) { - using enum ChunkLoadStyle; - - switch (id) { - case std::to_underlying(RANDOM): - m_chunk_load_style = RANDOM; - return; - case std::to_underlying(CENTER): - m_chunk_load_style = CENTER; - return; - } - Logger::error("Can,t Find Chunk Load Style Id {}, Nothing Will Do", id); -} - -ChunkInfo World::get_chunk_info(const glm::vec3& world_pos) const { - ChunkPos pos = get_chunk_pos(world_pos.x, world_pos.z); - - std::shared_lock lock(m_chunks_mutex); - auto it = m_chunks.find(pos); - if (it == m_chunks.end()) { - return ChunkInfo{}; - } - return it->second.get_info(); -} - -} // namespace Cubed \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 68dd1f2..cf846c4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,14 @@ #include "Cubed/app.hpp" +#ifdef _WIN32 + +extern "C" { +__declspec(dllexport) unsigned long NvOptimusEnablement = 1; +__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; +} + +#endif + int main(int argc, char** argv) { static_assert(sizeof(int) == sizeof(int32_t)); diff --git a/src/proto/auth/auth.proto b/src/proto/auth/auth.proto new file mode 100644 index 0000000..228044c --- /dev/null +++ b/src/proto/auth/auth.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +message LoginReq { + string name = 1; +} + +message LoginRsp { + bool success = 1; + string uuid = 2; +} + +message LogoutReq { + string uuid = 1; +} + +message LogoutRsp { + string uuid = 1; + bool server_stop = 2; +} + diff --git a/src/proto/common/chunk_pos.proto b/src/proto/common/chunk_pos.proto new file mode 100644 index 0000000..fc5f21a --- /dev/null +++ b/src/proto/common/chunk_pos.proto @@ -0,0 +1,6 @@ +syntax = "proto3"; + +message ChunkPosNet { + int32 x = 1; + int32 z = 2; +} \ No newline at end of file diff --git a/src/proto/common/error.proto b/src/proto/common/error.proto new file mode 100644 index 0000000..e8ada60 --- /dev/null +++ b/src/proto/common/error.proto @@ -0,0 +1,6 @@ +syntax = "proto3"; + +message Error { + int32 code = 1; + string mes = 2; +} \ No newline at end of file diff --git a/src/proto/common/player_info.proto b/src/proto/common/player_info.proto new file mode 100644 index 0000000..41fff9d --- /dev/null +++ b/src/proto/common/player_info.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message PlayerInfo { + string name = 1; +} \ No newline at end of file diff --git a/src/proto/common/vector3.proto b/src/proto/common/vector3.proto new file mode 100644 index 0000000..731c050 --- /dev/null +++ b/src/proto/common/vector3.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +message Vec3 { + float x = 1; + float y = 2; + float z = 3; +} \ No newline at end of file diff --git a/src/proto/packet.proto b/src/proto/packet.proto new file mode 100644 index 0000000..3b6ad3a --- /dev/null +++ b/src/proto/packet.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +import "common/error.proto"; +import "common/player_info.proto"; +import "common/vector3.proto"; +import "player/player.proto"; +import "auth/auth.proto"; +import "system/ping.proto"; +import "system/pong.proto"; +import "world/chunk_data.proto"; +import "world/block_change.proto"; +import "world/time.proto"; \ No newline at end of file diff --git a/src/proto/player/player.proto b/src/proto/player/player.proto new file mode 100644 index 0000000..dfbf483 --- /dev/null +++ b/src/proto/player/player.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +import "common/vector3.proto"; + +message PlayerPos { + string uuid = 1; + Vec3 pos = 2; +} + +message PlayerInfoRsp { + string uuid = 1; + string name = 2; + Vec3 pos = 3; +} \ No newline at end of file diff --git a/src/proto/system/ping.proto b/src/proto/system/ping.proto new file mode 100644 index 0000000..2e74c68 --- /dev/null +++ b/src/proto/system/ping.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message Ping { + uint64 timestamp = 1; +} \ No newline at end of file diff --git a/src/proto/system/pong.proto b/src/proto/system/pong.proto new file mode 100644 index 0000000..b40cffa --- /dev/null +++ b/src/proto/system/pong.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message Pong { + uint64 timestamp = 1; +} \ No newline at end of file diff --git a/src/proto/world/block_change.proto b/src/proto/world/block_change.proto new file mode 100644 index 0000000..1a9a0d1 --- /dev/null +++ b/src/proto/world/block_change.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +import "common/vector3.proto"; + +message BlockChangeReq { + string uuid = 1; + Vec3 pos = 2; + uint32 block = 3; +} + +message BlockChangeRsp { + Vec3 pos = 1; + uint32 block = 2; +} \ No newline at end of file diff --git a/src/proto/world/chunk_data.proto b/src/proto/world/chunk_data.proto new file mode 100644 index 0000000..8b29e73 --- /dev/null +++ b/src/proto/world/chunk_data.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; + +import "common/chunk_pos.proto"; + +message ChunkDataReq { + int32 task_id = 1; + string uuid = 2; + ChunkPosNet pos = 3; +} + +message ChunkDataRsp { + int32 task_id = 1; + ChunkPosNet pos = 2; + uint32 chunk_seed = 3; + int32 biome_type = 4; + repeated uint32 chunk_blocks = 5 [packed=true]; + repeated uint32 neighbor_blocks_1 = 6 [packed=true]; + repeated uint32 neighbor_blocks_2 = 7 [packed=true]; + repeated uint32 neighbor_blocks_3 = 8 [packed=true]; + repeated uint32 neighbor_blocks_4 = 9 [packed=true]; +} + +message S2C_ClearAllChunks { + bool clear = 1; +} diff --git a/src/proto/world/time.proto b/src/proto/world/time.proto new file mode 100644 index 0000000..1b381b4 --- /dev/null +++ b/src/proto/world/time.proto @@ -0,0 +1,6 @@ +syntax = "proto3"; + +message UpdateTime { + int64 game_tick = 1; + int64 day_tick = 2; +} \ No newline at end of file diff --git a/src/renderer.cpp b/src/renderer.cpp index a528ec7..4cfaf09 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -4,8 +4,8 @@ #include "Cubed/config.hpp" #include "Cubed/debug_collector.hpp" #include "Cubed/dev_panel.hpp" -#include "Cubed/gameplay/player.hpp" -#include "Cubed/gameplay/world.hpp" +#include "Cubed/gameplay/client_player.hpp" +#include "Cubed/gameplay/client_world.hpp" #include "Cubed/primitive_data.hpp" #include "Cubed/texture_manager.hpp" #include "Cubed/tools/cubed_assert.hpp" @@ -21,31 +21,34 @@ namespace Cubed { -Renderer::Renderer(const Camera& camera, World& world, +Renderer::Renderer(const Camera& camera, ClientWorld& world, const TextureManager& texture_manager, DevPanel& dev_panel) : m_camera(camera), m_dev_panel(dev_panel), m_texture_manager(texture_manager), m_world(world) {} Renderer::~Renderer() { - glBindBuffer(GL_ARRAY_BUFFER, 0); - glDeleteBuffers(1, &m_outline_vbo); - glDeleteBuffers(1, &m_outline_indices_vbo); - glDeleteBuffers(1, &m_sky_vbo); - glDeleteBuffers(1, &m_ui_vbo); - glDeleteBuffers(1, &m_text_vbo); - glBindVertexArray(0); - glDeleteVertexArrays(NUM_VAO, m_vao.data()); - glDeleteFramebuffers(1, &m_fbo); - glDeleteTextures(1, &m_screen_texture); - glDeleteTextures(1, &m_screen_depth_texture); + if (m_init) { + glBindBuffer(GL_ARRAY_BUFFER, 0); + glDeleteBuffers(1, &m_outline_vbo); + glDeleteBuffers(1, &m_outline_indices_vbo); + glDeleteBuffers(1, &m_sky_vbo); + glDeleteBuffers(1, &m_ui_vbo); + glDeleteBuffers(1, &m_text_vbo); + glDeleteBuffers(1, &m_player_vbo); + glBindVertexArray(0); + glDeleteVertexArrays(NUM_VAO, m_vao.data()); + glDeleteFramebuffers(1, &m_fbo); + glDeleteTextures(1, &m_screen_texture); + glDeleteTextures(1, &m_screen_depth_texture); - glDeleteFramebuffers(1, &m_oit_fbo); - glDeleteTextures(1, &m_accum_texture); - glDeleteTextures(1, &m_reveal_texture); - glDeleteTextures(1, &m_oit_depth_texture); + glDeleteFramebuffers(1, &m_oit_fbo); + glDeleteTextures(1, &m_accum_texture); + glDeleteTextures(1, &m_reveal_texture); + glDeleteTextures(1, &m_oit_depth_texture); - glDeleteFramebuffers(1, &m_depth_map_fbo); - glDeleteTextures(1, &m_depth_map_texture); + glDeleteFramebuffers(1, &m_depth_map_fbo); + glDeleteTextures(1, &m_depth_map_texture); + } } void Renderer::hot_reload() { @@ -86,7 +89,9 @@ void Renderer::init() { "shaders/billboard_f_shader.glsl"}; Shader water_shader{"water", "shaders/water_v_shader.glsl", "shaders/water_f_shader.glsl"}; - + Shader player_shader{"player", "shaders/player_v_shader.glsl", + "shaders/player_f_shader.glsl"}; + m_shaders.insert({player_shader.hash(), std::move(player_shader)}); m_shaders.insert({world_shader.hash(), std::move(world_shader)}); m_shaders.insert({outline_shader.hash(), std::move(outline_shader)}); m_shaders.insert({sky_shdaer.hash(), std::move(sky_shdaer)}); @@ -100,6 +105,7 @@ void Renderer::init() { m_shaders.insert({depth_shader.hash(), std::move(depth_shader)}); m_shaders.insert({billboard.hash(), std::move(billboard)}); m_shaders.insert({water_shader.hash(), std::move(water_shader)}); + glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); @@ -109,9 +115,9 @@ void Renderer::init() { #ifdef DEBUG_MODE glEnable(GL_DEBUG_OUTPUT); glDebugMessageCallback( - [](GLenum source, GLenum type, GLuint id, GLenum severity, - GLsizei length, const GLchar* message, const void* user_param) { - Logger::log(Logger::Level::DEBUG, std::source_location::current(), + [](GLenum, GLenum, GLuint, GLenum, GLsizei, const GLchar* message, + const void*) { + Logger::log(Logger::Level::L_DEBUG, std::source_location::current(), "GL Debug: {}", reinterpret_cast(message)); }, nullptr); @@ -165,12 +171,22 @@ void Renderer::init() { glEnableVertexAttribArray(1); glEnableVertexAttribArray(2); + glBindVertexArray(m_vao[5]); + glGenBuffers(1, &m_player_vbo); + glBindBuffer(GL_ARRAY_BUFFER, m_player_vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(VERTICES_PLAYER), VERTICES_PLAYER, + GL_STATIC_DRAW); + + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0); + glEnableVertexAttribArray(0); + init_quad(); init_text(); hot_reload(); glBindVertexArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0); + m_init = true; } const Shader& Renderer::get_shader(const std::string& name) const { @@ -214,7 +230,7 @@ void Renderer::render() { render_sky(); render_world(); render_outline(); - + render_player(); glBindFramebuffer(GL_FRAMEBUFFER, 0); glEnable(GL_FRAMEBUFFER_SRGB); glDisable(GL_DEPTH_TEST); @@ -260,7 +276,7 @@ void Renderer::render_outline() { const auto& shader = get_shader("outline"); shader.use(); - const auto& block_pos = m_world.get_look_block_pos("TestPlayer"); + const auto& block_pos = m_world.get_look_block_pos(); if (block_pos != std::nullopt) { @@ -660,37 +676,42 @@ void Renderer::render_world() { glActiveTexture(GL_TEXTURE1); glEnable(GL_DEPTH_TEST); for (const auto& snapshot : m_render_snapshots) { + if (!snapshot) { + continue; + } glBindTexture(GL_TEXTURE_2D_ARRAY, m_texture_manager.get_texture_array()); - glBindVertexArray(snapshot.normal_vao); + glBindVertexArray(snapshot->normal_vao); - glDrawArrays(GL_TRIANGLES, 0, snapshot.normal_vertices_count); + glDrawArrays(GL_TRIANGLES, 0, snapshot->normal_vertices_count); } // cross_plane and discard for (const auto& snapshot : m_render_snapshots) { - + if (!snapshot) { + continue; + } glm::vec2 camera_pos_xz{camera_pos.x, camera_pos.z}; - if (snapshot.cross_vertices_count != 0) { - glm::vec2 center_xz{snapshot.center.x, snapshot.center.z}; + if (snapshot->cross_vertices_count != 0) { + glm::vec2 center_xz{snapshot->center.x, snapshot->center.z}; float dist2d = glm::distance(camera_pos_xz, center_xz); if (dist2d <= CROSS_PLANE_DISTANCE * 16) { glBindTexture(GL_TEXTURE_2D_ARRAY, m_texture_manager.get_cross_plane_array()); - glBindVertexArray(snapshot.cross_vao); + glBindVertexArray(snapshot->cross_vao); glDrawArrays(GL_TRIANGLES, 0, - snapshot.cross_vertices_count); + snapshot->cross_vertices_count); } } - if (snapshot.normal_discard_vertices_count != 0) { + if (snapshot->normal_discard_vertices_count != 0) { glBindTexture(GL_TEXTURE_2D_ARRAY, m_texture_manager.get_texture_array()); - glBindVertexArray(snapshot.normal_discard_vao); + glBindVertexArray(snapshot->normal_discard_vao); glDrawArrays(GL_TRIANGLES, 0, - snapshot.normal_discard_vertices_count); + snapshot->normal_discard_vertices_count); } } } @@ -746,28 +767,33 @@ void Renderer::render_world() { glBindTexture(GL_TEXTURE_2D_ARRAY, m_texture_manager.get_pbr_texture()); normal_block_shader.set_loc("enablePBR", m_pbr); for (const auto& snapshot : m_render_snapshots) { - - if (Math::is_aabb_in_frustum(snapshot.center, snapshot.half_extents, + if (!snapshot) { + continue; + } + if (Math::is_aabb_in_frustum(snapshot->center, snapshot->half_extents, m_planes)) { - glBindVertexArray(snapshot.normal_vao); + glBindVertexArray(snapshot->normal_vao); - glDrawArrays(GL_TRIANGLES, 0, snapshot.normal_vertices_count); + glDrawArrays(GL_TRIANGLES, 0, snapshot->normal_vertices_count); rendered_sum++; } } // discard for (const auto& snapshot : m_render_snapshots) { - if (!Math::is_aabb_in_frustum(snapshot.center, snapshot.half_extents, + if (!snapshot) { + continue; + } + if (!Math::is_aabb_in_frustum(snapshot->center, snapshot->half_extents, m_planes)) { continue; } - if (snapshot.normal_discard_vertices_count != 0) { - glBindVertexArray(snapshot.normal_discard_vao); + if (snapshot->normal_discard_vertices_count != 0) { + glBindVertexArray(snapshot->normal_discard_vao); glDrawArrays(GL_TRIANGLES, 0, - snapshot.normal_discard_vertices_count); + snapshot->normal_discard_vertices_count); } } // cross_plane @@ -776,18 +802,21 @@ void Renderer::render_world() { m_texture_manager.get_cross_plane_array()); normal_block_shader.set_loc("enablePBR", false); for (const auto& snapshot : m_render_snapshots) { - if (!Math::is_aabb_in_frustum(snapshot.center, snapshot.half_extents, + if (!snapshot) { + continue; + } + if (!Math::is_aabb_in_frustum(snapshot->center, snapshot->half_extents, m_planes)) { continue; } glm::vec2 camera_pos_xz{camera_pos.x, camera_pos.z}; - if (snapshot.cross_vertices_count != 0) { - glm::vec2 center_xz{snapshot.center.x, snapshot.center.z}; + if (snapshot->cross_vertices_count != 0) { + glm::vec2 center_xz{snapshot->center.x, snapshot->center.z}; float dist2d = glm::distance(camera_pos_xz, center_xz); if (dist2d <= CROSS_PLANE_DISTANCE * 16) { - glBindVertexArray(snapshot.cross_vao); + glBindVertexArray(snapshot->cross_vao); - glDrawArrays(GL_TRIANGLES, 0, snapshot.cross_vertices_count); + glDrawArrays(GL_TRIANGLES, 0, snapshot->cross_vertices_count); } } } @@ -837,16 +866,20 @@ void Renderer::render_world() { glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D_ARRAY, m_texture_manager.get_texture_array()); for (const auto& snapshot : m_render_snapshots) { - if (!Math::is_aabb_in_frustum(snapshot.center, snapshot.half_extents, + if (!snapshot) { + continue; + } + if (!Math::is_aabb_in_frustum(snapshot->center, snapshot->half_extents, m_planes)) { continue; } - if (snapshot.normal_blend_vertices_count != 0) { + if (snapshot->normal_blend_vertices_count != 0) { - glBindVertexArray(snapshot.normal_blend_vao); + glBindVertexArray(snapshot->normal_blend_vao); - glDrawArrays(GL_TRIANGLES, 0, snapshot.normal_blend_vertices_count); + glDrawArrays(GL_TRIANGLES, 0, + snapshot->normal_blend_vertices_count); } } @@ -886,16 +919,19 @@ void Renderer::render_world() { glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D_ARRAY, m_texture_manager.get_texture_array()); for (const auto& snapshot : m_render_snapshots) { - if (!Math::is_aabb_in_frustum(snapshot.center, snapshot.half_extents, + if (!snapshot) { + continue; + } + if (!Math::is_aabb_in_frustum(snapshot->center, snapshot->half_extents, m_planes)) { continue; } - if (snapshot.water_vertices_count != 0) { + if (snapshot->water_vertices_count != 0) { - glBindVertexArray(snapshot.water_vao); + glBindVertexArray(snapshot->water_vao); - glDrawArrays(GL_TRIANGLES, 0, snapshot.water_vertices_count); + glDrawArrays(GL_TRIANGLES, 0, snapshot->water_vertices_count); } } @@ -924,6 +960,26 @@ void Renderer::render_world() { DebugCollector::get().report( "rendered_chunk", "Rendered Chunk: " + std::to_string(rendered_sum)); } + +void Renderer::render_player() { + auto& shader = get_shader("player"); + shader.use(); + m_v_mat = m_camera.get_camera_lookat(); + + auto& players = m_world.render_player_data(); + + for (auto& player : players) { + m_m_mat = glm::translate( + glm::mat4(1.0f), player.render_pos + glm::vec3(-0.5f, 0.0f, -0.5f)); + m_mv_mat = m_v_mat * m_m_mat; + shader.set_loc("mv_matrix", m_mv_mat); + shader.set_loc("proj_matrix", m_p_mat); + glBindVertexArray(m_vao[5]); + glEnable(GL_DEPTH_TEST); + glDrawArrays(GL_TRIANGLES, 0, 36); + } +} + #pragma endregion void Renderer::render_dev_panel() { glDisable(GL_DEPTH_TEST); diff --git a/src/texture_manager.cpp b/src/texture_manager.cpp index 24df2db..1b58e3e 100644 --- a/src/texture_manager.cpp +++ b/src/texture_manager.cpp @@ -36,14 +36,16 @@ TextureManager::TextureManager() {} TextureManager::~TextureManager() { delet_texture(); } void TextureManager::delet_texture() { - glDeleteTextures(1, &m_texture_array); - glDeleteTextures(1, &m_block_status_array); - glDeleteTextures(1, &m_cross_plane_array); - glDeleteTextures(1, &m_normal_texture_array); - for (auto& id : m_item_textures) { - glDeleteTextures(1, &id); + if (m_init) { + glDeleteTextures(1, &m_texture_array); + glDeleteTextures(1, &m_block_status_array); + glDeleteTextures(1, &m_cross_plane_array); + glDeleteTextures(1, &m_normal_texture_array); + for (auto& id : m_item_textures) { + glDeleteTextures(1, &id); + } + Logger::info("Successfully delete all texture"); } - Logger::info("Successfully delete all texture"); } GLuint TextureManager::get_block_status_array() const { @@ -313,6 +315,7 @@ void TextureManager::init_texture() { init_block(); init_block_status(); init_ui(); + m_init = true; } void TextureManager::update() { diff --git a/src/tools/math_tools.cpp b/src/tools/math_tools.cpp deleted file mode 100644 index 4c1621b..0000000 --- a/src/tools/math_tools.cpp +++ /dev/null @@ -1,105 +0,0 @@ -#include "Cubed/tools/math_tools.hpp" - -#include -#include -#include - -namespace Cubed { - -namespace Math { - -void extract_frustum_planes(const glm::mat4& mvp_matrix, - std::vector& planes) { - if (planes.size() != 6) { - planes.resize(6); - } - - const float* m = glm::value_ptr(mvp_matrix); - - // left plane - planes[0] = - glm::vec4(m[3] + m[0], m[7] + m[4], m[11] + m[8], m[15] + m[12]); - // right plane - planes[1] = - glm::vec4(m[3] - m[0], m[7] - m[4], m[11] - m[8], m[15] - m[12]); - // bottom plane - planes[2] = - glm::vec4(m[3] + m[1], m[7] + m[5], m[11] + m[9], m[15] + m[13]); - // top plane - planes[3] = - glm::vec4(m[3] - m[1], m[7] - m[5], m[11] - m[9], m[15] - m[13]); - // near plane - planes[4] = - glm::vec4(m[3] + m[2], m[7] + m[6], m[11] + m[10], m[15] + m[14]); - // far plane - planes[5] = - glm::vec4(m[3] - m[2], m[7] - m[6], m[11] - m[10], m[15] - m[14]); - - for (auto& p : planes) { - p = glm::normalize(p); - } -} - -float smootherstep(float edge0, float edge1, float x) { - - x = std::clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f); - - return x * x * x * (x * (6.0f * x - 15.0f) + 10.0f); -} - -bool is_aabb_in_frustum(const glm::vec3& center, const glm::vec3& half_extents, - const std::vector& planes) { - for (const auto& plane : planes) { - // distance - float d = glm::dot(glm::vec3(plane), center) + plane.w; - float r = half_extents.x * std::abs(plane.x) + - half_extents.y * std::abs(plane.y) + - half_extents.z * std::abs(plane.z); - if (d + r < 0) { - return false; - } - } - return true; -} -float deterministic_random(int x, int z, uint64_t seed) { - uint64_t h = seed; - h = h * 6364136223846793005ULL + (uint64_t)x; - h = h * 6364136223846793005ULL + (uint64_t)z; - return (float)(h >> 40) / (float)(1 << 24); -} - -glm::vec3 slerp(const glm::vec3& from, const glm::vec3& to, float t) { - - float cos_theta = glm::clamp(glm::dot(from, to), -1.0f, 1.0f); - - if (cos_theta > 0.9995f) { - return glm::normalize(glm::mix(from, to, t)); - } - - if (cos_theta < -0.9995f) { - - glm::vec3 axis = (std::fabs(from.x) < 0.9f) - ? glm::vec3(1.0f, 0.0f, 0.0f) - : glm::vec3(0.0f, 1.0f, 0.0f); - glm::vec3 ortho = glm::normalize(glm::cross(from, axis)); - - float angle = glm::pi() * t; - - glm::vec3 rotated = - from * std::cos(angle) + glm::cross(ortho, from) * std::sin(angle); - - return glm::normalize(rotated); - } - - float theta = std::acos(cos_theta); - float sin_theta = std::sin(theta); - - float a = std::sin((1.0f - t) * theta) / sin_theta; - float b = std::sin(t * theta) / sin_theta; - - return glm::normalize(a * from + b * to); -} - -} // namespace Math - -} // namespace Cubed \ No newline at end of file diff --git a/src/version.hpp.in b/src/version.hpp.in new file mode 100644 index 0000000..e16e678 --- /dev/null +++ b/src/version.hpp.in @@ -0,0 +1,3 @@ +#pragma once + +#define CUBED_VERSION "@CUBED_VERSION@" \ No newline at end of file diff --git a/src/window.cpp b/src/window.cpp index a30069a..fcaa24f 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -18,10 +18,14 @@ static int windowed_width = 800, windowed_height = 600; Window::Window(Renderer& renderer) : m_renderer(renderer) {} Window::~Window() { + if (m_imgui_init) { + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + } - ImGui_ImplOpenGL3_Shutdown(); - ImGui_ImplGlfw_Shutdown(); - ImGui::DestroyContext(); + if (ImGui::GetCurrentContext() != nullptr) { + ImGui::DestroyContext(); + } if (m_window) { glfwDestroyWindow(m_window); @@ -212,6 +216,8 @@ void Window::imgui_init() { // Setup Platform/Renderer backends ImGui_ImplGlfw_InitForOpenGL(m_window, false); ImGui_ImplOpenGL3_Init(); + + m_imgui_init = true; } } // namespace Cubed \ No newline at end of file diff --git a/third_party/asio/include/.gitignore b/third_party/asio/include/.gitignore new file mode 100644 index 0000000..282522d --- /dev/null +++ b/third_party/asio/include/.gitignore @@ -0,0 +1,2 @@ +Makefile +Makefile.in diff --git a/third_party/asio/include/Makefile.am b/third_party/asio/include/Makefile.am new file mode 100644 index 0000000..710231e --- /dev/null +++ b/third_party/asio/include/Makefile.am @@ -0,0 +1,640 @@ +# find . -name "*.*pp" | sed -e 's/^\.\///' | sed -e 's/^.*$/ & \\/' | sort +nobase_include_HEADERS = \ + asio/any_completion_executor.hpp \ + asio/any_completion_handler.hpp \ + asio/any_io_executor.hpp \ + asio/append.hpp \ + asio/as_tuple.hpp \ + asio/associated_allocator.hpp \ + asio/associated_cancellation_slot.hpp \ + asio/associated_executor.hpp \ + asio/associated_immediate_executor.hpp \ + asio/associator.hpp \ + asio/async_result.hpp \ + asio/awaitable.hpp \ + asio/basic_datagram_socket.hpp \ + asio/basic_deadline_timer.hpp \ + asio/basic_file.hpp \ + asio/basic_io_object.hpp \ + asio/basic_random_access_file.hpp \ + asio/basic_raw_socket.hpp \ + asio/basic_readable_pipe.hpp \ + asio/basic_seq_packet_socket.hpp \ + asio/basic_serial_port.hpp \ + asio/basic_signal_set.hpp \ + asio/basic_socket_acceptor.hpp \ + asio/basic_socket.hpp \ + asio/basic_socket_iostream.hpp \ + asio/basic_socket_streambuf.hpp \ + asio/basic_streambuf_fwd.hpp \ + asio/basic_streambuf.hpp \ + asio/basic_stream_file.hpp \ + asio/basic_stream_socket.hpp \ + asio/basic_waitable_timer.hpp \ + asio/basic_writable_pipe.hpp \ + asio/bind_allocator.hpp \ + asio/bind_cancellation_slot.hpp \ + asio/bind_executor.hpp \ + asio/bind_immediate_executor.hpp \ + asio/buffered_read_stream_fwd.hpp \ + asio/buffered_read_stream.hpp \ + asio/buffered_stream_fwd.hpp \ + asio/buffered_stream.hpp \ + asio/buffered_write_stream_fwd.hpp \ + asio/buffered_write_stream.hpp \ + asio/buffer.hpp \ + asio/buffer_registration.hpp \ + asio/buffers_iterator.hpp \ + asio/cancel_after.hpp \ + asio/cancel_at.hpp \ + asio/cancellation_signal.hpp \ + asio/cancellation_state.hpp \ + asio/cancellation_type.hpp \ + asio/co_composed.hpp \ + asio/co_spawn.hpp \ + asio/completion_condition.hpp \ + asio/compose.hpp \ + asio/composed.hpp \ + asio/config.hpp \ + asio/connect.hpp \ + asio/connect_pipe.hpp \ + asio/consign.hpp \ + asio/coroutine.hpp \ + asio/deadline_timer.hpp \ + asio/defer.hpp \ + asio/deferred.hpp \ + asio/default_completion_token.hpp \ + asio/detached.hpp \ + asio/detail/array_fwd.hpp \ + asio/detail/array.hpp \ + asio/detail/assert.hpp \ + asio/detail/atomic_count.hpp \ + asio/detail/atomic_slim_mutex.hpp \ + asio/detail/base_from_cancellation_state.hpp \ + asio/detail/base_from_completion_cond.hpp \ + asio/detail/bind_handler.hpp \ + asio/detail/blocking_executor_op.hpp \ + asio/detail/buffered_stream_storage.hpp \ + asio/detail/buffer_resize_guard.hpp \ + asio/detail/buffer_sequence_adapter.hpp \ + asio/detail/call_stack.hpp \ + asio/detail/chrono.hpp \ + asio/detail/chrono_time_traits.hpp \ + asio/detail/completion_handler.hpp \ + asio/detail/completion_message.hpp \ + asio/detail/completion_payload.hpp \ + asio/detail/completion_payload_handler.hpp \ + asio/detail/composed_work.hpp \ + asio/detail/concurrency_hint.hpp \ + asio/detail/conditionally_enabled_event.hpp \ + asio/detail/conditionally_enabled_mutex.hpp \ + asio/detail/config.hpp \ + asio/detail/consuming_buffers.hpp \ + asio/detail/cstddef.hpp \ + asio/detail/cstdint.hpp \ + asio/detail/date_time_fwd.hpp \ + asio/detail/deadline_timer_service.hpp \ + asio/detail/dependent_type.hpp \ + asio/detail/descriptor_ops.hpp \ + asio/detail/descriptor_read_op.hpp \ + asio/detail/descriptor_write_op.hpp \ + asio/detail/dev_poll_reactor.hpp \ + asio/detail/epoll_reactor.hpp \ + asio/detail/eventfd_select_interrupter.hpp \ + asio/detail/event.hpp \ + asio/detail/exception.hpp \ + asio/detail/executor_function.hpp \ + asio/detail/executor_op.hpp \ + asio/detail/fd_set_adapter.hpp \ + asio/detail/fenced_block.hpp \ + asio/detail/functional.hpp \ + asio/detail/future.hpp \ + asio/detail/global.hpp \ + asio/detail/handler_alloc_helpers.hpp \ + asio/detail/handler_cont_helpers.hpp \ + asio/detail/handler_tracking.hpp \ + asio/detail/handler_type_requirements.hpp \ + asio/detail/handler_work.hpp \ + asio/detail/hash_map.hpp \ + asio/detail/impl/buffer_sequence_adapter.ipp \ + asio/detail/impl/descriptor_ops.ipp \ + asio/detail/impl/dev_poll_reactor.hpp \ + asio/detail/impl/dev_poll_reactor.ipp \ + asio/detail/impl/epoll_reactor.hpp \ + asio/detail/impl/epoll_reactor.ipp \ + asio/detail/impl/eventfd_select_interrupter.ipp \ + asio/detail/impl/handler_tracking.ipp \ + asio/detail/impl/io_uring_descriptor_service.ipp \ + asio/detail/impl/io_uring_file_service.ipp \ + asio/detail/impl/io_uring_service.hpp \ + asio/detail/impl/io_uring_service.ipp \ + asio/detail/impl/io_uring_socket_service_base.ipp \ + asio/detail/impl/kqueue_reactor.hpp \ + asio/detail/impl/kqueue_reactor.ipp \ + asio/detail/impl/null_event.ipp \ + asio/detail/impl/pipe_select_interrupter.ipp \ + asio/detail/impl/posix_event.ipp \ + asio/detail/impl/posix_mutex.ipp \ + asio/detail/impl/posix_serial_port_service.ipp \ + asio/detail/impl/posix_thread.ipp \ + asio/detail/impl/posix_tss_ptr.ipp \ + asio/detail/impl/reactive_descriptor_service.ipp \ + asio/detail/impl/reactive_socket_service_base.ipp \ + asio/detail/impl/resolver_service_base.ipp \ + asio/detail/impl/resolver_thread_pool.ipp \ + asio/detail/impl/scheduler.ipp \ + asio/detail/impl/select_reactor.hpp \ + asio/detail/impl/select_reactor.ipp \ + asio/detail/impl/service_registry.hpp \ + asio/detail/impl/service_registry.ipp \ + asio/detail/impl/signal_set_service.ipp \ + asio/detail/impl/socket_ops.ipp \ + asio/detail/impl/socket_select_interrupter.ipp \ + asio/detail/impl/strand_executor_service.hpp \ + asio/detail/impl/strand_executor_service.ipp \ + asio/detail/impl/strand_service.hpp \ + asio/detail/impl/strand_service.ipp \ + asio/detail/impl/thread_context.ipp \ + asio/detail/impl/throw_error.ipp \ + asio/detail/impl/timer_queue_set.ipp \ + asio/detail/impl/win_critsec_mutex.ipp \ + asio/detail/impl/win_event.ipp \ + asio/detail/impl/win_iocp_file_service.ipp \ + asio/detail/impl/win_iocp_handle_service.ipp \ + asio/detail/impl/win_iocp_io_context.hpp \ + asio/detail/impl/win_iocp_io_context.ipp \ + asio/detail/impl/win_iocp_serial_port_service.ipp \ + asio/detail/impl/win_iocp_socket_service_base.ipp \ + asio/detail/impl/win_object_handle_service.ipp \ + asio/detail/impl/winrt_ssocket_service_base.ipp \ + asio/detail/impl/winrt_timer_scheduler.hpp \ + asio/detail/impl/winrt_timer_scheduler.ipp \ + asio/detail/impl/winsock_init.ipp \ + asio/detail/impl/win_static_mutex.ipp \ + asio/detail/impl/win_thread.ipp \ + asio/detail/impl/win_tss_ptr.ipp \ + asio/detail/initiate_defer.hpp \ + asio/detail/initiate_dispatch.hpp \ + asio/detail/initiate_post.hpp \ + asio/detail/initiation_base.hpp \ + asio/detail/io_control.hpp \ + asio/detail/io_object_impl.hpp \ + asio/detail/io_uring_descriptor_read_at_op.hpp \ + asio/detail/io_uring_descriptor_read_op.hpp \ + asio/detail/io_uring_descriptor_service.hpp \ + asio/detail/io_uring_descriptor_write_at_op.hpp \ + asio/detail/io_uring_descriptor_write_op.hpp \ + asio/detail/io_uring_file_service.hpp \ + asio/detail/io_uring_null_buffers_op.hpp \ + asio/detail/io_uring_operation.hpp \ + asio/detail/io_uring_service.hpp \ + asio/detail/io_uring_socket_accept_op.hpp \ + asio/detail/io_uring_socket_connect_op.hpp \ + asio/detail/io_uring_socket_recvfrom_op.hpp \ + asio/detail/io_uring_socket_recvmsg_op.hpp \ + asio/detail/io_uring_socket_recv_op.hpp \ + asio/detail/io_uring_socket_send_op.hpp \ + asio/detail/io_uring_socket_sendto_op.hpp \ + asio/detail/io_uring_socket_service_base.hpp \ + asio/detail/io_uring_socket_service.hpp \ + asio/detail/io_uring_wait_op.hpp \ + asio/detail/is_buffer_sequence.hpp \ + asio/detail/is_executor.hpp \ + asio/detail/keyword_tss_ptr.hpp \ + asio/detail/kqueue_reactor.hpp \ + asio/detail/limits.hpp \ + asio/detail/local_free_on_block_exit.hpp \ + asio/detail/memory.hpp \ + asio/detail/mutex.hpp \ + asio/detail/non_const_lvalue.hpp \ + asio/detail/noncopyable.hpp \ + asio/detail/null_event.hpp \ + asio/detail/null_fenced_block.hpp \ + asio/detail/null_global.hpp \ + asio/detail/null_mutex.hpp \ + asio/detail/null_reactor.hpp \ + asio/detail/null_signal_blocker.hpp \ + asio/detail/null_socket_service.hpp \ + asio/detail/null_static_mutex.hpp \ + asio/detail/null_thread.hpp \ + asio/detail/null_tss_ptr.hpp \ + asio/detail/object_pool.hpp \ + asio/detail/old_win_sdk_compat.hpp \ + asio/detail/operation.hpp \ + asio/detail/op_queue.hpp \ + asio/detail/pipe_select_interrupter.hpp \ + asio/detail/pop_options.hpp \ + asio/detail/posix_event.hpp \ + asio/detail/posix_fd_set_adapter.hpp \ + asio/detail/posix_global.hpp \ + asio/detail/posix_mutex.hpp \ + asio/detail/posix_serial_port_service.hpp \ + asio/detail/posix_signal_blocker.hpp \ + asio/detail/posix_static_mutex.hpp \ + asio/detail/posix_thread.hpp \ + asio/detail/posix_tss_ptr.hpp \ + asio/detail/push_options.hpp \ + asio/detail/reactive_descriptor_service.hpp \ + asio/detail/reactive_null_buffers_op.hpp \ + asio/detail/reactive_socket_accept_op.hpp \ + asio/detail/reactive_socket_connect_op.hpp \ + asio/detail/reactive_socket_recvfrom_op.hpp \ + asio/detail/reactive_socket_recvmsg_op.hpp \ + asio/detail/reactive_socket_recv_op.hpp \ + asio/detail/reactive_socket_send_op.hpp \ + asio/detail/reactive_socket_sendto_op.hpp \ + asio/detail/reactive_socket_service_base.hpp \ + asio/detail/reactive_socket_service.hpp \ + asio/detail/reactive_wait_op.hpp \ + asio/detail/reactor.hpp \ + asio/detail/reactor_op.hpp \ + asio/detail/reactor_op_queue.hpp \ + asio/detail/recycling_allocator.hpp \ + asio/detail/regex_fwd.hpp \ + asio/detail/resolve_endpoint_op.hpp \ + asio/detail/resolve_op.hpp \ + asio/detail/resolve_query_op.hpp \ + asio/detail/resolver_service_base.hpp \ + asio/detail/resolver_thread_pool.hpp \ + asio/detail/resolver_service.hpp \ + asio/detail/scheduler.hpp \ + asio/detail/scheduler_operation.hpp \ + asio/detail/scheduler_task.hpp \ + asio/detail/scheduler_thread_info.hpp \ + asio/detail/scoped_lock.hpp \ + asio/detail/scoped_ptr.hpp \ + asio/detail/select_interrupter.hpp \ + asio/detail/select_reactor.hpp \ + asio/detail/service_registry.hpp \ + asio/detail/signal_blocker.hpp \ + asio/detail/signal_handler.hpp \ + asio/detail/signal_init.hpp \ + asio/detail/signal_op.hpp \ + asio/detail/signal_set_service.hpp \ + asio/detail/slim_mutex.hpp \ + asio/detail/socket_holder.hpp \ + asio/detail/socket_ops.hpp \ + asio/detail/socket_option.hpp \ + asio/detail/socket_select_interrupter.hpp \ + asio/detail/socket_types.hpp \ + asio/detail/source_location.hpp \ + asio/detail/static_mutex.hpp \ + asio/detail/std_event.hpp \ + asio/detail/std_fenced_block.hpp \ + asio/detail/std_global.hpp \ + asio/detail/std_mutex.hpp \ + asio/detail/std_static_mutex.hpp \ + asio/detail/std_thread.hpp \ + asio/detail/strand_executor_service.hpp \ + asio/detail/strand_service.hpp \ + asio/detail/string_view.hpp \ + asio/detail/thread_context.hpp \ + asio/detail/thread_group.hpp \ + asio/detail/thread.hpp \ + asio/detail/thread_info_base.hpp \ + asio/detail/throw_error.hpp \ + asio/detail/throw_exception.hpp \ + asio/detail/timed_cancel_op.hpp \ + asio/detail/timer_queue_base.hpp \ + asio/detail/timer_queue.hpp \ + asio/detail/timer_queue_set.hpp \ + asio/detail/timer_scheduler_fwd.hpp \ + asio/detail/timer_scheduler.hpp \ + asio/detail/tss_ptr.hpp \ + asio/detail/type_traits.hpp \ + asio/detail/utility.hpp \ + asio/detail/wait_handler.hpp \ + asio/detail/wait_op.hpp \ + asio/detail/winapp_thread.hpp \ + asio/detail/wince_thread.hpp \ + asio/detail/win_critsec_mutex.hpp \ + asio/detail/win_event.hpp \ + asio/detail/win_fd_set_adapter.hpp \ + asio/detail/win_global.hpp \ + asio/detail/win_iocp_file_service.hpp \ + asio/detail/win_iocp_handle_read_op.hpp \ + asio/detail/win_iocp_handle_service.hpp \ + asio/detail/win_iocp_handle_write_op.hpp \ + asio/detail/win_iocp_io_context.hpp \ + asio/detail/win_iocp_null_buffers_op.hpp \ + asio/detail/win_iocp_operation.hpp \ + asio/detail/win_iocp_overlapped_op.hpp \ + asio/detail/win_iocp_overlapped_ptr.hpp \ + asio/detail/win_iocp_serial_port_service.hpp \ + asio/detail/win_iocp_socket_accept_op.hpp \ + asio/detail/win_iocp_socket_connect_op.hpp \ + asio/detail/win_iocp_socket_recvfrom_op.hpp \ + asio/detail/win_iocp_socket_recvmsg_op.hpp \ + asio/detail/win_iocp_socket_recv_op.hpp \ + asio/detail/win_iocp_socket_send_op.hpp \ + asio/detail/win_iocp_socket_service_base.hpp \ + asio/detail/win_iocp_socket_service.hpp \ + asio/detail/win_iocp_thread_info.hpp \ + asio/detail/win_iocp_wait_op.hpp \ + asio/detail/win_mutex.hpp \ + asio/detail/win_object_handle_service.hpp \ + asio/detail/winrt_async_manager.hpp \ + asio/detail/winrt_async_op.hpp \ + asio/detail/winrt_resolve_op.hpp \ + asio/detail/winrt_resolver_service.hpp \ + asio/detail/winrt_socket_connect_op.hpp \ + asio/detail/winrt_socket_recv_op.hpp \ + asio/detail/winrt_socket_send_op.hpp \ + asio/detail/winrt_ssocket_service_base.hpp \ + asio/detail/winrt_ssocket_service.hpp \ + asio/detail/winrt_timer_scheduler.hpp \ + asio/detail/winrt_utils.hpp \ + asio/detail/winsock_init.hpp \ + asio/detail/win_static_mutex.hpp \ + asio/detail/win_thread.hpp \ + asio/detail/win_tss_ptr.hpp \ + asio/detail/work_dispatcher.hpp \ + asio/detail/wrapped_handler.hpp \ + asio/dispatch.hpp \ + asio/disposition.hpp \ + asio/error_code.hpp \ + asio/error.hpp \ + asio/execution.hpp \ + asio/execution_context.hpp \ + asio/execution/allocator.hpp \ + asio/execution/any_executor.hpp \ + asio/execution/bad_executor.hpp \ + asio/execution/blocking.hpp \ + asio/execution/blocking_adaptation.hpp \ + asio/execution/context.hpp \ + asio/execution/context_as.hpp \ + asio/execution/executor.hpp \ + asio/execution/impl/bad_executor.ipp \ + asio/execution/inline_exception_handling.hpp \ + asio/execution/invocable_archetype.hpp \ + asio/execution/mapping.hpp \ + asio/execution/occupancy.hpp \ + asio/execution/outstanding_work.hpp \ + asio/execution/prefer_only.hpp \ + asio/execution/relationship.hpp \ + asio/executor.hpp \ + asio/executor_work_guard.hpp \ + asio/experimental/as_single.hpp \ + asio/experimental/awaitable_operators.hpp \ + asio/experimental/basic_channel.hpp \ + asio/experimental/basic_concurrent_channel.hpp \ + asio/experimental/cancellation_condition.hpp \ + asio/experimental/channel.hpp \ + asio/experimental/channel_error.hpp \ + asio/experimental/channel_traits.hpp \ + asio/experimental/co_composed.hpp \ + asio/experimental/co_spawn.hpp \ + asio/experimental/concurrent_channel.hpp \ + asio/experimental/coro.hpp \ + asio/experimental/coro_traits.hpp \ + asio/experimental/detail/channel_operation.hpp \ + asio/experimental/detail/channel_receive_op.hpp \ + asio/experimental/detail/channel_send_functions.hpp \ + asio/experimental/detail/channel_send_op.hpp \ + asio/experimental/detail/channel_service.hpp \ + asio/experimental/detail/coro_completion_handler.hpp \ + asio/experimental/detail/coro_promise_allocator.hpp \ + asio/experimental/detail/has_signature.hpp \ + asio/experimental/detail/impl/channel_service.hpp \ + asio/experimental/detail/partial_promise.hpp \ + asio/experimental/impl/as_single.hpp \ + asio/experimental/impl/channel_error.ipp \ + asio/experimental/impl/coro.hpp \ + asio/experimental/impl/parallel_group.hpp \ + asio/experimental/impl/promise.hpp \ + asio/experimental/impl/use_coro.hpp \ + asio/experimental/impl/use_promise.hpp \ + asio/experimental/parallel_group.hpp \ + asio/experimental/promise.hpp \ + asio/experimental/use_coro.hpp \ + asio/experimental/use_promise.hpp \ + asio/file_base.hpp \ + asio/generic/basic_endpoint.hpp \ + asio/generic/datagram_protocol.hpp \ + asio/generic/detail/endpoint.hpp \ + asio/generic/detail/impl/endpoint.ipp \ + asio/generic/raw_protocol.hpp \ + asio/generic/seq_packet_protocol.hpp \ + asio/generic/stream_protocol.hpp \ + asio/handler_continuation_hook.hpp \ + asio/high_resolution_timer.hpp \ + asio.hpp \ + asio/immediate.hpp \ + asio/impl/any_completion_executor.ipp \ + asio/impl/any_io_executor.ipp \ + asio/impl/append.hpp \ + asio/impl/as_tuple.hpp \ + asio/impl/awaitable.hpp \ + asio/impl/awaitable.ipp \ + asio/impl/buffered_read_stream.hpp \ + asio/impl/buffered_write_stream.hpp \ + asio/impl/cancel_after.hpp \ + asio/impl/cancel_at.hpp \ + asio/impl/cancellation_signal.ipp \ + asio/impl/co_spawn.hpp \ + asio/impl/config.hpp \ + asio/impl/config.ipp \ + asio/impl/connect.hpp \ + asio/impl/connect_pipe.hpp \ + asio/impl/connect_pipe.ipp \ + asio/impl/consign.hpp \ + asio/impl/deferred.hpp \ + asio/impl/detached.hpp \ + asio/impl/error_code.ipp \ + asio/impl/error.ipp \ + asio/impl/execution_context.hpp \ + asio/impl/execution_context.ipp \ + asio/impl/executor.hpp \ + asio/impl/executor.ipp \ + asio/impl/io_context.hpp \ + asio/impl/io_context.ipp \ + asio/impl/multiple_exceptions.ipp \ + asio/impl/prepend.hpp \ + asio/impl/read_at.hpp \ + asio/impl/read.hpp \ + asio/impl/read_until.hpp \ + asio/impl/redirect_disposition.hpp \ + asio/impl/redirect_error.hpp \ + asio/impl/serial_port_base.hpp \ + asio/impl/serial_port_base.ipp \ + asio/impl/spawn.hpp \ + asio/impl/src.hpp \ + asio/impl/system_context.hpp \ + asio/impl/system_context.ipp \ + asio/impl/system_executor.hpp \ + asio/impl/thread_pool.hpp \ + asio/impl/thread_pool.ipp \ + asio/impl/use_awaitable.hpp \ + asio/impl/use_future.hpp \ + asio/impl/write_at.hpp \ + asio/impl/write.hpp \ + asio/inline_executor.hpp \ + asio/inline_or_executor.hpp \ + asio/io_context.hpp \ + asio/io_context_strand.hpp \ + asio/ip/address.hpp \ + asio/ip/address_v4.hpp \ + asio/ip/address_v4_iterator.hpp \ + asio/ip/address_v4_range.hpp \ + asio/ip/address_v6.hpp \ + asio/ip/address_v6_iterator.hpp \ + asio/ip/address_v6_range.hpp \ + asio/ip/bad_address_cast.hpp \ + asio/ip/basic_endpoint.hpp \ + asio/ip/basic_resolver_entry.hpp \ + asio/ip/basic_resolver.hpp \ + asio/ip/basic_resolver_iterator.hpp \ + asio/ip/basic_resolver_query.hpp \ + asio/ip/basic_resolver_results.hpp \ + asio/ip/detail/endpoint.hpp \ + asio/ip/detail/impl/endpoint.ipp \ + asio/ip/detail/socket_option.hpp \ + asio/ip/host_name.hpp \ + asio/ip/icmp.hpp \ + asio/ip/impl/address.hpp \ + asio/ip/impl/address.ipp \ + asio/ip/impl/address_v4.hpp \ + asio/ip/impl/address_v4.ipp \ + asio/ip/impl/address_v6.hpp \ + asio/ip/impl/address_v6.ipp \ + asio/ip/impl/basic_endpoint.hpp \ + asio/ip/impl/host_name.ipp \ + asio/ip/impl/network_v4.hpp \ + asio/ip/impl/network_v4.ipp \ + asio/ip/impl/network_v6.hpp \ + asio/ip/impl/network_v6.ipp \ + asio/ip/multicast.hpp \ + asio/ip/network_v4.hpp \ + asio/ip/network_v6.hpp \ + asio/ip/resolver_base.hpp \ + asio/ip/resolver_query_base.hpp \ + asio/ip/tcp.hpp \ + asio/ip/udp.hpp \ + asio/ip/unicast.hpp \ + asio/ip/v6_only.hpp \ + asio/is_applicable_property.hpp \ + asio/is_contiguous_iterator.hpp \ + asio/is_executor.hpp \ + asio/is_read_buffered.hpp \ + asio/is_write_buffered.hpp \ + asio/local/basic_endpoint.hpp \ + asio/local/connect_pair.hpp \ + asio/local/datagram_protocol.hpp \ + asio/local/detail/endpoint.hpp \ + asio/local/detail/impl/endpoint.ipp \ + asio/local/seq_packet_protocol.hpp \ + asio/local/stream_protocol.hpp \ + asio/multiple_exceptions.hpp \ + asio/packaged_task.hpp \ + asio/placeholders.hpp \ + asio/posix/basic_descriptor.hpp \ + asio/posix/basic_stream_descriptor.hpp \ + asio/posix/descriptor_base.hpp \ + asio/posix/descriptor.hpp \ + asio/posix/stream_descriptor.hpp \ + asio/post.hpp \ + asio/prefer.hpp \ + asio/prepend.hpp \ + asio/query.hpp \ + asio/random_access_file.hpp \ + asio/read_at.hpp \ + asio/read.hpp \ + asio/read_until.hpp \ + asio/readable_pipe.hpp \ + asio/recycling_allocator.hpp \ + asio/redirect_disposition.hpp \ + asio/redirect_error.hpp \ + asio/registered_buffer.hpp \ + asio/require.hpp \ + asio/require_concept.hpp \ + asio/serial_port_base.hpp \ + asio/serial_port.hpp \ + asio/signal_set_base.hpp \ + asio/signal_set.hpp \ + asio/socket_base.hpp \ + asio/spawn.hpp \ + asio/ssl/context_base.hpp \ + asio/ssl/context.hpp \ + asio/ssl/detail/buffered_handshake_op.hpp \ + asio/ssl/detail/engine.hpp \ + asio/ssl/detail/handshake_op.hpp \ + asio/ssl/detail/impl/engine.ipp \ + asio/ssl/detail/impl/openssl_init.ipp \ + asio/ssl/detail/io.hpp \ + asio/ssl/detail/openssl_init.hpp \ + asio/ssl/detail/openssl_types.hpp \ + asio/ssl/detail/password_callback.hpp \ + asio/ssl/detail/read_op.hpp \ + asio/ssl/detail/shutdown_op.hpp \ + asio/ssl/detail/stream_core.hpp \ + asio/ssl/detail/verify_callback.hpp \ + asio/ssl/detail/write_op.hpp \ + asio/ssl/error.hpp \ + asio/ssl.hpp \ + asio/ssl/host_name_verification.hpp \ + asio/ssl/impl/context.hpp \ + asio/ssl/impl/context.ipp \ + asio/ssl/impl/error.ipp \ + asio/ssl/impl/host_name_verification.ipp \ + asio/ssl/impl/src.hpp \ + asio/ssl/stream_base.hpp \ + asio/ssl/stream.hpp \ + asio/ssl/verify_context.hpp \ + asio/ssl/verify_mode.hpp \ + asio/static_thread_pool.hpp \ + asio/steady_timer.hpp \ + asio/strand.hpp \ + asio/streambuf.hpp \ + asio/stream_file.hpp \ + asio/system_context.hpp \ + asio/system_error.hpp \ + asio/system_executor.hpp \ + asio/system_timer.hpp \ + asio/this_coro.hpp \ + asio/thread.hpp \ + asio/thread_pool.hpp \ + asio/time_traits.hpp \ + asio/traits/equality_comparable.hpp \ + asio/traits/execute_member.hpp \ + asio/traits/prefer_free.hpp \ + asio/traits/prefer_member.hpp \ + asio/traits/query_free.hpp \ + asio/traits/query_member.hpp \ + asio/traits/query_static_constexpr_member.hpp \ + asio/traits/require_concept_free.hpp \ + asio/traits/require_concept_member.hpp \ + asio/traits/require_free.hpp \ + asio/traits/require_member.hpp \ + asio/traits/static_query.hpp \ + asio/traits/static_require.hpp \ + asio/traits/static_require_concept.hpp \ + asio/ts/buffer.hpp \ + asio/ts/executor.hpp \ + asio/ts/internet.hpp \ + asio/ts/io_context.hpp \ + asio/ts/netfwd.hpp \ + asio/ts/net.hpp \ + asio/ts/socket.hpp \ + asio/ts/timer.hpp \ + asio/unyield.hpp \ + asio/use_awaitable.hpp \ + asio/use_future.hpp \ + asio/uses_executor.hpp \ + asio/version.hpp \ + asio/wait_traits.hpp \ + asio/windows/basic_object_handle.hpp \ + asio/windows/basic_overlapped_handle.hpp \ + asio/windows/basic_random_access_handle.hpp \ + asio/windows/basic_stream_handle.hpp \ + asio/windows/object_handle.hpp \ + asio/windows/overlapped_handle.hpp \ + asio/windows/overlapped_ptr.hpp \ + asio/windows/random_access_handle.hpp \ + asio/windows/stream_handle.hpp \ + asio/writable_pipe.hpp \ + asio/write_at.hpp \ + asio/write.hpp \ + asio/yield.hpp + +MAINTAINERCLEANFILES = \ + $(srcdir)/Makefile.in diff --git a/third_party/asio/include/asio.hpp b/third_party/asio/include/asio.hpp new file mode 100644 index 0000000..5f0b63e --- /dev/null +++ b/third_party/asio/include/asio.hpp @@ -0,0 +1,205 @@ +// +// asio.hpp +// ~~~~~~~~ +// +// Copyright (c) 2003-2026 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_HPP +#define ASIO_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/any_completion_executor.hpp" +#include "asio/any_completion_handler.hpp" +#include "asio/any_io_executor.hpp" +#include "asio/append.hpp" +#include "asio/as_tuple.hpp" +#include "asio/associated_allocator.hpp" +#include "asio/associated_cancellation_slot.hpp" +#include "asio/associated_executor.hpp" +#include "asio/associated_immediate_executor.hpp" +#include "asio/associator.hpp" +#include "asio/async_result.hpp" +#include "asio/awaitable.hpp" +#include "asio/basic_datagram_socket.hpp" +#include "asio/basic_file.hpp" +#include "asio/basic_io_object.hpp" +#include "asio/basic_random_access_file.hpp" +#include "asio/basic_raw_socket.hpp" +#include "asio/basic_readable_pipe.hpp" +#include "asio/basic_seq_packet_socket.hpp" +#include "asio/basic_serial_port.hpp" +#include "asio/basic_signal_set.hpp" +#include "asio/basic_socket.hpp" +#include "asio/basic_socket_acceptor.hpp" +#include "asio/basic_socket_iostream.hpp" +#include "asio/basic_socket_streambuf.hpp" +#include "asio/basic_stream_file.hpp" +#include "asio/basic_stream_socket.hpp" +#include "asio/basic_streambuf.hpp" +#include "asio/basic_waitable_timer.hpp" +#include "asio/basic_writable_pipe.hpp" +#include "asio/bind_allocator.hpp" +#include "asio/bind_cancellation_slot.hpp" +#include "asio/bind_executor.hpp" +#include "asio/bind_immediate_executor.hpp" +#include "asio/buffer.hpp" +#include "asio/buffer_registration.hpp" +#include "asio/buffered_read_stream_fwd.hpp" +#include "asio/buffered_read_stream.hpp" +#include "asio/buffered_stream_fwd.hpp" +#include "asio/buffered_stream.hpp" +#include "asio/buffered_write_stream_fwd.hpp" +#include "asio/buffered_write_stream.hpp" +#include "asio/buffers_iterator.hpp" +#include "asio/cancel_after.hpp" +#include "asio/cancel_at.hpp" +#include "asio/cancellation_signal.hpp" +#include "asio/cancellation_state.hpp" +#include "asio/cancellation_type.hpp" +#include "asio/co_composed.hpp" +#include "asio/co_spawn.hpp" +#include "asio/completion_condition.hpp" +#include "asio/compose.hpp" +#include "asio/composed.hpp" +#include "asio/config.hpp" +#include "asio/connect.hpp" +#include "asio/connect_pipe.hpp" +#include "asio/consign.hpp" +#include "asio/coroutine.hpp" +#include "asio/defer.hpp" +#include "asio/deferred.hpp" +#include "asio/default_completion_token.hpp" +#include "asio/detached.hpp" +#include "asio/dispatch.hpp" +#include "asio/disposition.hpp" +#include "asio/error.hpp" +#include "asio/error_code.hpp" +#include "asio/execution.hpp" +#include "asio/execution/allocator.hpp" +#include "asio/execution/any_executor.hpp" +#include "asio/execution/blocking.hpp" +#include "asio/execution/blocking_adaptation.hpp" +#include "asio/execution/context.hpp" +#include "asio/execution/context_as.hpp" +#include "asio/execution/executor.hpp" +#include "asio/execution/invocable_archetype.hpp" +#include "asio/execution/mapping.hpp" +#include "asio/execution/occupancy.hpp" +#include "asio/execution/outstanding_work.hpp" +#include "asio/execution/prefer_only.hpp" +#include "asio/execution/relationship.hpp" +#include "asio/executor.hpp" +#include "asio/executor_work_guard.hpp" +#include "asio/file_base.hpp" +#include "asio/generic/basic_endpoint.hpp" +#include "asio/generic/datagram_protocol.hpp" +#include "asio/generic/raw_protocol.hpp" +#include "asio/generic/seq_packet_protocol.hpp" +#include "asio/generic/stream_protocol.hpp" +#include "asio/handler_continuation_hook.hpp" +#include "asio/high_resolution_timer.hpp" +#include "asio/immediate.hpp" +#include "asio/inline_executor.hpp" +#include "asio/inline_or_executor.hpp" +#include "asio/io_context.hpp" +#include "asio/io_context_strand.hpp" +#include "asio/ip/address.hpp" +#include "asio/ip/address_v4.hpp" +#include "asio/ip/address_v4_iterator.hpp" +#include "asio/ip/address_v4_range.hpp" +#include "asio/ip/address_v6.hpp" +#include "asio/ip/address_v6_iterator.hpp" +#include "asio/ip/address_v6_range.hpp" +#include "asio/ip/network_v4.hpp" +#include "asio/ip/network_v6.hpp" +#include "asio/ip/bad_address_cast.hpp" +#include "asio/ip/basic_endpoint.hpp" +#include "asio/ip/basic_resolver.hpp" +#include "asio/ip/basic_resolver_entry.hpp" +#include "asio/ip/basic_resolver_iterator.hpp" +#include "asio/ip/basic_resolver_query.hpp" +#include "asio/ip/host_name.hpp" +#include "asio/ip/icmp.hpp" +#include "asio/ip/multicast.hpp" +#include "asio/ip/resolver_base.hpp" +#include "asio/ip/resolver_query_base.hpp" +#include "asio/ip/tcp.hpp" +#include "asio/ip/udp.hpp" +#include "asio/ip/unicast.hpp" +#include "asio/ip/v6_only.hpp" +#include "asio/is_applicable_property.hpp" +#include "asio/is_contiguous_iterator.hpp" +#include "asio/is_executor.hpp" +#include "asio/is_read_buffered.hpp" +#include "asio/is_write_buffered.hpp" +#include "asio/local/basic_endpoint.hpp" +#include "asio/local/connect_pair.hpp" +#include "asio/local/datagram_protocol.hpp" +#include "asio/local/seq_packet_protocol.hpp" +#include "asio/local/stream_protocol.hpp" +#include "asio/multiple_exceptions.hpp" +#include "asio/packaged_task.hpp" +#include "asio/placeholders.hpp" +#include "asio/posix/basic_descriptor.hpp" +#include "asio/posix/basic_stream_descriptor.hpp" +#include "asio/posix/descriptor.hpp" +#include "asio/posix/descriptor_base.hpp" +#include "asio/posix/stream_descriptor.hpp" +#include "asio/post.hpp" +#include "asio/prefer.hpp" +#include "asio/prepend.hpp" +#include "asio/query.hpp" +#include "asio/random_access_file.hpp" +#include "asio/read.hpp" +#include "asio/read_at.hpp" +#include "asio/read_until.hpp" +#include "asio/readable_pipe.hpp" +#include "asio/recycling_allocator.hpp" +#include "asio/redirect_disposition.hpp" +#include "asio/redirect_error.hpp" +#include "asio/registered_buffer.hpp" +#include "asio/require.hpp" +#include "asio/require_concept.hpp" +#include "asio/serial_port.hpp" +#include "asio/serial_port_base.hpp" +#include "asio/signal_set.hpp" +#include "asio/signal_set_base.hpp" +#include "asio/socket_base.hpp" +#include "asio/static_thread_pool.hpp" +#include "asio/steady_timer.hpp" +#include "asio/strand.hpp" +#include "asio/stream_file.hpp" +#include "asio/streambuf.hpp" +#include "asio/system_context.hpp" +#include "asio/system_error.hpp" +#include "asio/system_executor.hpp" +#include "asio/system_timer.hpp" +#include "asio/this_coro.hpp" +#include "asio/thread.hpp" +#include "asio/thread_pool.hpp" +#include "asio/use_awaitable.hpp" +#include "asio/use_future.hpp" +#include "asio/uses_executor.hpp" +#include "asio/version.hpp" +#include "asio/wait_traits.hpp" +#include "asio/windows/basic_object_handle.hpp" +#include "asio/windows/basic_overlapped_handle.hpp" +#include "asio/windows/basic_random_access_handle.hpp" +#include "asio/windows/basic_stream_handle.hpp" +#include "asio/windows/object_handle.hpp" +#include "asio/windows/overlapped_handle.hpp" +#include "asio/windows/overlapped_ptr.hpp" +#include "asio/windows/random_access_handle.hpp" +#include "asio/windows/stream_handle.hpp" +#include "asio/writable_pipe.hpp" +#include "asio/write.hpp" +#include "asio/write_at.hpp" + +#endif // ASIO_HPP diff --git a/third_party/asio/include/asio/any_completion_executor.hpp b/third_party/asio/include/asio/any_completion_executor.hpp new file mode 100644 index 0000000..ebc954a --- /dev/null +++ b/third_party/asio/include/asio/any_completion_executor.hpp @@ -0,0 +1,338 @@ +// +// any_completion_executor.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2026 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_ANY_COMPLETION_EXECUTOR_HPP +#define ASIO_ANY_COMPLETION_EXECUTOR_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/config.hpp" +#if defined(ASIO_USE_TS_EXECUTOR_AS_DEFAULT) +# include "asio/executor.hpp" +#else // defined(ASIO_USE_TS_EXECUTOR_AS_DEFAULT) +# include "asio/execution.hpp" +#endif // defined(ASIO_USE_TS_EXECUTOR_AS_DEFAULT) + +#include "asio/detail/push_options.hpp" + +namespace asio { +ASIO_INLINE_NAMESPACE_BEGIN + +#if defined(ASIO_USE_TS_EXECUTOR_AS_DEFAULT) + +typedef executor any_completion_executor; + +#else // defined(ASIO_USE_TS_EXECUTOR_AS_DEFAULT) + +/// Polymorphic executor type for use with I/O objects. +/** + * The @c any_completion_executor type is a polymorphic executor that supports + * the set of properties required for the execution of completion handlers. It + * is defined as the execution::any_executor class template parameterised as + * follows: + * @code execution::any_executor< + * execution::prefer_only, + * execution::prefer_only + * execution::prefer_only, + * execution::prefer_only + * > @endcode + */ +class any_completion_executor : +#if defined(GENERATING_DOCUMENTATION) + public execution::any_executor<...> +#else // defined(GENERATING_DOCUMENTATION) + public execution::any_executor< + execution::prefer_only, + execution::prefer_only, + execution::prefer_only, + execution::prefer_only + > +#endif // defined(GENERATING_DOCUMENTATION) +{ +public: +#if !defined(GENERATING_DOCUMENTATION) + typedef execution::any_executor< + execution::prefer_only, + execution::prefer_only, + execution::prefer_only, + execution::prefer_only + > base_type; + + typedef void supportable_properties_type( + execution::prefer_only, + execution::prefer_only, + execution::prefer_only, + execution::prefer_only + ); +#endif // !defined(GENERATING_DOCUMENTATION) + + /// Default constructor. + ASIO_DECL any_completion_executor() noexcept; + + /// Construct in an empty state. Equivalent effects to default constructor. + ASIO_DECL any_completion_executor(nullptr_t) noexcept; + + /// Copy constructor. + ASIO_DECL any_completion_executor( + const any_completion_executor& e) noexcept; + + /// Move constructor. + ASIO_DECL any_completion_executor( + any_completion_executor&& e) noexcept; + + /// Construct to point to the same target as another any_executor. +#if defined(GENERATING_DOCUMENTATION) + template + any_completion_executor( + execution::any_executor e); +#else // defined(GENERATING_DOCUMENTATION) + template + any_completion_executor(OtherAnyExecutor e, + constraint_t< + conditional< + !is_same::value + && is_base_of::value, + typename execution::detail::supportable_properties< + 0, supportable_properties_type>::template + is_valid_target, + false_type + >::type::value + > = 0) + : base_type(static_cast(e)) + { + } +#endif // defined(GENERATING_DOCUMENTATION) + + /// Construct to point to the same target as another any_executor. +#if defined(GENERATING_DOCUMENTATION) + template + any_completion_executor(std::nothrow_t, + execution::any_executor e); +#else // defined(GENERATING_DOCUMENTATION) + template + any_completion_executor(std::nothrow_t, OtherAnyExecutor e, + constraint_t< + conditional< + !is_same::value + && is_base_of::value, + typename execution::detail::supportable_properties< + 0, supportable_properties_type>::template + is_valid_target, + false_type + >::type::value + > = 0) noexcept + : base_type(std::nothrow, static_cast(e)) + { + } +#endif // defined(GENERATING_DOCUMENTATION) + + /// Construct to point to the same target as another any_executor. + ASIO_DECL any_completion_executor(std::nothrow_t, + const any_completion_executor& e) noexcept; + + /// Construct to point to the same target as another any_executor. + ASIO_DECL any_completion_executor(std::nothrow_t, + any_completion_executor&& e) noexcept; + + /// Construct a polymorphic wrapper for the specified executor. +#if defined(GENERATING_DOCUMENTATION) + template + any_completion_executor(Executor e); +#else // defined(GENERATING_DOCUMENTATION) + template + any_completion_executor(Executor e, + constraint_t< + conditional< + !is_same::value + && !is_base_of::value, + execution::detail::is_valid_target_executor< + Executor, supportable_properties_type>, + false_type + >::type::value + > = 0) + : base_type(static_cast(e)) + { + } +#endif // defined(GENERATING_DOCUMENTATION) + + /// Construct a polymorphic wrapper for the specified executor. +#if defined(GENERATING_DOCUMENTATION) + template + any_completion_executor(std::nothrow_t, Executor e); +#else // defined(GENERATING_DOCUMENTATION) + template + any_completion_executor(std::nothrow_t, Executor e, + constraint_t< + conditional< + !is_same::value + && !is_base_of::value, + execution::detail::is_valid_target_executor< + Executor, supportable_properties_type>, + false_type + >::type::value + > = 0) noexcept + : base_type(std::nothrow, static_cast(e)) + { + } +#endif // defined(GENERATING_DOCUMENTATION) + + /// Assignment operator. + ASIO_DECL any_completion_executor& operator=( + const any_completion_executor& e) noexcept; + + /// Move assignment operator. + ASIO_DECL any_completion_executor& operator=( + any_completion_executor&& e) noexcept; + + /// Assignment operator that sets the polymorphic wrapper to the empty state. + ASIO_DECL any_completion_executor& operator=(nullptr_t); + + /// Destructor. + ASIO_DECL ~any_completion_executor(); + + /// Swap targets with another polymorphic wrapper. + ASIO_DECL void swap(any_completion_executor& other) noexcept; + + /// Obtain a polymorphic wrapper with the specified property. + /** + * Do not call this function directly. It is intended for use with the + * asio::require and asio::prefer customisation points. + * + * For example: + * @code any_completion_executor ex = ...; + * auto ex2 = asio::require(ex, execution::relationship.fork); @endcode + */ + template + any_completion_executor require(const Property& p, + constraint_t< + traits::require_member::is_valid + > = 0) const + { + return static_cast(*this).require(p); + } + + /// Obtain a polymorphic wrapper with the specified property. + /** + * Do not call this function directly. It is intended for use with the + * asio::prefer customisation point. + * + * For example: + * @code any_completion_executor ex = ...; + * auto ex2 = asio::prefer(ex, execution::relationship.fork); @endcode + */ + template + any_completion_executor prefer(const Property& p, + constraint_t< + traits::prefer_member::is_valid + > = 0) const + { + return static_cast(*this).prefer(p); + } +}; + +#if !defined(GENERATING_DOCUMENTATION) + +template <> +ASIO_DECL any_completion_executor any_completion_executor::prefer( + const execution::outstanding_work_t::tracked_t&, int) const; + +template <> +ASIO_DECL any_completion_executor any_completion_executor::prefer( + const execution::outstanding_work_t::untracked_t&, int) const; + +template <> +ASIO_DECL any_completion_executor any_completion_executor::prefer( + const execution::relationship_t::fork_t&, int) const; + +template <> +ASIO_DECL any_completion_executor any_completion_executor::prefer( + const execution::relationship_t::continuation_t&, int) const; + +namespace traits { + +#if !defined(ASIO_HAS_DEDUCED_EQUALITY_COMPARABLE_TRAIT) + +template <> +struct equality_comparable +{ + static const bool is_valid = true; + static const bool is_noexcept = true; +}; + +#endif // !defined(ASIO_HAS_DEDUCED_EQUALITY_COMPARABLE_TRAIT) + +#if !defined(ASIO_HAS_DEDUCED_EXECUTE_MEMBER_TRAIT) + +template +struct execute_member +{ + static const bool is_valid = true; + static const bool is_noexcept = false; + typedef void result_type; +}; + +#endif // !defined(ASIO_HAS_DEDUCED_EXECUTE_MEMBER_TRAIT) + +#if !defined(ASIO_HAS_DEDUCED_QUERY_MEMBER_TRAIT) + +template +struct query_member : + query_member +{ +}; + +#endif // !defined(ASIO_HAS_DEDUCED_QUERY_MEMBER_TRAIT) + +#if !defined(ASIO_HAS_DEDUCED_REQUIRE_MEMBER_TRAIT) + +template +struct require_member : + require_member +{ + typedef any_completion_executor result_type; +}; + +#endif // !defined(ASIO_HAS_DEDUCED_REQUIRE_MEMBER_TRAIT) + +#if !defined(ASIO_HAS_DEDUCED_PREFER_MEMBER_TRAIT) + +template +struct prefer_member : + prefer_member +{ + typedef any_completion_executor result_type; +}; + +#endif // !defined(ASIO_HAS_DEDUCED_PREFER_MEMBER_TRAIT) + +} // namespace traits + +#endif // !defined(GENERATING_DOCUMENTATION) + +#endif // defined(ASIO_USE_TS_EXECUTOR_AS_DEFAULT) + +ASIO_INLINE_NAMESPACE_END +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#if defined(ASIO_HEADER_ONLY) \ + && !defined(ASIO_USE_TS_EXECUTOR_AS_DEFAULT) +# include "asio/impl/any_completion_executor.ipp" +#endif // defined(ASIO_HEADER_ONLY) + // && !defined(ASIO_USE_TS_EXECUTOR_AS_DEFAULT) + +#endif // ASIO_ANY_COMPLETION_EXECUTOR_HPP diff --git a/third_party/asio/include/asio/any_completion_handler.hpp b/third_party/asio/include/asio/any_completion_handler.hpp new file mode 100644 index 0000000..6882cc0 --- /dev/null +++ b/third_party/asio/include/asio/any_completion_handler.hpp @@ -0,0 +1,824 @@ +// +// any_completion_handler.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2026 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_ANY_COMPLETION_HANDLER_HPP +#define ASIO_ANY_COMPLETION_HANDLER_HPP + +#include "asio/detail/config.hpp" +#include +#include +#include +#include +#include "asio/any_completion_executor.hpp" +#include "asio/any_io_executor.hpp" +#include "asio/associated_allocator.hpp" +#include "asio/associated_cancellation_slot.hpp" +#include "asio/associated_executor.hpp" +#include "asio/associated_immediate_executor.hpp" +#include "asio/cancellation_state.hpp" +#include "asio/recycling_allocator.hpp" + +#include "asio/detail/push_options.hpp" + +namespace asio { +ASIO_INLINE_NAMESPACE_BEGIN +namespace detail { + +class any_completion_handler_impl_base +{ +public: + template + explicit any_completion_handler_impl_base(S&& slot) + : cancel_state_(static_cast(slot), enable_total_cancellation()) + { + } + + cancellation_slot get_cancellation_slot() const noexcept + { + return cancel_state_.slot(); + } + +private: + cancellation_state cancel_state_; +}; + +template +class any_completion_handler_impl : + public any_completion_handler_impl_base +{ +public: + template + any_completion_handler_impl(S&& slot, H&& h) + : any_completion_handler_impl_base(static_cast(slot)), + handler_(static_cast(h)) + { + } + + struct uninit_deleter + { + typename std::allocator_traits< + associated_allocator_t>>::template + rebind_alloc alloc; + + void operator()(any_completion_handler_impl* ptr) + { + std::allocator_traits::deallocate(alloc, ptr, 1); + } + }; + + struct deleter + { + typename std::allocator_traits< + associated_allocator_t>>::template + rebind_alloc alloc; + + void operator()(any_completion_handler_impl* ptr) + { + std::allocator_traits::destroy(alloc, ptr); + std::allocator_traits::deallocate(alloc, ptr, 1); + } + }; + + template + static any_completion_handler_impl* create(S&& slot, H&& h) + { + uninit_deleter d{ + (get_associated_allocator)(h, + asio::recycling_allocator())}; + + std::unique_ptr uninit_ptr( + std::allocator_traits::allocate(d.alloc, 1), d); + + any_completion_handler_impl* ptr = + new (uninit_ptr.get()) any_completion_handler_impl( + static_cast(slot), static_cast(h)); + + uninit_ptr.release(); + return ptr; + } + + void destroy() + { + deleter d{ + (get_associated_allocator)(handler_, + asio::recycling_allocator())}; + + d(this); + } + + any_completion_executor executor( + const any_completion_executor& candidate) const noexcept + { + return any_completion_executor(std::nothrow, + (get_associated_executor)(handler_, candidate)); + } + + any_completion_executor immediate_executor( + const any_io_executor& candidate) const noexcept + { + return any_completion_executor(std::nothrow, + (get_associated_immediate_executor)(handler_, candidate)); + } + + void* allocate(std::size_t size, std::size_t align_size) const + { + typename std::allocator_traits< + associated_allocator_t>>::template + rebind_alloc alloc( + (get_associated_allocator)(handler_, + asio::recycling_allocator())); + + std::size_t space = size + align_size - 1; + unsigned char* base = + std::allocator_traits::allocate( + alloc, space + sizeof(std::ptrdiff_t)); + + void* p = base; + if (detail::align(align_size, size, p, space)) + { + std::ptrdiff_t off = static_cast(p) - base; + std::memcpy(static_cast(p) + size, &off, sizeof(off)); + return p; + } + + std::bad_alloc ex; + asio::detail::throw_exception(ex); + return nullptr; + } + + void deallocate(void* p, std::size_t size, std::size_t align) const + { + if (p) + { + typename std::allocator_traits< + associated_allocator_t>>::template + rebind_alloc alloc( + (get_associated_allocator)(handler_, + asio::recycling_allocator())); + + std::ptrdiff_t off; + std::memcpy(&off, static_cast(p) + size, sizeof(off)); + unsigned char* base = static_cast(p) - off; + + std::allocator_traits::deallocate( + alloc, base, size + align -1 + sizeof(std::ptrdiff_t)); + } + } + + template + void call(Args&&... args) + { + deleter d{ + (get_associated_allocator)(handler_, + asio::recycling_allocator())}; + + std::unique_ptr ptr(this, d); + Handler handler(static_cast(handler_)); + ptr.reset(); + + static_cast(handler)( + static_cast(args)...); + } + +private: + Handler handler_; +}; + +template +class any_completion_handler_call_fn; + +template +class any_completion_handler_call_fn +{ +public: + using type = void(*)(any_completion_handler_impl_base*, Args...); + + constexpr any_completion_handler_call_fn(type fn) + : call_fn_(fn) + { + } + + void call(any_completion_handler_impl_base* impl, Args... args) const + { + call_fn_(impl, static_cast(args)...); + } + + template + static void impl(any_completion_handler_impl_base* impl, Args... args) + { + static_cast*>(impl)->call( + static_cast(args)...); + } + +private: + type call_fn_; +}; + +template +class any_completion_handler_call_fns; + +template +class any_completion_handler_call_fns : + public any_completion_handler_call_fn +{ +public: + using any_completion_handler_call_fn< + Signature>::any_completion_handler_call_fn; + using any_completion_handler_call_fn::call; +}; + +template +class any_completion_handler_call_fns : + public any_completion_handler_call_fn, + public any_completion_handler_call_fns +{ +public: + template + constexpr any_completion_handler_call_fns(CallFn fn, CallFns... fns) + : any_completion_handler_call_fn(fn), + any_completion_handler_call_fns(fns...) + { + } + + using any_completion_handler_call_fn::call; + using any_completion_handler_call_fns::call; +}; + +class any_completion_handler_destroy_fn +{ +public: + using type = void(*)(any_completion_handler_impl_base*); + + constexpr any_completion_handler_destroy_fn(type fn) + : destroy_fn_(fn) + { + } + + void destroy(any_completion_handler_impl_base* impl) const + { + destroy_fn_(impl); + } + + template + static void impl(any_completion_handler_impl_base* impl) + { + static_cast*>(impl)->destroy(); + } + +private: + type destroy_fn_; +}; + +class any_completion_handler_executor_fn +{ +public: + using type = any_completion_executor(*)( + any_completion_handler_impl_base*, const any_completion_executor&); + + constexpr any_completion_handler_executor_fn(type fn) + : executor_fn_(fn) + { + } + + any_completion_executor executor(any_completion_handler_impl_base* impl, + const any_completion_executor& candidate) const + { + return executor_fn_(impl, candidate); + } + + template + static any_completion_executor impl(any_completion_handler_impl_base* impl, + const any_completion_executor& candidate) + { + return static_cast*>(impl)->executor( + candidate); + } + +private: + type executor_fn_; +}; + +class any_completion_handler_immediate_executor_fn +{ +public: + using type = any_completion_executor(*)( + any_completion_handler_impl_base*, const any_io_executor&); + + constexpr any_completion_handler_immediate_executor_fn(type fn) + : immediate_executor_fn_(fn) + { + } + + any_completion_executor immediate_executor( + any_completion_handler_impl_base* impl, + const any_io_executor& candidate) const + { + return immediate_executor_fn_(impl, candidate); + } + + template + static any_completion_executor impl(any_completion_handler_impl_base* impl, + const any_io_executor& candidate) + { + return static_cast*>( + impl)->immediate_executor(candidate); + } + +private: + type immediate_executor_fn_; +}; + +class any_completion_handler_allocate_fn +{ +public: + using type = void*(*)(any_completion_handler_impl_base*, + std::size_t, std::size_t); + + constexpr any_completion_handler_allocate_fn(type fn) + : allocate_fn_(fn) + { + } + + void* allocate(any_completion_handler_impl_base* impl, + std::size_t size, std::size_t align) const + { + return allocate_fn_(impl, size, align); + } + + template + static void* impl(any_completion_handler_impl_base* impl, + std::size_t size, std::size_t align) + { + return static_cast*>(impl)->allocate( + size, align); + } + +private: + type allocate_fn_; +}; + +class any_completion_handler_deallocate_fn +{ +public: + using type = void(*)(any_completion_handler_impl_base*, + void*, std::size_t, std::size_t); + + constexpr any_completion_handler_deallocate_fn(type fn) + : deallocate_fn_(fn) + { + } + + void deallocate(any_completion_handler_impl_base* impl, + void* p, std::size_t size, std::size_t align) const + { + deallocate_fn_(impl, p, size, align); + } + + template + static void impl(any_completion_handler_impl_base* impl, + void* p, std::size_t size, std::size_t align) + { + static_cast*>(impl)->deallocate( + p, size, align); + } + +private: + type deallocate_fn_; +}; + +template +class any_completion_handler_fn_table + : private any_completion_handler_destroy_fn, + private any_completion_handler_executor_fn, + private any_completion_handler_immediate_executor_fn, + private any_completion_handler_allocate_fn, + private any_completion_handler_deallocate_fn, + private any_completion_handler_call_fns +{ +public: + template + constexpr any_completion_handler_fn_table( + any_completion_handler_destroy_fn::type destroy_fn, + any_completion_handler_executor_fn::type executor_fn, + any_completion_handler_immediate_executor_fn::type immediate_executor_fn, + any_completion_handler_allocate_fn::type allocate_fn, + any_completion_handler_deallocate_fn::type deallocate_fn, + CallFns... call_fns) + : any_completion_handler_destroy_fn(destroy_fn), + any_completion_handler_executor_fn(executor_fn), + any_completion_handler_immediate_executor_fn(immediate_executor_fn), + any_completion_handler_allocate_fn(allocate_fn), + any_completion_handler_deallocate_fn(deallocate_fn), + any_completion_handler_call_fns(call_fns...) + { + } + + using any_completion_handler_destroy_fn::destroy; + using any_completion_handler_executor_fn::executor; + using any_completion_handler_immediate_executor_fn::immediate_executor; + using any_completion_handler_allocate_fn::allocate; + using any_completion_handler_deallocate_fn::deallocate; + using any_completion_handler_call_fns::call; +}; + +template +struct any_completion_handler_fn_table_instance +{ + static constexpr any_completion_handler_fn_table + value = any_completion_handler_fn_table( + &any_completion_handler_destroy_fn::impl, + &any_completion_handler_executor_fn::impl, + &any_completion_handler_immediate_executor_fn::impl, + &any_completion_handler_allocate_fn::impl, + &any_completion_handler_deallocate_fn::impl, + &any_completion_handler_call_fn::template impl...); +}; + +template +constexpr any_completion_handler_fn_table +any_completion_handler_fn_table_instance::value; + +} // namespace detail + +template +class any_completion_handler; + +/// An allocator type that forwards memory allocation operations through an +/// instance of @c any_completion_handler. +template +class any_completion_handler_allocator +{ +private: + template + friend class any_completion_handler; + + template + friend class any_completion_handler_allocator; + + const detail::any_completion_handler_fn_table* fn_table_; + detail::any_completion_handler_impl_base* impl_; + + constexpr any_completion_handler_allocator(int, + const any_completion_handler& h) noexcept + : fn_table_(h.fn_table_), + impl_(h.impl_) + { + } + +public: + /// The type of objects that may be allocated by the allocator. + typedef T value_type; + + /// Rebinds an allocator to another value type. + template + struct rebind + { + /// Specifies the type of the rebound allocator. + typedef any_completion_handler_allocator other; + }; + + /// Construct from another @c any_completion_handler_allocator. + template + constexpr any_completion_handler_allocator( + const any_completion_handler_allocator& a) + noexcept + : fn_table_(a.fn_table_), + impl_(a.impl_) + { + } + + /// Equality operator. + constexpr bool operator==( + const any_completion_handler_allocator& other) const noexcept + { + return fn_table_ == other.fn_table_ && impl_ == other.impl_; + } + + /// Inequality operator. + constexpr bool operator!=( + const any_completion_handler_allocator& other) const noexcept + { + return fn_table_ != other.fn_table_ || impl_ != other.impl_; + } + + /// Allocate space for @c n objects of the allocator's value type. + T* allocate(std::size_t n) const + { + if (fn_table_) + { + return static_cast( + fn_table_->allocate( + impl_, sizeof(T) * n, alignof(T))); + } + std::bad_alloc ex; + asio::detail::throw_exception(ex); + return nullptr; + } + + /// Deallocate space for @c n objects of the allocator's value type. + void deallocate(T* p, std::size_t n) const + { + fn_table_->deallocate(impl_, p, sizeof(T) * n, alignof(T)); + } +}; + +/// A protoco-allocator type that may be rebound to obtain an allocator that +/// forwards memory allocation operations through an instance of +/// @c any_completion_handler. +template +class any_completion_handler_allocator +{ +private: + template + friend class any_completion_handler; + + template + friend class any_completion_handler_allocator; + + const detail::any_completion_handler_fn_table* fn_table_; + detail::any_completion_handler_impl_base* impl_; + + constexpr any_completion_handler_allocator(int, + const any_completion_handler& h) noexcept + : fn_table_(h.fn_table_), + impl_(h.impl_) + { + } + +public: + /// @c void as no objects can be allocated through a proto-allocator. + typedef void value_type; + + /// Rebinds an allocator to another value type. + template + struct rebind + { + /// Specifies the type of the rebound allocator. + typedef any_completion_handler_allocator other; + }; + + /// Construct from another @c any_completion_handler_allocator. + template + constexpr any_completion_handler_allocator( + const any_completion_handler_allocator& a) + noexcept + : fn_table_(a.fn_table_), + impl_(a.impl_) + { + } + + /// Equality operator. + constexpr bool operator==( + const any_completion_handler_allocator& other) const noexcept + { + return fn_table_ == other.fn_table_ && impl_ == other.impl_; + } + + /// Inequality operator. + constexpr bool operator!=( + const any_completion_handler_allocator& other) const noexcept + { + return fn_table_ != other.fn_table_ || impl_ != other.impl_; + } +}; + +/// Polymorphic wrapper for completion handlers. +/** + * The @c any_completion_handler class template is a polymorphic wrapper for + * completion handlers that propagates the associated executor, associated + * allocator, and associated cancellation slot through a type-erasing interface. + * + * When using @c any_completion_handler, specify one or more completion + * signatures as template parameters. These will dictate the arguments that may + * be passed to the handler through the polymorphic interface. + * + * Typical uses for @c any_completion_handler include: + * + * @li Separate compilation of asynchronous operation implementations. + * + * @li Enabling interoperability between asynchronous operations and virtual + * functions. + */ +template +class any_completion_handler +{ +#if !defined(GENERATING_DOCUMENTATION) +private: + template + friend class any_completion_handler_allocator; + + template + friend struct associated_executor; + + template + friend struct associated_immediate_executor; + + const detail::any_completion_handler_fn_table* fn_table_; + detail::any_completion_handler_impl_base* impl_; +#endif // !defined(GENERATING_DOCUMENTATION) + +public: + /// The associated allocator type. + using allocator_type = any_completion_handler_allocator; + + /// The associated cancellation slot type. + using cancellation_slot_type = cancellation_slot; + + /// Construct an @c any_completion_handler in an empty state, without a target + /// object. + constexpr any_completion_handler() + : fn_table_(nullptr), + impl_(nullptr) + { + } + + /// Construct an @c any_completion_handler in an empty state, without a target + /// object. + constexpr any_completion_handler(nullptr_t) + : fn_table_(nullptr), + impl_(nullptr) + { + } + + /// Construct an @c any_completion_handler to contain the specified target. + template > + any_completion_handler(H&& h, + constraint_t< + !is_same, any_completion_handler>::value + > = 0) + : fn_table_( + &detail::any_completion_handler_fn_table_instance< + Handler, Signatures...>::value), + impl_(detail::any_completion_handler_impl::create( + (get_associated_cancellation_slot)(h), static_cast(h))) + { + } + + /// Move-construct an @c any_completion_handler from another. + /** + * After the operation, the moved-from object @c other has no target. + */ + any_completion_handler(any_completion_handler&& other) noexcept + : fn_table_(other.fn_table_), + impl_(other.impl_) + { + other.fn_table_ = nullptr; + other.impl_ = nullptr; + } + + /// Move-assign an @c any_completion_handler from another. + /** + * After the operation, the moved-from object @c other has no target. + */ + any_completion_handler& operator=( + any_completion_handler&& other) noexcept + { + any_completion_handler( + static_cast(other)).swap(*this); + return *this; + } + + /// Assignment operator that sets the polymorphic wrapper to the empty state. + any_completion_handler& operator=(nullptr_t) noexcept + { + any_completion_handler().swap(*this); + return *this; + } + + /// Destructor. + ~any_completion_handler() + { + if (impl_) + fn_table_->destroy(impl_); + } + + /// Test if the polymorphic wrapper is empty. + constexpr explicit operator bool() const noexcept + { + return impl_ != nullptr; + } + + /// Test if the polymorphic wrapper is non-empty. + constexpr bool operator!() const noexcept + { + return impl_ == nullptr; + } + + /// Swap the content of an @c any_completion_handler with another. + void swap(any_completion_handler& other) noexcept + { + std::swap(fn_table_, other.fn_table_); + std::swap(impl_, other.impl_); + } + + /// Get the associated allocator. + allocator_type get_allocator() const noexcept + { + return allocator_type(0, *this); + } + + /// Get the associated cancellation slot. + cancellation_slot_type get_cancellation_slot() const noexcept + { + return impl_ ? impl_->get_cancellation_slot() : cancellation_slot_type(); + } + + /// Function call operator. + /** + * Invokes target completion handler with the supplied arguments. + * + * This function may only be called once, as the target handler is moved from. + * The polymorphic wrapper is left in an empty state. + * + * Throws @c std::bad_function_call if the polymorphic wrapper is empty. + */ + template + auto operator()(Args&&... args) + -> decltype(fn_table_->call(impl_, static_cast(args)...)) + { + if (detail::any_completion_handler_impl_base* impl = impl_) + { + impl_ = nullptr; + return fn_table_->call(impl, static_cast(args)...); + } + std::bad_function_call ex; + asio::detail::throw_exception(ex); + } + + /// Equality operator. + friend constexpr bool operator==( + const any_completion_handler& a, nullptr_t) noexcept + { + return a.impl_ == nullptr; + } + + /// Equality operator. + friend constexpr bool operator==( + nullptr_t, const any_completion_handler& b) noexcept + { + return nullptr == b.impl_; + } + + /// Inequality operator. + friend constexpr bool operator!=( + const any_completion_handler& a, nullptr_t) noexcept + { + return a.impl_ != nullptr; + } + + /// Inequality operator. + friend constexpr bool operator!=( + nullptr_t, const any_completion_handler& b) noexcept + { + return nullptr != b.impl_; + } +}; + +template +struct associated_executor, Candidate> +{ + using type = any_completion_executor; + + static type get(const any_completion_handler& handler, + const Candidate& candidate = Candidate()) noexcept + { + any_completion_executor any_candidate(std::nothrow, candidate); + return handler.fn_table_ + ? handler.fn_table_->executor(handler.impl_, any_candidate) + : any_candidate; + } +}; + +template +struct associated_immediate_executor< + any_completion_handler, Candidate> +{ + using type = any_completion_executor; + + static type get(const any_completion_handler& handler, + const Candidate& candidate = Candidate()) noexcept + { + any_io_executor any_candidate(std::nothrow, candidate); + return handler.fn_table_ + ? handler.fn_table_->immediate_executor(handler.impl_, any_candidate) + : any_candidate; + } +}; + +ASIO_INLINE_NAMESPACE_END +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_ANY_COMPLETION_HANDLER_HPP diff --git a/third_party/asio/include/asio/any_io_executor.hpp b/third_party/asio/include/asio/any_io_executor.hpp new file mode 100644 index 0000000..035565c --- /dev/null +++ b/third_party/asio/include/asio/any_io_executor.hpp @@ -0,0 +1,353 @@ +// +// any_io_executor.hpp +// ~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2026 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_ANY_IO_EXECUTOR_HPP +#define ASIO_ANY_IO_EXECUTOR_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/config.hpp" +#if defined(ASIO_USE_TS_EXECUTOR_AS_DEFAULT) +# include "asio/executor.hpp" +#else // defined(ASIO_USE_TS_EXECUTOR_AS_DEFAULT) +# include "asio/execution.hpp" +# include "asio/execution_context.hpp" +#endif // defined(ASIO_USE_TS_EXECUTOR_AS_DEFAULT) + +#include "asio/detail/push_options.hpp" + +namespace asio { +ASIO_INLINE_NAMESPACE_BEGIN + +#if defined(ASIO_USE_TS_EXECUTOR_AS_DEFAULT) + +typedef executor any_io_executor; + +#else // defined(ASIO_USE_TS_EXECUTOR_AS_DEFAULT) + +/// Polymorphic executor type for use with I/O objects. +/** + * The @c any_io_executor type is a polymorphic executor that supports the set + * of properties required by I/O objects. It is defined as the + * execution::any_executor class template parameterised as follows: + * @code execution::any_executor< + * execution::context_as_t, + * execution::blocking_t::never_t, + * execution::prefer_only, + * execution::prefer_only, + * execution::prefer_only, + * execution::prefer_only, + * execution::prefer_only + * > @endcode + */ +class any_io_executor : +#if defined(GENERATING_DOCUMENTATION) + public execution::any_executor<...> +#else // defined(GENERATING_DOCUMENTATION) + public execution::any_executor< + execution::context_as_t, + execution::blocking_t::never_t, + execution::prefer_only, + execution::prefer_only, + execution::prefer_only, + execution::prefer_only, + execution::prefer_only + > +#endif // defined(GENERATING_DOCUMENTATION) +{ +public: +#if !defined(GENERATING_DOCUMENTATION) + typedef execution::any_executor< + execution::context_as_t, + execution::blocking_t::never_t, + execution::prefer_only, + execution::prefer_only, + execution::prefer_only, + execution::prefer_only, + execution::prefer_only + > base_type; + + typedef void supportable_properties_type( + execution::context_as_t, + execution::blocking_t::never_t, + execution::prefer_only, + execution::prefer_only, + execution::prefer_only, + execution::prefer_only, + execution::prefer_only + ); +#endif // !defined(GENERATING_DOCUMENTATION) + + /// Default constructor. + ASIO_DECL any_io_executor() noexcept; + + /// Construct in an empty state. Equivalent effects to default constructor. + ASIO_DECL any_io_executor(nullptr_t) noexcept; + + /// Copy constructor. + ASIO_DECL any_io_executor(const any_io_executor& e) noexcept; + + /// Move constructor. + ASIO_DECL any_io_executor(any_io_executor&& e) noexcept; + + /// Construct to point to the same target as another any_executor. +#if defined(GENERATING_DOCUMENTATION) + template + any_io_executor(execution::any_executor e); +#else // defined(GENERATING_DOCUMENTATION) + template + any_io_executor(OtherAnyExecutor e, + constraint_t< + conditional_t< + !is_same::value + && is_base_of::value, + typename execution::detail::supportable_properties< + 0, supportable_properties_type>::template + is_valid_target, + false_type + >::value + > = 0) + : base_type(static_cast(e)) + { + } +#endif // defined(GENERATING_DOCUMENTATION) + + /// Construct to point to the same target as another any_executor. +#if defined(GENERATING_DOCUMENTATION) + template + any_io_executor(std::nothrow_t, + execution::any_executor e); +#else // defined(GENERATING_DOCUMENTATION) + template + any_io_executor(std::nothrow_t, OtherAnyExecutor e, + constraint_t< + conditional_t< + !is_same::value + && is_base_of::value, + typename execution::detail::supportable_properties< + 0, supportable_properties_type>::template + is_valid_target, + false_type + >::value + > = 0) noexcept + : base_type(std::nothrow, static_cast(e)) + { + } +#endif // defined(GENERATING_DOCUMENTATION) + + /// Construct to point to the same target as another any_executor. + ASIO_DECL any_io_executor(std::nothrow_t, + const any_io_executor& e) noexcept; + + /// Construct to point to the same target as another any_executor. + ASIO_DECL any_io_executor(std::nothrow_t, any_io_executor&& e) noexcept; + + /// Construct a polymorphic wrapper for the specified executor. +#if defined(GENERATING_DOCUMENTATION) + template + any_io_executor(Executor e); +#else // defined(GENERATING_DOCUMENTATION) + template + any_io_executor(Executor e, + constraint_t< + conditional_t< + !is_same::value + && !is_base_of::value, + execution::detail::is_valid_target_executor< + Executor, supportable_properties_type>, + false_type + >::value + > = 0) + : base_type(static_cast(e)) + { + } +#endif // defined(GENERATING_DOCUMENTATION) + + /// Construct a polymorphic wrapper for the specified executor. +#if defined(GENERATING_DOCUMENTATION) + template + any_io_executor(std::nothrow_t, Executor e); +#else // defined(GENERATING_DOCUMENTATION) + template + any_io_executor(std::nothrow_t, Executor e, + constraint_t< + conditional_t< + !is_same::value + && !is_base_of::value, + execution::detail::is_valid_target_executor< + Executor, supportable_properties_type>, + false_type + >::value + > = 0) noexcept + : base_type(std::nothrow, static_cast(e)) + { + } +#endif // defined(GENERATING_DOCUMENTATION) + + /// Assignment operator. + ASIO_DECL any_io_executor& operator=( + const any_io_executor& e) noexcept; + + /// Move assignment operator. + ASIO_DECL any_io_executor& operator=(any_io_executor&& e) noexcept; + + /// Assignment operator that sets the polymorphic wrapper to the empty state. + ASIO_DECL any_io_executor& operator=(nullptr_t); + + /// Destructor. + ASIO_DECL ~any_io_executor(); + + /// Swap targets with another polymorphic wrapper. + ASIO_DECL void swap(any_io_executor& other) noexcept; + + /// Obtain a polymorphic wrapper with the specified property. + /** + * Do not call this function directly. It is intended for use with the + * asio::require and asio::prefer customisation points. + * + * For example: + * @code any_io_executor ex = ...; + * auto ex2 = asio::require(ex, execution::blocking.possibly); @endcode + */ + template + any_io_executor require(const Property& p, + constraint_t< + traits::require_member::is_valid + > = 0) const + { + return static_cast(*this).require(p); + } + + /// Obtain a polymorphic wrapper with the specified property. + /** + * Do not call this function directly. It is intended for use with the + * asio::prefer customisation point. + * + * For example: + * @code any_io_executor ex = ...; + * auto ex2 = asio::prefer(ex, execution::blocking.possibly); @endcode + */ + template + any_io_executor prefer(const Property& p, + constraint_t< + traits::prefer_member::is_valid + > = 0) const + { + return static_cast(*this).prefer(p); + } +}; + +#if !defined(GENERATING_DOCUMENTATION) + +template <> +ASIO_DECL any_io_executor any_io_executor::require( + const execution::blocking_t::never_t&, int) const; + +template <> +ASIO_DECL any_io_executor any_io_executor::prefer( + const execution::blocking_t::possibly_t&, int) const; + +template <> +ASIO_DECL any_io_executor any_io_executor::prefer( + const execution::outstanding_work_t::tracked_t&, int) const; + +template <> +ASIO_DECL any_io_executor any_io_executor::prefer( + const execution::outstanding_work_t::untracked_t&, int) const; + +template <> +ASIO_DECL any_io_executor any_io_executor::prefer( + const execution::relationship_t::fork_t&, int) const; + +template <> +ASIO_DECL any_io_executor any_io_executor::prefer( + const execution::relationship_t::continuation_t&, int) const; + +namespace traits { + +#if !defined(ASIO_HAS_DEDUCED_EQUALITY_COMPARABLE_TRAIT) + +template <> +struct equality_comparable +{ + static const bool is_valid = true; + static const bool is_noexcept = true; +}; + +#endif // !defined(ASIO_HAS_DEDUCED_EQUALITY_COMPARABLE_TRAIT) + +#if !defined(ASIO_HAS_DEDUCED_EXECUTE_MEMBER_TRAIT) + +template +struct execute_member +{ + static const bool is_valid = true; + static const bool is_noexcept = false; + typedef void result_type; +}; + +#endif // !defined(ASIO_HAS_DEDUCED_EXECUTE_MEMBER_TRAIT) + +#if !defined(ASIO_HAS_DEDUCED_QUERY_MEMBER_TRAIT) + +template +struct query_member : + query_member +{ +}; + +#endif // !defined(ASIO_HAS_DEDUCED_QUERY_MEMBER_TRAIT) + +#if !defined(ASIO_HAS_DEDUCED_REQUIRE_MEMBER_TRAIT) + +template +struct require_member : + require_member +{ + typedef any_io_executor result_type; +}; + +#endif // !defined(ASIO_HAS_DEDUCED_REQUIRE_MEMBER_TRAIT) + +#if !defined(ASIO_HAS_DEDUCED_PREFER_MEMBER_TRAIT) + +template +struct prefer_member : + prefer_member +{ + typedef any_io_executor result_type; +}; + +#endif // !defined(ASIO_HAS_DEDUCED_PREFER_MEMBER_TRAIT) + +} // namespace traits + +#endif // !defined(GENERATING_DOCUMENTATION) + +#endif // defined(ASIO_USE_TS_EXECUTOR_AS_DEFAULT) + +ASIO_INLINE_NAMESPACE_END +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#if defined(ASIO_HEADER_ONLY) \ + && !defined(ASIO_USE_TS_EXECUTOR_AS_DEFAULT) +# include "asio/impl/any_io_executor.ipp" +#endif // defined(ASIO_HEADER_ONLY) + // && !defined(ASIO_USE_TS_EXECUTOR_AS_DEFAULT) + +#endif // ASIO_ANY_IO_EXECUTOR_HPP diff --git a/third_party/asio/include/asio/append.hpp b/third_party/asio/include/asio/append.hpp new file mode 100644 index 0000000..314ec19 --- /dev/null +++ b/third_party/asio/include/asio/append.hpp @@ -0,0 +1,67 @@ +// +// append.hpp +// ~~~~~~~~~~ +// +// Copyright (c) 2003-2026 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_APPEND_HPP +#define ASIO_APPEND_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/config.hpp" +#include +#include "asio/detail/type_traits.hpp" + +#include "asio/detail/push_options.hpp" + +namespace asio { +ASIO_INLINE_NAMESPACE_BEGIN + +/// Completion token type used to specify that the completion handler +/// arguments should be passed additional values after the results of the +/// operation. +template +class append_t +{ +public: + /// Constructor. + template + constexpr explicit append_t(T&& completion_token, V&&... values) + : token_(static_cast(completion_token)), + values_(static_cast(values)...) + { + } + +//private: + CompletionToken token_; + std::tuple values_; +}; + +/// Completion token type used to specify that the completion handler +/// arguments should be passed additional values after the results of the +/// operation. +template +ASIO_NODISCARD inline constexpr +append_t, decay_t...> +append(CompletionToken&& completion_token, Values&&... values) +{ + return append_t, decay_t...>( + static_cast(completion_token), + static_cast(values)...); +} + +ASIO_INLINE_NAMESPACE_END +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#include "asio/impl/append.hpp" + +#endif // ASIO_APPEND_HPP diff --git a/third_party/asio/include/asio/as_tuple.hpp b/third_party/asio/include/asio/as_tuple.hpp new file mode 100644 index 0000000..e90cc43 --- /dev/null +++ b/third_party/asio/include/asio/as_tuple.hpp @@ -0,0 +1,154 @@ +// +// as_tuple.hpp +// ~~~~~~~~~~~~ +// +// Copyright (c) 2003-2026 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_AS_TUPLE_HPP +#define ASIO_AS_TUPLE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/config.hpp" +#include "asio/detail/type_traits.hpp" + +#include "asio/detail/push_options.hpp" + +namespace asio { +ASIO_INLINE_NAMESPACE_BEGIN + +/// A @ref completion_token adapter used to specify that the completion handler +/// arguments should be combined into a single tuple argument. +/** + * The as_tuple_t class is used to indicate that any arguments to the + * completion handler should be combined and passed as a single tuple argument. + * The arguments are first moved into a @c std::tuple and that tuple is then + * passed to the completion handler. + */ +template +class as_tuple_t +{ +public: + /// Tag type used to prevent the "default" constructor from being used for + /// conversions. + struct default_constructor_tag {}; + + /// Default constructor. + /** + * This constructor is only valid if the underlying completion token is + * default constructible and move constructible. The underlying completion + * token is itself defaulted as an argument to allow it to capture a source + * location. + */ + constexpr as_tuple_t( + default_constructor_tag = default_constructor_tag(), + CompletionToken token = CompletionToken()) + : token_(static_cast(token)) + { + } + + /// Constructor. + template + constexpr explicit as_tuple_t( + T&& completion_token) + : token_(static_cast(completion_token)) + { + } + + /// Adapts an executor to add the @c as_tuple_t completion token as the + /// default. + template + struct executor_with_default : InnerExecutor + { + /// Specify @c as_tuple_t as the default completion token type. + typedef as_tuple_t default_completion_token_type; + + /// Construct the adapted executor from the inner executor type. + template + executor_with_default(const InnerExecutor1& ex, + constraint_t< + conditional_t< + !is_same::value, + is_convertible, + false_type + >::value + > = 0) noexcept + : InnerExecutor(ex) + { + } + }; + + /// Type alias to adapt an I/O object to use @c as_tuple_t as its + /// default completion token type. + template + using as_default_on_t = typename T::template rebind_executor< + executor_with_default>::other; + + /// Function helper to adapt an I/O object to use @c as_tuple_t as its + /// default completion token type. + template + static typename decay_t::template rebind_executor< + executor_with_default::executor_type> + >::other + as_default_on(T&& object) + { + return typename decay_t::template rebind_executor< + executor_with_default::executor_type> + >::other(static_cast(object)); + } + +//private: + CompletionToken token_; +}; + +/// A function object type that adapts a @ref completion_token to specify that +/// the completion handler arguments should be combined into a single tuple +/// argument. +/** + * May also be used directly as a completion token, in which case it adapts the + * asynchronous operation's default completion token (or asio::deferred + * if no default is available). + */ +struct partial_as_tuple +{ + /// Default constructor. + constexpr partial_as_tuple() + { + } + + /// Adapt a @ref completion_token to specify that the completion handler + /// arguments should be combined into a single tuple argument. + template + ASIO_NODISCARD inline + constexpr as_tuple_t> + operator()(CompletionToken&& completion_token) const + { + return as_tuple_t>( + static_cast(completion_token)); + } +}; + +/// A function object that adapts a @ref completion_token to specify that the +/// completion handler arguments should be combined into a single tuple +/// argument. +/** + * May also be used directly as a completion token, in which case it adapts the + * asynchronous operation's default completion token (or asio::deferred + * if no default is available). + */ +ASIO_INLINE_VARIABLE constexpr partial_as_tuple as_tuple; + +ASIO_INLINE_NAMESPACE_END +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#include "asio/impl/as_tuple.hpp" + +#endif // ASIO_AS_TUPLE_HPP diff --git a/third_party/asio/include/asio/associated_allocator.hpp b/third_party/asio/include/asio/associated_allocator.hpp new file mode 100644 index 0000000..aa9748d --- /dev/null +++ b/third_party/asio/include/asio/associated_allocator.hpp @@ -0,0 +1,216 @@ +// +// associated_allocator.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2026 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_ASSOCIATED_ALLOCATOR_HPP +#define ASIO_ASSOCIATED_ALLOCATOR_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/config.hpp" +#include +#include "asio/associator.hpp" +#include "asio/detail/functional.hpp" +#include "asio/detail/type_traits.hpp" + +#include "asio/detail/push_options.hpp" + +namespace asio { +ASIO_INLINE_NAMESPACE_BEGIN + +template +struct associated_allocator; + +namespace detail { + +template +struct has_allocator_type : false_type +{ +}; + +template +struct has_allocator_type> : true_type +{ +}; + +template +struct associated_allocator_impl +{ + typedef void asio_associated_allocator_is_unspecialised; + + typedef A type; + + static type get(const T&) noexcept + { + return type(); + } + + static const type& get(const T&, const A& a) noexcept + { + return a; + } +}; + +template +struct associated_allocator_impl> +{ + typedef typename T::allocator_type type; + + static auto get(const T& t) noexcept + -> decltype(t.get_allocator()) + { + return t.get_allocator(); + } + + static auto get(const T& t, const A&) noexcept + -> decltype(t.get_allocator()) + { + return t.get_allocator(); + } +}; + +template +struct associated_allocator_impl::value + >, + void_t< + typename associator::type + >> : associator +{ +}; + +} // namespace detail + +/// Traits type used to obtain the allocator associated with an object. +/** + * A program may specialise this traits type if the @c T template parameter in + * the specialisation is a user-defined type. The template parameter @c + * Allocator shall be a type meeting the Allocator requirements. + * + * Specialisations shall meet the following requirements, where @c t is a const + * reference to an object of type @c T, and @c a is an object of type @c + * Allocator. + * + * @li Provide a nested typedef @c type that identifies a type meeting the + * Allocator requirements. + * + * @li Provide a noexcept static member function named @c get, callable as @c + * get(t) and with return type @c type or a (possibly const) reference to @c + * type. + * + * @li Provide a noexcept static member function named @c get, callable as @c + * get(t,a) and with return type @c type or a (possibly const) reference to @c + * type. + */ +template > +struct associated_allocator +#if !defined(GENERATING_DOCUMENTATION) + : detail::associated_allocator_impl +#endif // !defined(GENERATING_DOCUMENTATION) +{ +#if defined(GENERATING_DOCUMENTATION) + /// If @c T has a nested type @c allocator_type, T::allocator_type. + /// Otherwise @c Allocator. + typedef see_below type; + + /// If @c T has a nested type @c allocator_type, returns + /// t.get_allocator(). Otherwise returns @c type(). + static decltype(auto) get(const T& t) noexcept; + + /// If @c T has a nested type @c allocator_type, returns + /// t.get_allocator(). Otherwise returns @c a. + static decltype(auto) get(const T& t, const Allocator& a) noexcept; +#endif // defined(GENERATING_DOCUMENTATION) +}; + +/// Helper function to obtain an object's associated allocator. +/** + * @returns associated_allocator::get(t) + */ +template +ASIO_NODISCARD inline typename associated_allocator::type +get_associated_allocator(const T& t) noexcept +{ + return associated_allocator::get(t); +} + +/// Helper function to obtain an object's associated allocator. +/** + * @returns associated_allocator::get(t, a) + */ +template +ASIO_NODISCARD inline auto get_associated_allocator( + const T& t, const Allocator& a) noexcept + -> decltype(associated_allocator::get(t, a)) +{ + return associated_allocator::get(t, a); +} + +template > +using associated_allocator_t + = typename associated_allocator::type; + +namespace detail { + +template +struct associated_allocator_forwarding_base +{ +}; + +template +struct associated_allocator_forwarding_base::asio_associated_allocator_is_unspecialised, + void + >::value + >> +{ + typedef void asio_associated_allocator_is_unspecialised; +}; + +} // namespace detail + +/// Specialisation of associated_allocator for @c std::reference_wrapper. +template +struct associated_allocator, Allocator> +#if !defined(GENERATING_DOCUMENTATION) + : detail::associated_allocator_forwarding_base +#endif // !defined(GENERATING_DOCUMENTATION) +{ + /// Forwards @c type to the associator specialisation for the unwrapped type + /// @c T. + typedef typename associated_allocator::type type; + + /// Forwards the request to get the allocator to the associator specialisation + /// for the unwrapped type @c T. + static type get(reference_wrapper t) noexcept + { + return associated_allocator::get(t.get()); + } + + /// Forwards the request to get the allocator to the associator specialisation + /// for the unwrapped type @c T. + static auto get(reference_wrapper t, const Allocator& a) noexcept + -> decltype(associated_allocator::get(t.get(), a)) + { + return associated_allocator::get(t.get(), a); + } +}; + +ASIO_INLINE_NAMESPACE_END +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_ASSOCIATED_ALLOCATOR_HPP diff --git a/third_party/asio/include/asio/associated_cancellation_slot.hpp b/third_party/asio/include/asio/associated_cancellation_slot.hpp new file mode 100644 index 0000000..69206f8 --- /dev/null +++ b/third_party/asio/include/asio/associated_cancellation_slot.hpp @@ -0,0 +1,223 @@ +// +// associated_cancellation_slot.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2026 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_ASSOCIATED_CANCELLATION_SLOT_HPP +#define ASIO_ASSOCIATED_CANCELLATION_SLOT_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/config.hpp" +#include "asio/associator.hpp" +#include "asio/cancellation_signal.hpp" +#include "asio/detail/functional.hpp" +#include "asio/detail/type_traits.hpp" + +#include "asio/detail/push_options.hpp" + +namespace asio { +ASIO_INLINE_NAMESPACE_BEGIN + +template +struct associated_cancellation_slot; + +namespace detail { + +template +struct has_cancellation_slot_type : false_type +{ +}; + +template +struct has_cancellation_slot_type> + : true_type +{ +}; + +template +struct associated_cancellation_slot_impl +{ + typedef void asio_associated_cancellation_slot_is_unspecialised; + + typedef S type; + + static type get(const T&) noexcept + { + return type(); + } + + static const type& get(const T&, const S& s) noexcept + { + return s; + } +}; + +template +struct associated_cancellation_slot_impl> +{ + typedef typename T::cancellation_slot_type type; + + static auto get(const T& t) noexcept + -> decltype(t.get_cancellation_slot()) + { + return t.get_cancellation_slot(); + } + + static auto get(const T& t, const S&) noexcept + -> decltype(t.get_cancellation_slot()) + { + return t.get_cancellation_slot(); + } +}; + +template +struct associated_cancellation_slot_impl::value + >, + void_t< + typename associator::type + >> : associator +{ +}; + +} // namespace detail + +/// Traits type used to obtain the cancellation_slot associated with an object. +/** + * A program may specialise this traits type if the @c T template parameter in + * the specialisation is a user-defined type. The template parameter @c + * CancellationSlot shall be a type meeting the CancellationSlot requirements. + * + * Specialisations shall meet the following requirements, where @c t is a const + * reference to an object of type @c T, and @c s is an object of type @c + * CancellationSlot. + * + * @li Provide a nested typedef @c type that identifies a type meeting the + * CancellationSlot requirements. + * + * @li Provide a noexcept static member function named @c get, callable as @c + * get(t) and with return type @c type or a (possibly const) reference to @c + * type. + * + * @li Provide a noexcept static member function named @c get, callable as @c + * get(t,s) and with return type @c type or a (possibly const) reference to @c + * type. + */ +template +struct associated_cancellation_slot +#if !defined(GENERATING_DOCUMENTATION) + : detail::associated_cancellation_slot_impl +#endif // !defined(GENERATING_DOCUMENTATION) +{ +#if defined(GENERATING_DOCUMENTATION) + /// If @c T has a nested type @c cancellation_slot_type, + /// T::cancellation_slot_type. Otherwise + /// @c CancellationSlot. + typedef see_below type; + + /// If @c T has a nested type @c cancellation_slot_type, returns + /// t.get_cancellation_slot(). Otherwise returns @c type(). + static decltype(auto) get(const T& t) noexcept; + + /// If @c T has a nested type @c cancellation_slot_type, returns + /// t.get_cancellation_slot(). Otherwise returns @c s. + static decltype(auto) get(const T& t, + const CancellationSlot& s) noexcept; +#endif // defined(GENERATING_DOCUMENTATION) +}; + +/// Helper function to obtain an object's associated cancellation_slot. +/** + * @returns associated_cancellation_slot::get(t) + */ +template +ASIO_NODISCARD inline typename associated_cancellation_slot::type +get_associated_cancellation_slot(const T& t) noexcept +{ + return associated_cancellation_slot::get(t); +} + +/// Helper function to obtain an object's associated cancellation_slot. +/** + * @returns associated_cancellation_slot::get(t, st) + */ +template +ASIO_NODISCARD inline auto get_associated_cancellation_slot( + const T& t, const CancellationSlot& st) noexcept + -> decltype(associated_cancellation_slot::get(t, st)) +{ + return associated_cancellation_slot::get(t, st); +} + +template +using associated_cancellation_slot_t = + typename associated_cancellation_slot::type; + +namespace detail { + +template +struct associated_cancellation_slot_forwarding_base +{ +}; + +template +struct associated_cancellation_slot_forwarding_base::asio_associated_cancellation_slot_is_unspecialised, + void + >::value + >> +{ + typedef void asio_associated_cancellation_slot_is_unspecialised; +}; + +} // namespace detail + +/// Specialisation of associated_cancellation_slot for @c +/// std::reference_wrapper. +template +struct associated_cancellation_slot, CancellationSlot> +#if !defined(GENERATING_DOCUMENTATION) + : detail::associated_cancellation_slot_forwarding_base +#endif // !defined(GENERATING_DOCUMENTATION) +{ + /// Forwards @c type to the associator specialisation for the unwrapped type + /// @c T. + typedef typename associated_cancellation_slot::type type; + + /// Forwards the request to get the cancellation slot to the associator + /// specialisation for the unwrapped type @c T. + static type get(reference_wrapper t) noexcept + { + return associated_cancellation_slot::get(t.get()); + } + + /// Forwards the request to get the cancellation slot to the associator + /// specialisation for the unwrapped type @c T. + static auto get(reference_wrapper t, const CancellationSlot& s) noexcept + -> decltype( + associated_cancellation_slot::get(t.get(), s)) + { + return associated_cancellation_slot::get(t.get(), s); + } +}; + +ASIO_INLINE_NAMESPACE_END +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_ASSOCIATED_CANCELLATION_SLOT_HPP diff --git a/third_party/asio/include/asio/associated_executor.hpp b/third_party/asio/include/asio/associated_executor.hpp new file mode 100644 index 0000000..6ddc88c --- /dev/null +++ b/third_party/asio/include/asio/associated_executor.hpp @@ -0,0 +1,238 @@ +// +// associated_executor.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2026 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_ASSOCIATED_EXECUTOR_HPP +#define ASIO_ASSOCIATED_EXECUTOR_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/config.hpp" +#include "asio/associator.hpp" +#include "asio/detail/functional.hpp" +#include "asio/detail/type_traits.hpp" +#include "asio/execution/executor.hpp" +#include "asio/execution_context.hpp" +#include "asio/inline_executor.hpp" +#include "asio/is_executor.hpp" + +#include "asio/detail/push_options.hpp" + +namespace asio { +ASIO_INLINE_NAMESPACE_BEGIN + +template +struct associated_executor; + +namespace detail { + +template +struct has_executor_type : false_type +{ +}; + +template +struct has_executor_type> + : true_type +{ +}; + +template +struct associated_executor_impl +{ + typedef void asio_associated_executor_is_unspecialised; + + typedef E type; + + static type get(const T&) noexcept + { + return type(); + } + + static const type& get(const T&, const E& e) noexcept + { + return e; + } +}; + +template +struct associated_executor_impl> +{ + typedef typename T::executor_type type; + + static auto get(const T& t) noexcept + -> decltype(t.get_executor()) + { + return t.get_executor(); + } + + static auto get(const T& t, const E&) noexcept + -> decltype(t.get_executor()) + { + return t.get_executor(); + } +}; + +template +struct associated_executor_impl::value + >, + void_t< + typename associator::type + >> : associator +{ +}; + +} // namespace detail + +/// Traits type used to obtain the executor associated with an object. +/** + * A program may specialise this traits type if the @c T template parameter in + * the specialisation is a user-defined type. The template parameter @c + * Executor shall be a type meeting the Executor requirements. + * + * Specialisations shall meet the following requirements, where @c t is a const + * reference to an object of type @c T, and @c e is an object of type @c + * Executor. + * + * @li Provide a nested typedef @c type that identifies a type meeting the + * Executor requirements. + * + * @li Provide a noexcept static member function named @c get, callable as @c + * get(t) and with return type @c type or a (possibly const) reference to @c + * type. + * + * @li Provide a noexcept static member function named @c get, callable as @c + * get(t,e) and with return type @c type or a (possibly const) reference to @c + * type. + */ +template +struct associated_executor +#if !defined(GENERATING_DOCUMENTATION) + : detail::associated_executor_impl +#endif // !defined(GENERATING_DOCUMENTATION) +{ +#if defined(GENERATING_DOCUMENTATION) + /// If @c T has a nested type @c executor_type, T::executor_type. + /// Otherwise @c Executor. + typedef see_below type; + + /// If @c T has a nested type @c executor_type, returns + /// t.get_executor(). Otherwise returns @c type(). + static decltype(auto) get(const T& t) noexcept; + + /// If @c T has a nested type @c executor_type, returns + /// t.get_executor(). Otherwise returns @c ex. + static decltype(auto) get(const T& t, const Executor& ex) noexcept; +#endif // defined(GENERATING_DOCUMENTATION) +}; + +/// Helper function to obtain an object's associated executor. +/** + * @returns associated_executor::get(t) + */ +template +ASIO_NODISCARD inline typename associated_executor::type +get_associated_executor(const T& t) noexcept +{ + return associated_executor::get(t); +} + +/// Helper function to obtain an object's associated executor. +/** + * @returns associated_executor::get(t, ex) + */ +template +ASIO_NODISCARD inline auto get_associated_executor( + const T& t, const Executor& ex, + constraint_t< + is_executor::value || execution::is_executor::value + > = 0) noexcept + -> decltype(associated_executor::get(t, ex)) +{ + return associated_executor::get(t, ex); +} + +/// Helper function to obtain an object's associated executor. +/** + * @returns associated_executor::get(t, ctx.get_executor()) + */ +template +ASIO_NODISCARD inline typename associated_executor::type +get_associated_executor(const T& t, ExecutionContext& ctx, + constraint_t::value> = 0) noexcept +{ + return associated_executor::get(t, ctx.get_executor()); +} + +template +using associated_executor_t = typename associated_executor::type; + +namespace detail { + +template +struct associated_executor_forwarding_base +{ +}; + +template +struct associated_executor_forwarding_base::asio_associated_executor_is_unspecialised, + void + >::value + >> +{ + typedef void asio_associated_executor_is_unspecialised; +}; + +} // namespace detail + +/// Specialisation of associated_executor for @c std::reference_wrapper. +template +struct associated_executor, Executor> +#if !defined(GENERATING_DOCUMENTATION) + : detail::associated_executor_forwarding_base +#endif // !defined(GENERATING_DOCUMENTATION) +{ + /// Forwards @c type to the associator specialisation for the unwrapped type + /// @c T. + typedef typename associated_executor::type type; + + /// Forwards the request to get the executor to the associator specialisation + /// for the unwrapped type @c T. + static type get(reference_wrapper t) noexcept + { + return associated_executor::get(t.get()); + } + + /// Forwards the request to get the executor to the associator specialisation + /// for the unwrapped type @c T. + static auto get(reference_wrapper t, const Executor& ex) noexcept + -> decltype(associated_executor::get(t.get(), ex)) + { + return associated_executor::get(t.get(), ex); + } +}; + +ASIO_INLINE_NAMESPACE_END +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_ASSOCIATED_EXECUTOR_HPP diff --git a/third_party/asio/include/asio/associated_immediate_executor.hpp b/third_party/asio/include/asio/associated_immediate_executor.hpp new file mode 100644 index 0000000..00a95bc --- /dev/null +++ b/third_party/asio/include/asio/associated_immediate_executor.hpp @@ -0,0 +1,283 @@ +// +// associated_immediate_executor.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2026 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_ASSOCIATED_IMMEDIATE_EXECUTOR_HPP +#define ASIO_ASSOCIATED_IMMEDIATE_EXECUTOR_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/config.hpp" +#include "asio/associator.hpp" +#include "asio/detail/functional.hpp" +#include "asio/detail/type_traits.hpp" +#include "asio/execution/blocking.hpp" +#include "asio/execution/executor.hpp" +#include "asio/execution_context.hpp" +#include "asio/is_executor.hpp" +#include "asio/require.hpp" + +#include "asio/detail/push_options.hpp" + +namespace asio { +ASIO_INLINE_NAMESPACE_BEGIN + +template +struct associated_immediate_executor; + +namespace detail { + +template +struct has_immediate_executor_type : false_type +{ +}; + +template +struct has_immediate_executor_type> + : true_type +{ +}; + +template +struct default_immediate_executor +{ + typedef decay_t> type; + + static auto get(const E& e) noexcept + -> decltype(asio::require(e, execution::blocking.never)) + { + return asio::require(e, execution::blocking.never); + } +}; + +template +struct default_immediate_executor::value + >, + enable_if_t< + is_executor::value + >> +{ + class type : public E + { + public: + template + explicit type(const Executor1& e, + constraint_t< + conditional_t< + !is_same::value, + is_convertible, + false_type + >::value + > = 0) noexcept + : E(e) + { + } + + type(const type& other) noexcept + : E(static_cast(other)) + { + } + + type(type&& other) noexcept + : E(static_cast(other)) + { + } + + template + void dispatch(Function&& f, const Allocator& a) const + { + this->post(static_cast(f), a); + } + + friend bool operator==(const type& a, const type& b) noexcept + { + return static_cast(a) == static_cast(b); + } + + friend bool operator!=(const type& a, const type& b) noexcept + { + return static_cast(a) != static_cast(b); + } + }; + + static type get(const E& e) noexcept + { + return type(e); + } +}; + +template +struct associated_immediate_executor_impl +{ + typedef void asio_associated_immediate_executor_is_unspecialised; + + typedef typename default_immediate_executor::type type; + + static auto get(const T&, const E& e) noexcept + -> decltype(default_immediate_executor::get(e)) + { + return default_immediate_executor::get(e); + } +}; + +template +struct associated_immediate_executor_impl> +{ + typedef typename T::immediate_executor_type type; + + static auto get(const T& t, const E&) noexcept + -> decltype(t.get_immediate_executor()) + { + return t.get_immediate_executor(); + } +}; + +template +struct associated_immediate_executor_impl::value + >, + void_t< + typename associator::type + >> : associator +{ +}; + +} // namespace detail + +/// Traits type used to obtain the immediate executor associated with an object. +/** + * A program may specialise this traits type if the @c T template parameter in + * the specialisation is a user-defined type. The template parameter @c + * Executor shall be a type meeting the Executor requirements. + * + * Specialisations shall meet the following requirements, where @c t is a const + * reference to an object of type @c T, and @c e is an object of type @c + * Executor. + * + * @li Provide a nested typedef @c type that identifies a type meeting the + * Executor requirements. + * + * @li Provide a noexcept static member function named @c get, callable as @c + * get(t) and with return type @c type or a (possibly const) reference to @c + * type. + * + * @li Provide a noexcept static member function named @c get, callable as @c + * get(t,e) and with return type @c type or a (possibly const) reference to @c + * type. + */ +template +struct associated_immediate_executor +#if !defined(GENERATING_DOCUMENTATION) + : detail::associated_immediate_executor_impl +#endif // !defined(GENERATING_DOCUMENTATION) +{ +#if defined(GENERATING_DOCUMENTATION) + /// If @c T has a nested type @c immediate_executor_type, + // T::immediate_executor_type. Otherwise @c Executor. + typedef see_below type; + + /// If @c T has a nested type @c immediate_executor_type, returns + /// t.get_immediate_executor(). Otherwise returns + /// asio::require(ex, asio::execution::blocking.never). + static decltype(auto) get(const T& t, const Executor& ex) noexcept; +#endif // defined(GENERATING_DOCUMENTATION) +}; + +/// Helper function to obtain an object's associated executor. +/** + * @returns associated_immediate_executor::get(t, ex) + */ +template +ASIO_NODISCARD inline auto get_associated_immediate_executor( + const T& t, const Executor& ex, + constraint_t< + is_executor::value || execution::is_executor::value + > = 0) noexcept + -> decltype(associated_immediate_executor::get(t, ex)) +{ + return associated_immediate_executor::get(t, ex); +} + +/// Helper function to obtain an object's associated executor. +/** + * @returns associated_immediate_executor::get(t, ctx.get_executor()) + */ +template +ASIO_NODISCARD inline typename associated_immediate_executor::type +get_associated_immediate_executor(const T& t, ExecutionContext& ctx, + constraint_t< + is_convertible::value + > = 0) noexcept +{ + return associated_immediate_executor::get(t, ctx.get_executor()); +} + +template +using associated_immediate_executor_t = + typename associated_immediate_executor::type; + +namespace detail { + +template +struct associated_immediate_executor_forwarding_base +{ +}; + +template +struct associated_immediate_executor_forwarding_base::asio_associated_immediate_executor_is_unspecialised, + void + >::value + >> +{ + typedef void asio_associated_immediate_executor_is_unspecialised; +}; + +} // namespace detail + +/// Specialisation of associated_immediate_executor for +/// @c std::reference_wrapper. +template +struct associated_immediate_executor, Executor> +#if !defined(GENERATING_DOCUMENTATION) + : detail::associated_immediate_executor_forwarding_base +#endif // !defined(GENERATING_DOCUMENTATION) +{ + /// Forwards @c type to the associator specialisation for the unwrapped type + /// @c T. + typedef typename associated_immediate_executor::type type; + + /// Forwards the request to get the executor to the associator specialisation + /// for the unwrapped type @c T. + static auto get(reference_wrapper t, const Executor& ex) noexcept + -> decltype(associated_immediate_executor::get(t.get(), ex)) + { + return associated_immediate_executor::get(t.get(), ex); + } +}; + +ASIO_INLINE_NAMESPACE_END +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_ASSOCIATED_IMMEDIATE_EXECUTOR_HPP diff --git a/third_party/asio/include/asio/associator.hpp b/third_party/asio/include/asio/associator.hpp new file mode 100644 index 0000000..2006c28 --- /dev/null +++ b/third_party/asio/include/asio/associator.hpp @@ -0,0 +1,37 @@ +// +// associator.hpp +// ~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2026 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_ASSOCIATOR_HPP +#define ASIO_ASSOCIATOR_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/config.hpp" + +#include "asio/detail/push_options.hpp" + +namespace asio { +ASIO_INLINE_NAMESPACE_BEGIN + +/// Used to generically specialise associators for a type. +template