35 Commits

Author SHA1 Message Date
d31ec68f3c feat(packet): implement zstd compression and refactor packet decoding 2026-06-26 20:20:34 +08:00
c7c6ee9a61 refactor(network): extract packet header decoding into function 2026-06-26 19:20:14 +08:00
db7b67f265 refactor(packet): replace zlib with zstd compression and restructure header
Increase header length to 12 bytes, add CompressType and PacketHeader struct, and implement decode_packet_header. Update CMake to find zstd and link against it, adding Findzstd module.
2026-06-26 19:12:39 +08:00
7e42f595c9 build: add ZLIB dependency 2026-06-26 17:23:35 +08:00
11c902d2bf fix(dev_panel): add protobuf to about table libraries 2026-06-26 17:18:05 +08:00
e5e84848a9 feat(gameplay): add player logout and disconnect handling 2026-06-26 17:05:09 +08:00
25e9b0e0d3 feat(gameplay): add thread pool for asynchronous chunk data processing 2026-06-26 16:14:20 +08:00
48300b5fe2 feat(multiplayer): add rendering of other players as colored cubes 2026-06-26 15:52:22 +08:00
3c14a4ffe1 feat(app): add client/server CLI arguments and init guards 2026-06-26 13:48:07 +08:00
fbb6fe2fa6 feat(client): add center-based chunk loading priority 2026-06-26 11:32:52 +08:00
a858774b71 refactor(client_world): split pending queue into data and upload queues 2026-06-26 11:20:47 +08:00
4631bbe6d6 feat(gameplay): optimize chunk request with player chunk tracking
Add `update_player_chunk()` method to `ClientPlayer` that triggers a chunk request when the player moves more than 2 chunks away from the last recorded chunk. Introduce an atomic `m_requesting_chunk` flag in `ClientWorld` to prevent concurrent requests. Rename `m_player_chunk_pos` to `m_last_chunk_pos` for clarity.
2026-06-25 21:57:55 +08:00
db10d30dc2 fix: correct neighbor block indexing and add time update support 2026-06-25 21:37:19 +08:00
47552a1887 feat(gameplay): add time sync and network improvements 2026-06-25 20:13:51 +08:00
d7d47e9e7f refactor(gameplay): split world into client and server
Remove monolithic World, Player, Chunk classes. Introduce ClientWorld, ServerWorld, ClientPlayer, and related networked components. Add Abseil dependency for logging and checks. Rename old files to pre_remove_* and update all includes and references accordingly.
2026-06-25 17:54:32 +08:00
2c6dccfb53 refactor(server): replace Chunk with ServerChunk 2026-06-25 17:13:50 +08:00
b782b40133 feat(gameplay): add block change sync and coordinate utils 2026-06-25 17:09:41 +08:00
4a1ef54258 feat(gameplay): integrate network client and thread-safe player pos 2026-06-25 16:49:45 +08:00
cbc04f6ab0 feat(client_player): add name constructor param, remove chunk transition 2026-06-25 14:38:57 +08:00
df2e26e731 feat(client-chunk): add greedy meshing, receive_chunk, and biome field 2026-06-25 14:31:11 +08:00
c92e2249a4 feat(client): add client-side chunk, player, world and network classes 2026-06-24 21:49:30 +08:00
4e6f29bedd fix(gameplay): correct unknown error log condition and missing chunk pos update 2026-06-24 18:50:02 +08:00
0223fefa6b fix(network-server): stop accept loop on shutdown flag 2026-06-24 18:03:40 +08:00
8d86a71c3f fix(session): catch std::exception in read_loop and log error 2026-06-24 17:59:32 +08:00
af8812916d refactor(server): enhance thread safety and session management 2026-06-24 17:56:11 +08:00
85341651b3 feat(server): add block change packet handling and increase reserved threads 2026-06-24 17:02:21 +08:00
9d33b240c6 feat(world): add player-based chunk loading and UUID support
Restructure world generation to trigger chunk loading based on player
movement. Replace player name with UUID for identification. Implement
chunk request/response protocol. Improve thread management for gen
thread.
2026-06-24 14:51:48 +08:00
6f39ff771d refactor(packet): serialize directly into packet buffer with ByteSizeLong 2026-06-23 22:00:19 +08:00
2953e8a233 feat(protocol): add packet serialization and login handling
Introduce packet header and ID mapping for protobuf messages.
Refactor session and server_world to use new packet wrapper.
Fix missing semicolons in proto files.
2026-06-23 21:58:35 +08:00
678fc02998 refactor(proto): restructure protobuf definitions and update build system 2026-06-23 21:10:40 +08:00
4303c1cd32 feat(network): integrate protobuf for player sync and session management
Add Protobuf dependency, define proto messages for player requests, positions, and chunk data. Refactor Session to use strand and async write. Implement player join/exit and position sync in ServerWorld.
2026-06-23 20:08:40 +08:00
1acfeef9e6 feat(gameplay): add Session class and UUID generation utility 2026-06-23 15:54:01 +08:00
27e3cd6851 refactor(gameplay): split Chunk into server/client variants and add networking 2026-06-23 14:49:16 +08:00
d4a25bdf4b feat(dev_panel): add Asio to about table 2026-06-23 10:49:25 +08:00
7c07110afa build: add asio library 2026-06-23 10:42:13 +08:00
52 changed files with 3582 additions and 2186 deletions

View File

@@ -1,3 +1,4 @@
# .github/workflows/format-check.yml
name: Code Format Check
on: [push, pull_request]
@@ -8,22 +9,9 @@ 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 '*.hpp' \) \
-print0 | xargs -0 clang-format-22 --dry-run --Werror
find src include -name '*.cpp' -o -name '*.h' -print0 | xargs -0 clang-format --dry-run --Werror

View File

@@ -1,154 +0,0 @@
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: Bootstrap vcpkg
run: |
git clone https://github.com/microsoft/vcpkg.git
./vcpkg/bootstrap-vcpkg.bat
- name: Configure
if: matrix.platform == 'windows'
run: >
cmake
-B build
-G Ninja
-DCMAKE_BUILD_TYPE=Release
-DCMAKE_TOOLCHAIN_FILE=${{ github.workspace }}/vcpkg/scripts/buildsystems/vcpkg.cmake
-DVCPKG_TARGET_TRIPLET=x64-windows-release
-DVCPKG_BUILD_TYPE=release
-DBUILD_TESTING=OFF
"-DCUBED_VERSION=${{ steps.version.outputs.version }}"
- 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 }}

3
.gitignore vendored
View File

@@ -42,5 +42,4 @@ CMakeError.log
.DS_Store
assets/config.toml
.venv/
pyout/
vcpkg_installed/
pyout/

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.21)
cmake_minimum_required(VERSION 3.14...3.24)
project(Cubed LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 23)
@@ -6,119 +6,194 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
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")
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
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)
list(APPEND CMAKE_MODULE_PATH
"${CMAKE_CURRENT_SOURCE_DIR}/cmake"
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules"
if(MSVC)
add_compile_options(/utf-8)
endif()
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules")
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(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
)
include(Dependencies)
add_subdirectory(third_party/glad)
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)
add_subdirectory(third_party/imgui)
add_executable(${PROJECT_NAME})
add_subdirectory(src)
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_generator.cpp
src/gameplay/tree.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
src/gameplay/network_server.cpp
src/gameplay/server_world.cpp
src/gameplay/client_world.cpp
src/gameplay/server_chunk.cpp
src/gameplay/client_chunk.cpp
src/gameplay/server_player.cpp
src/gameplay/client_player.cpp
src/gameplay/session.cpp
src/gameplay/network_client.cpp
)
file(GLOB_RECURSE PROTO_FILES
${CMAKE_CURRENT_SOURCE_DIR}/src/proto/*.proto
)
protobuf_generate(
TARGET ${PROJECT_NAME}
LANGUAGE cpp
PROTOS ${PROTO_FILES}
IMPORT_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/src/proto
)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
message(STATUS "Building with AddressSanitizer enabled for target: ${PROJECT_NAME}")
configure_file(
src/version.hpp.in
${CMAKE_BINARY_DIR}/generated/version.hpp
@ONLY
)
target_compile_options(${PROJECT_NAME} PRIVATE
#-fsanitize=address
#-fsanitize=thread
-fno-omit-frame-pointer
-g
)
target_compile_options(${PROJECT_NAME}
PRIVATE
$<$<CONFIG:Debug>:-fno-omit-frame-pointer>
$<$<CONFIG:Debug>:-g>
#$<$<CONFIG:Debug>:-fsanitize=address>
#$<$<CONFIG:Debug>:-fsanitize=thread>
target_link_options(${PROJECT_NAME} PRIVATE
#-fsanitize=address
#-fsanitize=thread
)
$<$<CXX_COMPILER_ID:GNU,Clang>:-Wall>
$<$<CXX_COMPILER_ID:GNU,Clang>:-Wextra>
$<$<CXX_COMPILER_ID:GNU,Clang>:-Wpedantic>
$<$<CXX_COMPILER_ID:MSVC>:/utf-8>
$<$<CXX_COMPILER_ID:MSVC>:/W4>
)
target_link_options(${PROJECT_NAME}
PRIVATE
#$<$<CONFIG:Debug>:-fsanitize=address>
#$<$<CONFIG:Debug>:-fsanitize=thread>
)
target_compile_definitions(${PROJECT_NAME}
PRIVATE
ASIO_STANDALONE
ASIO_NO_DEPRECATED
$<$<CONFIG:Debug>:DEBUG_MODE>
$<$<CXX_COMPILER_ID:MSVC>:
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="$<$<CONFIG:Debug>:${PROJECT_SOURCE_DIR}/assets/>$<$<NOT:$<CONFIG:Debug>>:./assets/>"
target_compile_definitions(${PROJECT_NAME} PRIVATE DEBUG_MODE)
target_compile_definitions(${PROJECT_NAME} PRIVATE
ASSETS_PATH="${CMAKE_SOURCE_DIR}/assets/"
)
else()
# 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()
target_compile_definitions(${PROJECT_NAME} PRIVATE
ASSETS_PATH="./assets/"
)
endif()
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_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/include)
target_include_directories(${PROJECT_NAME} PRIVATE
${CMAKE_SOURCE_DIR}/third_party/asio/include
)
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/src)
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_compile_definitions(${PROJECT_NAME} PRIVATE
ASIO_STANDALONE
ASIO_NO_DEPRECATED
)
target_link_libraries(${PROJECT_NAME}
PRIVATE
glfw
@@ -133,21 +208,43 @@ target_link_libraries(${PROJECT_NAME}
protobuf::libprotobuf
absl::log
absl::check
absl::base
absl::strings
absl::flat_hash_map
zstd::zstd
$<$<PLATFORM_ID:Windows>:ws2_32>
)
if(WIN32)
add_custom_command(
TARGET ${PROJECT_NAME}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_RUNTIME_DLLS:${PROJECT_NAME}>
$<TARGET_FILE_DIR:${PROJECT_NAME}>
COMMAND_EXPAND_LISTS
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}
)
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
$<TARGET_FILE:${TBB_LIB}>
$<TARGET_FILE_DIR:${PROJECT_NAME}>
COMMENT "Copying ${TBB_LIB}.dll"
)
else()
message(STATUS "Target ${TBB_LIB} not found, skipping copy")
endif()
endforeach()
endif()

View File

@@ -1,79 +0,0 @@
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()

View File

@@ -16,7 +16,6 @@ public:
bool is_client = false;
int port = 25530;
std::string ip{"127.0.0.1"};
std::string player{"Unknown"};
};
App();
@@ -47,7 +46,6 @@ public:
Window& window();
ClientWorld& client_world();
ServerWorld& server_world();
const Argument& argument() const;
private:
Camera m_camera;
@@ -71,7 +69,6 @@ private:
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();

View File

@@ -1,9 +1,17 @@
#pragma once
#include "Cubed/tools/cubed_assert.hpp"
#include "Cubed/tools/toml.utils.hpp"
#include <toml++/toml.hpp>
namespace Cubed {
template <typename T>
concept TomlValueType =
std::same_as<T, int> || std::same_as<T, bool> || std::same_as<T, double> ||
std::same_as<T, const char*> || std::same_as<T, toml::date> ||
std::same_as<T, toml::time> || std::same_as<T, toml::date_time> ||
std::same_as<T, std::string>;
class Config {
public:
Config();
@@ -16,7 +24,7 @@ public:
void load_or_create_config();
void save_to_file();
template <TOML::TomlValueType T> T get(std::string_view key) const {
template <TomlValueType T> T get(std::string_view key) const {
size_t cur = 0;
auto pos = key.find('.');
const toml::table* table = &m_tbl;
@@ -53,7 +61,7 @@ public:
}
}
template <typename T> void set(std::string_view key, T&& val) {
if constexpr (!TOML::TomlValueType<std::decay_t<T>>) {
if constexpr (!TomlValueType<std::decay_t<T>>) {
static_assert(false, "Type Not Support");
}
size_t cur = 0;

View File

@@ -26,6 +26,9 @@ class DevPanel {
int gait = 0;
float pos[3] = {0.0f, 0.0f, 0.0f};
};
struct TextEditing {
bool perlin_seed = false;
};
public:
DevPanel(App& app);
@@ -37,6 +40,7 @@ private:
ConfigView m_config;
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;
@@ -54,8 +58,6 @@ 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();

View File

@@ -51,7 +51,6 @@ public:
const std::vector<BlockType>& get_chunk_blocks() const;
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;
@@ -83,7 +82,6 @@ public:
std::vector<BlockType>& blocks();
ClientWorld& world();
unsigned seed() const;
const ChunkRenderSnapshot* get_render_snapshot() const;
private:
struct FaceKey {
@@ -119,9 +117,6 @@ private:
4 - water
*/
std::vector<VertexData> m_vertex_data;
ChunkRenderSnapshot m_render_snapshot;
unsigned m_seed = 0;
void clear_dirty();
void gen_vertices(const OptionalBlockVectorArray& neighbor_block);

View File

@@ -6,7 +6,6 @@
#include "Cubed/gameplay/game_mode.hpp"
#include "Cubed/input.hpp"
#include <absl/container/flat_hash_set.h>
#include <glm/glm.hpp>
#include <optional>
#include <shared_mutex>
@@ -15,15 +14,9 @@ enum class Gait { WALK = 0, RUN };
class ClientWorld;
class ClientPlayer {
public:
using ChunkPosSet = absl::flat_hash_set<ChunkPos, ChunkPos::Hash>;
ClientPlayer(ClientWorld& world);
~ClientPlayer();
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);
AABB get_aabb(const glm::vec3& pos) const;
const glm::vec3& get_front() const;
const Gait& get_gait() const;
const std::optional<LookBlock>& get_look_block_pos() const;
@@ -97,7 +90,7 @@ private:
glm::vec3 m_front{0, 0, -1};
glm::vec3 m_right{0, 0, 0};
static constexpr glm::vec3 M_SIZE{0.6f, 1.8f, 0.6f};
glm::vec3 m_size{0.6f, 1.8f, 0.6f};
Gait m_gait = Gait::WALK;
MoveState m_move_state{};
@@ -108,8 +101,7 @@ private:
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);

View File

@@ -5,24 +5,20 @@
#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 "Cubed/tools/thread_pool.hpp"
#include <absl/container/flat_hash_set.h>
#include <deque>
#include <tbb/concurrent_hash_map.h>
#include <tbb/concurrent_queue.h>
#include <tbb/concurrent_unordered_map.h>
namespace Cubed {
struct RemotePlayerInfo {
struct OtherPlayerInfo {
std::string name;
glm::vec3 render_pos;
glm::vec3 target_pos;
glm::vec3 pos;
};
struct RemotePlayerRenderData {
struct RenderPlayerData {
std::string name;
glm::vec3 render_pos;
glm::vec3 pos;
};
class ClientWorld {
@@ -32,15 +28,14 @@ public:
void init(std::string_view player_name,
std::shared_ptr<NetworkClient> client);
void update(float delta_time);
const std::optional<LookBlock>& get_look_block_pos() const;
const std::optional<LookBlock>&
get_look_block_pos(const std::string& name) 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();
@@ -50,11 +45,10 @@ public:
void receive_block_change(const BlockChangeRsp& rsp);
void receive_time(const UpdateTime& rsp);
void receive_remote_player(const PlayerInfoRsp& rsp);
void receive_other_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();
@@ -64,14 +58,11 @@ public:
void hot_reload();
void request_chunk();
std::vector<glm::vec4>& planes();
const std::vector<const ChunkRenderSnapshot*>& render_snapshots() const;
const std::vector<RemotePlayerRenderData>& render_player_data() const;
const std::vector<ChunkRenderSnapshot>& render_snapshots() const;
const std::vector<RenderPlayerData>& render_player_data() const;
glm::vec3 sunlight_dir() const;
void receive_chunk(std::vector<uint8_t> data, PacketHeader header);
void request_exit();
bool is_receive_exit();
int chunk_size() const;
static AABB get_block_aabb(const glm::ivec3& pos);
void receive_chunk(const ChunkDataRsp& data);
void exit();
template <typename Fn>
void register_timer(std::string_view id, TickType threshold, Fn&& f) {
m_timers.emplace(std::piecewise_construct,
@@ -82,48 +73,40 @@ public:
private:
enum class ChunkLoadStyle { RANDOM, CENTER };
using ChunkHashMap =
tbb::concurrent_hash_map<ChunkPos, std::shared_ptr<ClientChunk>,
ChunkPos::TBBHash>;
using ChunkPosSet = absl::flat_hash_set<ChunkPos, ChunkPos::Hash>;
tbb::concurrent_unordered_map<ChunkPos, ClientChunk, ChunkPos::Hash>;
using ChunkPosSet = std::unordered_set<ChunkPos, ChunkPos::Hash>;
using ChunkPosVector = std::vector<ChunkPos>;
using OtherPlayerHashMap =
std::unordered_map<std::string, RemotePlayerInfo>;
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;
using OtherPlayerHashMap = std::unordered_map<std::string, OtherPlayerInfo>;
ClientPlayer m_player;
OtherPlayerHashMap m_other_players;
ChunkHashMap m_chunks;
std::vector<glm::vec4> m_planes;
std::jthread m_client_thread;
mutable std::shared_mutex m_chunks_mutex;
std::mutex m_delete_vbo_mutex;
std::mutex m_delete_vao_mutex;
mutable std::shared_mutex m_other_players_mutex;
std::mutex m_pending_upload_queue_mutex;
std::mutex m_other_players_mutex;
tbb::concurrent_queue<std::unique_ptr<ClientChunk>> m_pending_upload_queue;
tbb::concurrent_queue<ChunkPos> m_dirty_chunk_queue;
std::deque<ClientChunk> m_pending_upload_queue;
std::vector<GLuint> m_pending_delete_vbo;
std::vector<GLuint> m_pending_delete_vao;
std::deque<ChunkPos> m_dirty_queue;
std::vector<const ChunkRenderSnapshot*> m_render_snapshots;
std::vector<RemotePlayerRenderData> m_render_player_data;
std::vector<ChunkRenderSnapshot> m_render_snapshots;
std::vector<RenderPlayerData> m_render_player_data;
tbb::concurrent_unordered_map<std::string, Timer> m_timers;
std::atomic<bool> m_game_running{false};
std::atomic<bool> m_receive_exit{false};
std::atomic<int> m_rendering_distance{24};
std::atomic<TickType> m_game_ticks{0};
std::atomic<TickType> m_day_tick{6000};
std::atomic<bool> m_requesting_chunk{false};
std::atomic<bool> m_is_rebuilding{false};
std::atomic<int> m_chunk_task_id{0};
std::shared_ptr<NetworkClient> m_client;
ChunkLoadStyle m_chunk_load_style{ChunkLoadStyle::CENTER};
std::atomic<std::shared_ptr<PriorityThreadPool>> m_thread_pool;
std::atomic<std::shared_ptr<ThreadPool>> m_thread_pool;
void client_run(std::stop_token token);
@@ -132,7 +115,5 @@ private:
void report_player_pos();
void set_block(const glm::ivec3& pos, unsigned id);
void update_chunk(const ChunkPosSet& old, const ChunkPosSet& now);
};
} // namespace Cubed

View File

@@ -3,7 +3,6 @@
#include "Cubed/gameplay/packet.hpp"
#include <asio.hpp>
#include <queue>
#include <string>
#include <thread>
namespace Cubed {
@@ -15,50 +14,28 @@ public:
~NetworkClient();
void close();
void stop();
void send(Packet packet, int priority = 10);
void send(Packet packet);
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<char> m_read_buffer;
std::priority_queue<Task, std::vector<Task>, TaskCompare> m_write_queue;
std::deque<Packet> m_write_queue;
asio::strand<asio::io_context::executor_type> m_strand;
asio::awaitable<void> connect(std::string ip, int port);
asio::awaitable<void> read_loop();
std::atomic<bool> m_closed{false};
std::atomic<bool> m_connected{false};
std::atomic<bool> m_connect_error{false};
// ClientWorld is managed by App
ClientWorld& m_world;
std::atomic_uint64_t m_sequence{0};
asio::awaitable<void> connect(std::string ip, int port);
asio::awaitable<void> read_loop();
void do_write();
};

View File

@@ -6,14 +6,7 @@
#include <cstdint>
#include <cstring>
#include <memory>
#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <winsock2.h>
#else
#include <netinet/in.h>
#endif
#include <span>
#include <stdexcept>
#include <type_traits>
@@ -30,10 +23,11 @@ enum class CompressType : uint16_t {
inline CompressType get_compress_type(uint16_t id) {
using enum CompressType;
constexpr auto& to = std::to_underlying<CompressType>;
switch (id) {
case std::to_underlying(NONE):
case to(NONE):
return NONE;
case std::to_underlying(ZSTD):
case to(ZSTD):
return ZSTD;
}
throw std::runtime_error(std::format("Unknown CompressType {}", id));
@@ -58,64 +52,50 @@ enum class PacketEnum : uint16_t {
CHUNK_DATA_RSP = 3002,
BLOCK_CHANGE_REQ = 3003,
BLOCK_CHANGE_RSP = 3004,
S2C_CLEAR_ALL_CHUNKS = 3005,
UPDATE_TIME = 3006,
UPDATE_TIME = 3005,
PING = 9001,
PONG = 9002
};
template <typename> struct always_false : std::false_type {}; // NOLINT
template <typename T> constexpr uint16_t get_packet_id() {
static_assert(always_false<T>::value, "Unknown Type");
return 0;
}
using enum PacketEnum;
using std::is_same_v;
template <> constexpr uint16_t get_packet_id<LoginReq>() {
return std::to_underlying(PacketEnum::LOGIN_REQ);
}
template <> constexpr uint16_t get_packet_id<LoginRsp>() {
return std::to_underlying(PacketEnum::LOGIN_RSP);
}
template <> constexpr uint16_t get_packet_id<LogoutReq>() {
return std::to_underlying(PacketEnum::LOGOUT_REQ);
}
template <> constexpr uint16_t get_packet_id<LogoutRsp>() {
return std::to_underlying(PacketEnum::LOGOUT_RSP);
}
template <> constexpr uint16_t get_packet_id<PlayerInfo>() {
return std::to_underlying(PacketEnum::PLAYER_INFO);
}
template <> constexpr uint16_t get_packet_id<PlayerPos>() {
return std::to_underlying(PacketEnum::PLAYER_POS);
}
template <> constexpr uint16_t get_packet_id<PlayerInfoRsp>() {
return std::to_underlying(PacketEnum::PLAYER_INFO_RSP);
}
template <> constexpr uint16_t get_packet_id<ChunkDataReq>() {
return std::to_underlying(PacketEnum::CHUNK_DATA_REQ);
}
template <> constexpr uint16_t get_packet_id<ChunkDataRsp>() {
return std::to_underlying(PacketEnum::CHUNK_DATA_RSP);
}
template <> constexpr uint16_t get_packet_id<BlockChangeReq>() {
return std::to_underlying(PacketEnum::BLOCK_CHANGE_REQ);
}
template <> constexpr uint16_t get_packet_id<BlockChangeRsp>() {
return std::to_underlying(PacketEnum::BLOCK_CHANGE_RSP);
}
template <> constexpr uint16_t get_packet_id<S2C_ClearAllChunks>() {
return std::to_underlying(PacketEnum::S2C_CLEAR_ALL_CHUNKS);
}
template <> constexpr uint16_t get_packet_id<UpdateTime>() {
return std::to_underlying(PacketEnum::UPDATE_TIME);
}
template <> constexpr uint16_t get_packet_id<Ping>() {
return std::to_underlying(PacketEnum::PING);
}
template <> constexpr uint16_t get_packet_id<Pong>() {
return std::to_underlying(PacketEnum::PONG);
using U = std::decay_t<T>;
constexpr auto& to_num = std::to_underlying<PacketEnum>;
if constexpr (is_same_v<U, LoginReq>) {
return to_num(LOGIN_REQ);
} else if constexpr (is_same_v<U, LoginRsp>) {
return to_num(LOGIN_RSP);
} else if constexpr (is_same_v<U, LogoutReq>) {
return to_num(LOGOUT_REQ);
} else if constexpr (is_same_v<U, LogoutRsp>) {
return to_num(LOGOUT_RSP);
} else if constexpr (is_same_v<U, PlayerInfo>) {
return to_num(PLAYER_INFO);
} else if constexpr (is_same_v<U, PlayerPos>) {
return to_num(PLAYER_POS);
} else if constexpr (is_same_v<U, PlayerInfoRsp>) {
return to_num(PLAYER_INFO_RSP);
} else if constexpr (is_same_v<U, ChunkDataReq>) {
return to_num(CHUNK_DATA_REQ);
} else if constexpr (is_same_v<U, ChunkDataRsp>) {
return to_num(CHUNK_DATA_RSP);
} else if constexpr (is_same_v<U, BlockChangeReq>) {
return to_num(BLOCK_CHANGE_REQ);
} else if constexpr (is_same_v<U, BlockChangeRsp>) {
return to_num(BLOCK_CHANGE_RSP);
} else if constexpr (is_same_v<U, UpdateTime>) {
return to_num(UPDATE_TIME);
} else if constexpr (is_same_v<U, Ping>) {
return to_num(PING);
} else if constexpr (is_same_v<U, Pong>) {
return to_num(PONG);
} else {
static_assert(always_false<U>::value, "Unknown Type");
}
}
template <typename T>

View File

@@ -0,0 +1,171 @@
#pragma once
#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 <atomic>
#include <mutex>
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 World;
// if want to use, do init_chunk(), gen_vertex_data() and
class Chunk {
private:
using OptionalBlockVectorArray =
std::array<std::optional<std::vector<BlockType>>, 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<bool> m_dirty{false};
std::atomic<bool> m_need_upload{true};
std::atomic<bool> m_is_on_gen_vertex_data{false};
std::atomic<bool> m_gening{false};
std::atomic<bool> m_temp_chunk{false};
bool m_has_cave{false};
std::atomic<BiomeType> m_biome = BiomeType::PLAIN;
std::mutex m_vertexs_data_mutex;
std::unique_ptr<ChunkGenerator> m_generator;
ChunkPos m_chunk_pos;
World& m_world;
HeightMapArray m_heightmap;
// the index is a array of block id
std::vector<BlockType> m_blocks;
/*
0 - normal
1 - cross_plane
2 - normal_discard
3 - transparent and blend
4 - water
*/
std::vector<VertexData> 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);
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;
static std::tuple<int, int, int> world_to_block(int world_x, int world_y,
int world_z, int chunk_x,
int chunk_z);
static std::tuple<int, int, int> world_to_block(const glm::ivec3& block_pos,
ChunkPos chunk_pos);
static std::tuple<int, int, int> block_to_world(int x, int y, int z,
int chunk_x, int chunk_z);
static std::tuple<int, int, int> block_to_world(const glm::ivec3& block_pos,
ChunkPos chunk_pos);
BiomeType get_biome() const;
ChunkPos get_chunk_pos() const;
const std::vector<BlockType>& 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<const Chunk*, 8>& 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<std::optional<HeightMapArray>, 8>& neighbor_heightmap,
const std::array<BiomeType, 8>& 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<std::optional<std::vector<BlockType>>,
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 gen_vertex_data(const OptionalBlockVectorArray& neighbor_block);
void upload_to_gpu();
GLuint get_normal_vao() const;
size_t get_normal_vertices_sum() const;
GLuint get_cross_vao() const;
size_t get_cross_vertices_sum() const;
GLuint get_normal_discard_vao() const;
size_t get_normal_discard_vertices_sum() const;
GLuint get_normal_blend_vao() const;
size_t get_normal_blend_vertices_sum() const;
GLuint get_water_vao() const;
size_t get_water_vertices_sum() const;
bool is_dirty() const;
void mark_dirty();
bool is_need_upload() const;
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<BlockType>& blocks();
World& world();
unsigned seed() const;
BiomeConditions& conditions();
ChunkInfo get_info() const;
bool& has_cave();
};
} // namespace Cubed

View File

@@ -0,0 +1,112 @@
#pragma once
#include "Cubed/AABB.hpp"
#include "Cubed/constants.hpp"
#include "Cubed/gameplay/block.hpp"
#include "Cubed/gameplay/chunk_pos.hpp"
#include "Cubed/gameplay/game_mode.hpp"
#include "Cubed/input.hpp"
#include <glm/glm.hpp>
#include <optional>
#include <string>
namespace Cubed {
enum class Gait { WALK = 0, RUN };
class World;
class Player {
private:
using enum GameMode;
float m_max_walk_speed = DEFAULT_MAX_WALK_SPEED;
float m_max_run_speed = DEFAULT_MAX_RUN_SPEED;
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;
float m_pitch = 0.0f;
float m_sensitivity = 0.15f;
float m_max_speed = m_max_walk_speed;
float m_y_speed = 0.0f;
float m_fly_y_speed = 7.5f;
bool can_up = true;
float space_on_time = 0.0f;
bool space_on = false;
bool is_fly = false;
float m_xz_speed = 0.0f;
unsigned m_place_block = 1;
glm::vec3 direction = glm::vec3(0.0f, 0.0f, 0.0f);
glm::vec3 move_distance{0.0f, 0.0f, 0.0f};
// 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};
glm::vec3 m_front{0, 0, -1};
glm::vec3 m_right{0, 0, 0};
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<LookBlock> m_look_block = std::nullopt;
std::string m_name{};
World& m_world;
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<LookBlock>& 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;
};
} // namespace Cubed

View File

@@ -0,0 +1,173 @@
#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 <atomic>
#include <condition_variable>
#include <mutex>
#include <optional>
#include <shared_mutex>
#include <thread>
#include <unordered_map>
#include <unordered_set>
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<void> future;
};
using OptionalBlockVectorArray =
std::array<std::optional<std::vector<BlockType>>, 4>;
using ChunkPtrUpdateList = std::vector<std::pair<ChunkPos, Chunk*>>;
using ChunkPairVector = std::vector<std::pair<ChunkPos, Chunk>>;
using ChunkPairQueue = std::queue<std::pair<ChunkPos, Chunk>>;
using ConstChunkMap =
std::unordered_map<ChunkPos, const Chunk*, ChunkPos::Hash>;
using ChunkPosSet = std::unordered_set<ChunkPos, ChunkPos::Hash>;
using ChunkHashMap = std::unordered_map<ChunkPos, Chunk, ChunkPos::Hash>;
using PendingChunkHashMap =
std::unordered_map<ChunkPos, PendingChunk, ChunkPos::Hash>;
glm::vec3 m_gen_player_pos{0.0f, 0.0f, 0.0f};
ChunkHashMap m_chunks;
std::unordered_map<std::size_t, Player> m_players;
std::vector<glm::vec4> m_planes;
std::thread m_gen_thread;
std::thread m_server_thread;
std::atomic<std::shared_ptr<ThreadPool>> m_gen_thread_pool;
std::stop_source m_server_stop_source;
std::atomic<int> m_per_tick_time = DEFAULT_PER_TICK_TIME; // ms
std::atomic<TickType> 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<GLuint> m_pending_delete_vbo;
std::vector<GLuint> m_pending_delete_vao;
std::condition_variable m_gen_cv;
std::atomic<bool> m_gen_running{false};
std::atomic<bool> m_need_gen_chunk{false};
std::atomic<bool> m_is_rebuilding{false};
std::atomic<bool> m_chunk_gen_finished{false};
std::atomic<bool> m_could_gen{true};
std::atomic<bool> m_tick_running{true};
std::atomic<int> m_rendering_distance{24};
std::atomic<int> m_pool_threads{0};
std::atomic<int> m_max_threads{1};
std::atomic<TickType> m_game_ticks{0};
std::atomic<ChunkLoadStyle> m_chunk_load_style{ChunkLoadStyle::RANDOM};
std::vector<ChunkPos> m_dirty_queue;
std::vector<ChunkRenderSnapshot> m_render_snapshots;
std::vector<std::pair<ChunkPos, Chunk>> 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<ChunkPos>&,
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<LookBlock>&
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<glm::vec4>& planes();
std::vector<ChunkRenderSnapshot>& 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

View File

@@ -93,5 +93,4 @@ private:
// Generate biome-specific vegetation/structures
void gen_phase_five();
};
} // namespace Cubed

View File

@@ -2,28 +2,19 @@
#include "Cubed/gameplay/chunk_pos.hpp"
#include "Cubed/gameplay/game_time.hpp"
#include <absl/container/flat_hash_set.h>
#include <atomic>
#include <glm/glm.hpp>
#include <memory>
#include <shared_mutex>
#include <string>
#include <string_view>
namespace Cubed {
class ServerWorld;
class Session;
class ServerPlayer {
public:
using ChunkPosSet = absl::flat_hash_set<ChunkPos, ChunkPos::Hash>;
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> session,
TickType gametick);
const glm::vec3& get_pos() const;
const std::string& get_name() const;
const std::string& get_uuid() const;
@@ -31,12 +22,6 @@ public:
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;
@@ -47,8 +32,5 @@ private:
ChunkPos m_last_chunk_pos{0, 0};
std::shared_ptr<Session> m_session;
std::atomic<TickType> m_last_gametick{0};
std::atomic<int> m_chunk_task_id{0};
mutable std::shared_mutex m_chunk_pos_mutex;
ChunkPosSet m_player_chunk_pos_set;
};
} // namespace Cubed

View File

@@ -6,33 +6,31 @@
#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 <absl/container/flat_hash_set.h>
#include <future>
#include <shared_mutex>
#include <tbb/concurrent_hash_map.h>
#include <tbb/concurrent_queue.h>
#include <tbb/concurrent_unordered_map.h>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
namespace Cubed {
class Session;
class ServerWorld {
public:
enum class ThreadPoolKind { NET, GEN };
ServerWorld();
~ServerWorld();
void stop();
void player_join(std::string_view name, std::string_view uuid);
void handle_player_exit(const std::string& uuid);
void init_world();
void need_gen(std::string uuid);
void need_gen(std::optional<std::string> uuid);
void update();
void hot_reload();
void rebuild_world();
int rendering_distance() const;
void rendering_distance(int rendering_distance);
void start_gen_thread();
@@ -59,10 +57,9 @@ public:
bool is_tick_running() const;
void tick_running(bool run);
int gen_pool_threads() const;
int pool_threads() const;
int max_threads() const;
void change_pool_threads(ThreadPoolKind kind, int threads);
void change_pool_threads(int threads);
int chunk_load_style() const;
void set_chunk_load_style(int id);
@@ -74,10 +71,8 @@ public:
std::shared_ptr<Session> 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_chunk_req(const std::string& uuid, ChunkPos pos);
void handle_block_change(const BlockChangeReq& req);
int chunk_size() const;
template <typename Fn>
void register_timer(std::string_view id, TickType threshold, Fn&& f) {
m_timers.emplace(std::piecewise_construct,
@@ -86,39 +81,25 @@ public:
}
private:
enum class ChunkState { NONE, GENERATING, READY, PENDING_DELETE };
struct ChunkEntity {
ChunkState state;
std::shared_ptr<ServerChunk> 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<ServerChunk> chunk;
ServerChunk chunk;
std::future<void> future;
};
using ChunkHashMap =
tbb::concurrent_hash_map<ChunkPos, ChunkEntity, ChunkPos::TBBHash>;
tbb::concurrent_unordered_map<ChunkPos, ServerChunk, ChunkPos::Hash>;
using PlayerHashMap = std::unordered_map<std::string, ServerPlayer>;
using NewChunkVector = std::vector<PendingChunk>;
using ChunkPosSet = absl::flat_hash_set<ChunkPos, ChunkPos::Hash>;
using PendingChunkHashMap =
std::unordered_map<ChunkPos, PendingChunk, ChunkPos::Hash>;
using ChunkPosSet = std::unordered_set<ChunkPos, ChunkPos::Hash>;
using PlayerUUIDMap = tbb::concurrent_hash_map<std::string, std::string>;
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;
PendingChunkHashMap m_new_chunks;
std::vector<std::pair<ChunkPos, ServerChunk>> m_new_finished_chunk;
CaveCarver m_cave_carcer;
RiverWorm m_river_worm;
@@ -130,11 +111,10 @@ private:
std::atomic<bool> m_could_gen{true};
std::atomic<bool> m_gen_running{false};
std::atomic<bool> m_need_gen_chunk{false};
std::atomic<bool> m_is_rebuilding{false};
std::atomic<bool> m_init{false};
std::atomic<bool> m_stopped{false};
std::atomic<int> m_rendering_distance{24};
std::atomic<int> m_gen_pool_threads{0};
std::atomic<int> m_net_pool_threads{0};
std::atomic<int> m_pool_threads{0};
std::atomic<int> m_max_threads{1};
std::atomic<TickType> m_game_ticks{0};
@@ -142,46 +122,32 @@ private:
std::atomic<bool> m_tick_running{true};
std::atomic<int> m_per_tick_time = DEFAULT_PER_TICK_TIME; // ms
mutable std::shared_mutex m_chunks_mutex;
std::shared_mutex m_new_chunk_mutex;
mutable std::shared_mutex m_player_mutex;
std::mutex m_need_gen_queue_mutex;
std::condition_variable_any m_gen_cv;
RecentQueue<std::string> m_need_gen_queue;
std::deque<std::string> m_need_gen_queue;
std::atomic<std::shared_ptr<PriorityThreadPool>> m_gen_thread_pool;
std::atomic<std::shared_ptr<ThreadPool>> m_net_thread_pool;
std::atomic<std::shared_ptr<ThreadPool>> m_gen_thread_pool;
std::atomic<ChunkLoadStyle> m_chunk_load_style{ChunkLoadStyle::CENTER};
PlayerUUIDMap m_uuid_to_name;
tbb::concurrent_unordered_map<std::string, Timer> m_timers;
tbb::concurrent_queue<PendingRequest> m_waiting_chunk_requests;
tbb::concurrent_queue<std::unique_ptr<ServerChunk>> m_finished_queue;
void init_chunks();
void gen_chunks_internal(const std::string& uuid);
void gen_chunks_internal(std::optional<std::string> uuid);
void compute_required_chunks(ChunkPosSet& required_chunks,
const std::optional<std::string>& uuid);
void sync_and_collect_missing_chunks(std::vector<ChunkPos>&,
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 submit_new_chunks(const std::optional<std::string>& uuid);
void poll_finished_chunks();
void wait_all_chunk_tasks();
void send_time();
void send_chunk(int task_id, const std::string& uuid, ChunkPos pos);
int
change_pool_threads(std::atomic<std::shared_ptr<ThreadPool>>& thread_pool,
int threads);
int change_pool_threads(
std::atomic<std::shared_ptr<PriorityThreadPool>>& thread_pool,
int threads);
void send_server_stop();
};
} // namespace Cubed

View File

@@ -3,8 +3,8 @@
#include "Cubed/gameplay/packet.hpp"
#include <asio.hpp>
#include <deque>
#include <memory>
#include <queue>
#include <string>
namespace Cubed {
@@ -17,44 +17,20 @@ public:
asio::io_context& io);
~Session();
void start();
void send(Packet packet, int priority = 10);
void send(Packet packet);
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<char> m_read_buffer;
std::priority_queue<Task, std::vector<Task>, TaskCompare> m_write_queue;
std::deque<Packet> m_write_queue;
asio::strand<asio::io_context::executor_type> m_strand;
std::string m_uuid;
ServerWorld& m_server_world;
std::atomic<bool> m_closed{false};
std::atomic_uint64_t m_sequence{0};
asio::awaitable<void> read_loop();
std::atomic<bool> m_closed{false};
void do_write();
};
} // namespace Cubed

View File

@@ -9,7 +9,7 @@
namespace Cubed {
namespace Logger {
enum class Level { L_TRACE, L_DEBUG, L_INFO, L_ERROR, L_WARN };
enum class Level { TRACE, DEBUG, INFO, ERROR, WARN };
template <typename... Args>
inline void info(std::format_string<Args...> 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::L_TRACE:
case Logger::Level::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::L_DEBUG:
case Logger::Level::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::L_INFO:
case Logger::Level::INFO:
info(fmt, std::forward<Args>(args)...);
break;
case Logger::Level::L_WARN:
case Logger::Level::WARN:
warn(fmt, std::forward<Args>(args)...);
break;
case Logger::Level::L_ERROR:
case Logger::Level::ERROR:
error(fmt, std::forward<Args>(args)...);
break;
}

View File

@@ -1,108 +1,18 @@
#pragma once
#include <algorithm>
#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>
namespace Cubed {
namespace Math {
inline void extract_frustum_planes(const glm::mat4& mvp_matrix,
std::vector<glm::vec4>& planes) {
if (planes.size() != 6) {
planes.resize(6);
}
void extract_frustum_planes(const glm::mat4& mvp_matrix,
std::vector<glm::vec4>& planes);
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<glm::vec4>& 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<float>() * 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);
}
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<glm::vec4>& planes);
float deterministic_random(int x, int z, uint64_t seed);
glm::vec3 slerp(const glm::vec3& from, const glm::vec3& to, float t);
} // namespace Math

View File

@@ -1,151 +0,0 @@
#pragma once
#include <condition_variable>
#include <cstddef>
#include <functional>
#include <future>
#include <mutex>
#include <queue>
#include <thread>
#include <vector>
namespace Cubed {
class PriorityThreadPool {
private:
struct Task {
int priority = 10;
std::uint64_t sequence;
std::function<void()> task;
Task(int p, std::uint64_t seq, std::function<void()> 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<std::jthread> m_workers;
std::priority_queue<Task, std::vector<Task>, TaskCompare> m_tasks;
std::mutex m_mtx;
std::condition_variable_any m_cv;
std::atomic<bool> m_stopping{false};
std::atomic<size_t> 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<void()> 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 <typename F> auto enqueue(int priority, F&& f) {
using R = std::invoke_result_t<F>;
auto task =
std::make_shared<std::packaged_task<R()>>(std::forward<F>(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 <typename F> auto enqueue(F&& f) {
return enqueue(10, std::forward<F>(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 <std::random_access_iterator Iter, typename F>
void parallel_do(PriorityThreadPool& pool, Iter first, Iter last,
size_t max_threads, F&& f) {
max_threads = std::max<size_t>(1, max_threads);
max_threads = std::min(max_threads, pool.thread_sum());
std::decay_t<F> fn(std::forward<F>(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<size_t>(1, num_blocks);
size_t block_size = (length + num_blocks - 1) / num_blocks;
std::vector<std::future<void>> 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<size_t>(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

View File

@@ -1,55 +0,0 @@
#pragma once
#include <list>
#include <unordered_map>
namespace Cubed {
template <typename T> class RecentQueue {
private:
std::list<T> m_list;
std::unordered_map<T, typename std::list<T>::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

View File

@@ -31,7 +31,7 @@ public:
{
std::unique_lock lock(m_mtx);
m_cv.wait(lock, stoken,
[this] { return !m_tasks.empty(); });
[this, stoken] { return !m_tasks.empty(); });
if (stoken.stop_requested() && m_tasks.empty()) {
return;
}
@@ -62,9 +62,7 @@ public:
return fut;
}
void stop() {
if (m_stopping.exchange(true)) {
return;
}
m_stopping = true;
for (auto& w : m_workers) {
w.request_stop();
}

View File

@@ -1,33 +0,0 @@
#pragma once
#include "Cubed/tools/log.hpp"
#include <toml++/toml.hpp>
namespace Cubed {
namespace TOML {
template <typename T>
concept TomlValueType =
std::same_as<std::decay_t<T>, int> || std::same_as<std::decay_t<T>, bool> ||
std::same_as<std::decay_t<T>, double> ||
std::same_as<std::decay_t<T>, char> ||
std::same_as<std::decay_t<T>, toml::date> ||
std::same_as<std::decay_t<T>, toml::time> ||
std::same_as<std::decay_t<T>, toml::date_time> ||
std::same_as<std::decay_t<T>, std::string>;
template <TomlValueType T>
std::optional<T> safe_get_value(const toml::table& table, std::string_view key,
const T& default_value) {
auto value = table[key].value<T>();
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

View File

@@ -1,46 +0,0 @@
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
)

View File

@@ -6,7 +6,6 @@
#include "Cubed/tools/cubed_assert.hpp"
#include "Cubed/tools/log.hpp"
#include "Cubed/tools/system_info.hpp"
#include "version.hpp"
#include <exception>
#include <imgui_impl_glfw.h>
@@ -35,9 +34,7 @@ void App::cursor_position_callback(GLFWwindow* window, double xpos,
}
}
void App::init(int argc, char** argv) {
handle_toml();
handle_argument(argc, argv);
m_window.init();
m_window.imgui_init();
@@ -75,7 +72,7 @@ void App::init(int argc, char** argv) {
m_client->start(m_argument.ip, m_argument.port);
// init will send packet
m_client_world.init(m_argument.player, m_client);
m_client_world.init("test", m_client);
Logger::info("World Init Success");
@@ -97,10 +94,10 @@ void App::handle_argument(int argc, char** argv) {
[&](ArgParser& p) {
auto arg = p.require_next("-p");
auto r = std::from_chars(arg.data(), arg.data() + arg.size(),
m_argument.port);
auto r =
std::from_chars(arg.begin(), arg.end(), m_argument.port);
if (r.ec != std::errc{} || r.ptr != arg.data() + arg.size()) {
if (r.ec != std::errc{} || r.ptr != arg.end()) {
throw std::runtime_error(
std::format("Invalid port: {}", arg));
}
@@ -115,16 +112,6 @@ void App::handle_argument(int argc, char** argv) {
[&](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);
}}
};
@@ -140,21 +127,6 @@ void App::handle_argument(int argc, char** argv) {
}
}
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();
@@ -252,7 +224,8 @@ void App::window_focus_callback(GLFWwindow* window, int focused) {
}
}
void App::window_reshape_callback(GLFWwindow* window, int, int) {
void App::window_reshape_callback(GLFWwindow* window, int new_width,
int new_height) {
App* app = static_cast<App*>(glfwGetWindowUserPointer(window));
ASSERT_MSG(app, "nullptr");
@@ -307,16 +280,11 @@ 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();
}
m_client_world.exit();
}
static Gait player_gait = Gait::WALK;
void App::update() {
@@ -385,5 +353,5 @@ TextureManager& App::texture_manager() { return m_texture_manager; }
Window& App::window() { return m_window; }
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

View File

@@ -1,18 +1,31 @@
#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 <filesystem>
#include <toml++/toml.hpp>
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 <Cubed::TomlValueType T>
std::optional<T> safe_get_value(const toml::table& table, std::string_view key,
const T& default_value) {
auto value = table[key].value<T>();
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 {

View File

@@ -24,6 +24,7 @@ toml::table& Config::table() { return m_tbl; }
void Config::create_config() {
static constexpr auto SOURCE = R"(
version = "0.0.1"
[window]
width = 800

View File

@@ -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,16 +24,10 @@ 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);
.text("Version: " + Config::get().get<std::string>("version"));
fps_text.position(0.0f, 50.0f).text("FPS: 0");
player_pos_text.position(0.0f, 150.0f)
.scale(0.8f)

View File

@@ -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 constexpr const char* CHUNK_LOAD_STYLE[] = {"Random", "Center"};
static char perlin_noise_input_buffer[64];
constexpr float TEMP_MIN = 0.0f;
constexpr float TEMP_MAX = 1.0f;
@@ -46,6 +46,16 @@ 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() {
@@ -448,116 +458,108 @@ void DevPanel::show_settings_tab_item() {
void DevPanel::show_world_tab_item() {
if (ImGui::BeginTabItem("world")) {
if (ImGui::BeginTabBar("World Kind")) {
if (!m_app.argument().is_client) {
if (ImGui::BeginTabItem("ServerWorld")) {
show_server_world_table_bar();
ImGui::EndTabItem();
}
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<unsigned int>(
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.server_world().rebuild_world();
}
if (ImGui::BeginTabItem("Client World")) {
show_client_world_table_bar();
ImGui::EndTabItem();
}
if (!m_text_editing.perlin_seed) {
ImGui::Text("ChunkGenerator Seed: %u", ChunkGenerator::seed());
if (ImGui::IsItemClicked()) {
m_text_editing.perlin_seed = true;
}
ImGui::EndTabBar();
}
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);
}
ImGui::Text(
"Pool Threads %d Max Support Threads %d Reserved Threads %d",
m_app.server_world().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(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.");
}
static const char* chunk_load_style[] = {"Random", "Center"};
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("Rebuild World")) {
m_app.server_world().rebuild_world();
}
ImGui::SameLine();
if (ImGui::Button("Request Chunk Build")) {
Logger::warn("This Request Chunk Build button is not finish");
m_app.server_world().need_gen(std::nullopt);
}
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.server_world().start_gen_thread();
} else {
m_app.server_world().stop_gen_thread();
}
}
// ImGui::Text("Chunk Build Progress\n");
// ImGui::ProgressBar(m_app.world().chunk_gen_fraction());
// show_chunk_table_bar();
show_chunk_table_bar();
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();
}
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) {
@@ -670,9 +672,8 @@ 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",

View File

@@ -233,9 +233,7 @@ size_t ClientChunk::get_water_vertices_sum() const {
void ClientChunk::upload_to_gpu() {
if (!is_need_upload()) {
return;
}
ASSERT(is_need_upload());
std::lock_guard lk(m_vertexs_data_mutex);
@@ -245,27 +243,6 @@ void ClientChunk::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<float>(m_chunk_pos.x * CHUNK_SIZE) +
static_cast<float>(CHUNK_SIZE / 2),
static_cast<float>(WORLD_SIZE_Y / 2),
static_cast<float>(m_chunk_pos.z * CHUNK_SIZE) +
static_cast<float>(CHUNK_SIZE / 2)),
glm::vec3(static_cast<float>(CHUNK_SIZE / 2),
static_cast<float>(WORLD_SIZE_Y / 2),
static_cast<float>(CHUNK_SIZE / 2))};
m_need_upload = false;
}
@@ -281,6 +258,7 @@ void ClientChunk::need_upload() { m_need_upload = true; }
void ClientChunk::set_chunk_block(int index, unsigned id) {
m_blocks[index] = id;
mark_dirty();
}
ChunkPos ClientChunk::chunk_pos() const { return m_chunk_pos; }
@@ -298,10 +276,6 @@ unsigned ClientChunk::seed() const {
return m_seed;
}
const ChunkRenderSnapshot* ClientChunk::get_render_snapshot() const {
return &m_render_snapshot;
}
void ClientChunk::gen_vertices(const OptionalBlockVectorArray& neighbor_block) {
// SIZE_X=SIZE_Z=CHUNK_SIZE=16, SIZE_Y=WORLD_SIZE_Y=256

View File

@@ -8,13 +8,13 @@ namespace Cubed {
ClientPlayer::ClientPlayer(ClientWorld& world) : m_world(world) {}
ClientPlayer::~ClientPlayer() {}
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 ClientPlayer::get_aabb(const glm::vec3& pos) 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 max{pos.x + half_width, pos.y + M_SIZE.y, pos.z + half_depth};
glm::vec3 max{pos.x + half_width, pos.y + m_size.y, pos.z + half_depth};
return AABB{min, max};
}
@@ -282,7 +282,15 @@ void ClientPlayer::update_lookup_block() {
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)) {
AABB block_box = ClientWorld::get_block_aabb(near_pos);
auto x = near_pos.x;
auto y = near_pos.y;
auto z = near_pos.z;
AABB block_box = {glm::vec3{static_cast<float>(x),
static_cast<float>(y),
static_cast<float>(z)},
glm::vec3{static_cast<float>(x + 1),
static_cast<float>(y + 1),
static_cast<float>(z + 1)}};
AABB player_box = get_aabb(get_player_pos());
if (!player_box.intersects(block_box)) {
m_world.report_block_change(near_pos, m_place_block);
@@ -401,9 +409,13 @@ void ClientPlayer::update_x_move(glm::vec3& player_pos) {
for (int x = minx; x <= maxx; ++x) {
for (int y = miny; y <= maxy; ++y) {
for (int z = minz; z <= maxz; ++z) {
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 (!m_world.can_pass_block(glm::ivec3{x, y, z})) {
AABB block_box = {glm::vec3{static_cast<float>(x),
static_cast<float>(y),
static_cast<float>(z)},
glm::vec3{static_cast<float>(x + 1),
static_cast<float>(y + 1),
static_cast<float>(z + 1)}};
if (player_box.intersects(block_box)) {
m_gait = Gait::WALK;
player_pos.x -= move_distance.x;
@@ -431,9 +443,13 @@ void ClientPlayer::update_y_move(glm::vec3& player_pos) {
for (int x = minx; x <= maxx; ++x) {
for (int y = miny; y <= maxy; ++y) {
for (int z = minz; z <= maxz; ++z) {
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 (!m_world.can_pass_block(glm::ivec3{x, y, z})) {
AABB block_box = {glm::vec3{static_cast<float>(x),
static_cast<float>(y),
static_cast<float>(z)},
glm::vec3{static_cast<float>(x + 1),
static_cast<float>(y + 1),
static_cast<float>(z + 1)}};
if (player_box.intersects(block_box)) {
player_pos.y -= move_distance.y;
m_y_speed = 0.0f;
@@ -465,9 +481,13 @@ void ClientPlayer::update_z_move(glm::vec3& player_pos) {
for (int x = minx; x <= maxx; ++x) {
for (int y = miny; y <= maxy; ++y) {
for (int z = minz; z <= maxz; ++z) {
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 (!m_world.can_pass_block(glm::ivec3{x, y, z})) {
AABB block_box = {glm::vec3{static_cast<float>(x),
static_cast<float>(y),
static_cast<float>(z)},
glm::vec3{static_cast<float>(x + 1),
static_cast<float>(y + 1),
static_cast<float>(z + 1)}};
if (player_box.intersects(block_box)) {
m_gait = Gait::WALK;
player_pos.z -= move_distance.z;
@@ -489,7 +509,6 @@ void ClientPlayer::update_player_chunk() {
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;
}
@@ -522,22 +541,6 @@ void ClientPlayer::update_scroll(double yoffset) {
}
}
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; }

View File

@@ -3,14 +3,8 @@
#include "Cubed/config.hpp"
#include "Cubed/gameplay/game_time.hpp"
#include "Cubed/gameplay/packet.hpp"
#include "Cubed/tools/math_tools.hpp"
#include <absl/container/inlined_vector.h>
#include <numbers>
using namespace std::chrono;
using namespace std::chrono_literals;
using namespace google::protobuf;
namespace Cubed {
namespace {
@@ -25,9 +19,10 @@ ClientWorld::ClientWorld() : m_player(*this) {}
ClientWorld::~ClientWorld() {
stop_client_thread();
stop_thread_pool();
m_chunks.clear();
{
std::lock_guard lock(m_chunks_mutex);
m_chunks.clear();
}
{
std::lock_guard lk(m_delete_vbo_mutex);
for (auto x : m_pending_delete_vbo) {
@@ -45,7 +40,8 @@ ClientWorld::~ClientWorld() {
m_timers.clear();
}
const std::optional<LookBlock>& ClientWorld::get_look_block_pos() const {
const std::optional<LookBlock>&
ClientWorld::get_look_block_pos(const std::string& name) const {
return m_player.get_look_block_pos();
}
@@ -54,13 +50,14 @@ 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;
std::shared_lock lk(m_chunks_mutex);
auto it = m_chunks.find(ChunkPos{chunk_x, chunk_z});
if (!m_chunks.find(cacc, ChunkPos{chunk_x, chunk_z})) {
if (it == m_chunks.end()) {
return 0;
}
const auto& chunk_blocks = cacc->second->get_chunk_blocks();
const auto& chunk_blocks = it->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) {
@@ -70,12 +67,13 @@ int ClientWorld::get_block(const glm::ivec3& block_pos) const {
}
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;
std::shared_lock lk(m_chunks_mutex);
auto it = m_chunks.find(ChunkPos{chunk_x, chunk_z});
if (!m_chunks.find(cacc, ChunkPos{chunk_x, chunk_z})) {
if (it == m_chunks.end()) {
return false;
}
const auto& chunk_blocks = cacc->second->get_chunk_blocks();
const auto& chunk_blocks = it->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) {
@@ -90,12 +88,13 @@ bool ClientWorld::is_solid(const glm::ivec3& block_pos) const {
}
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;
std::shared_lock lk(m_chunks_mutex);
auto it = m_chunks.find(ChunkPos{chunk_x, chunk_z});
if (!m_chunks.find(cacc, ChunkPos{chunk_x, chunk_z})) {
if (it == m_chunks.end()) {
return true;
}
const auto& chunk_blocks = cacc->second->get_chunk_blocks();
const auto& chunk_blocks = it->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) {
@@ -104,36 +103,17 @@ bool ClientWorld::can_pass_block(const glm::ivec3& block_pos) const {
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;
;
std::shared_lock lk(m_chunks_mutex);
auto it = m_chunks.find(ChunkPos{chunk_x, chunk_z});
if (!m_chunks.find(cacc, 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 = cacc->second->get_chunk_blocks();
const auto& chunk_blocks = it->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) {
@@ -150,106 +130,34 @@ void ClientWorld::set_block(const glm::ivec3& block_pos, unsigned id) {
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;
std::lock_guard lk(m_chunks_mutex);
auto it = m_chunks.find(ChunkPos{chunk_x, chunk_z});
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();
if (it == m_chunks.end()) {
return;
}
auto pool = m_thread_pool.load();
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;
}
pool->enqueue(0, [this, pos]() {
std::shared_ptr<ClientChunk> 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);
});
it->second.set_chunk_block(ClientChunk::index(x, y, z), id);
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<ChunkPos, NPOS_SUM> 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);
}
auto it = m_chunks.find({cx, cz});
if (it != m_chunks.end()) {
it->second.mark_dirty();
}
}
for (auto& npos : nposes) {
pool->enqueue(0, [this, npos]() {
std::shared_ptr<ClientChunk> 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);
@@ -262,27 +170,14 @@ void ClientWorld::push_delete_vao(GLuint 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<BlockChangeReq>(&arena);
req->set_uuid(m_player.get_uuid());
req->set_block(id);
auto* p = req->mutable_pos();
BlockChangeReq req;
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);
m_client->send(make_packet(req));
}
void ClientWorld::receive_block_change(const BlockChangeRsp& rsp) {
@@ -295,31 +190,17 @@ void ClientWorld::receive_time(const UpdateTime& rsp) {
m_day_tick = rsp.day_tick();
}
void ClientWorld::receive_remote_player(const PlayerInfoRsp& rsp) {
void ClientWorld::receive_other_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;
}
m_other_players[rsp.uuid()].name = rsp.name();
m_other_players[rsp.uuid()].pos =
glm::vec3{rsp.pos().x(), rsp.pos().y(), rsp.pos().z()};
// 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());
@@ -333,6 +214,7 @@ void ClientWorld::receive_player_logout(const LogoutRsp& rsp) {
void ClientWorld::init(std::string_view player_name,
std::shared_ptr<NetworkClient> client) {
m_chunks.reserve(MAX_DISTANCE * MAX_DISTANCE * 4);
m_player.init(player_name);
m_client = client;
// timer
@@ -348,7 +230,7 @@ void ClientWorld::init(std::string_view player_name,
start_thread_pool();
// request login
Logger::info("Send Login Request");
m_client->send(make_packet(req), 0);
m_client->send(make_packet(req));
}
void ClientWorld::start_client_thread(std::string_view uuid) {
@@ -362,12 +244,6 @@ void ClientWorld::start_client_thread(std::string_view uuid) {
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();
}
@@ -378,11 +254,7 @@ void ClientWorld::stop_client_thread() {
}
m_game_running = false;
}
void ClientWorld::start_thread_pool() {
int max_threads = std::thread::hardware_concurrency();
int threads = std::min<size_t>(max_threads, 4);
change_pool_threads(threads);
}
void ClientWorld::start_thread_pool() { change_pool_threads(1); }
void ClientWorld::stop_thread_pool() {
auto pool_ptr = m_thread_pool.load();
if (pool_ptr) {
@@ -400,30 +272,28 @@ void ClientWorld::change_pool_threads(int threads) {
}
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<PriorityThreadPool>(used_thread));
m_thread_pool.store(std::make_shared<ThreadPool>(used_thread));
}
void ClientWorld::hot_reload() {
auto& config = Config::get();
int dist = config.get<int>("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;
auto t1 = system_clock::now();
for (auto& x : m_timers) {
x.second.update();
}
std::this_thread::sleep_until(next);
auto t2 = system_clock::now();
auto dt = duration_cast<microseconds>(t2 - t1);
auto st = std::max(dt, milliseconds(DEFAULT_PER_TICK_TIME) - dt);
std::this_thread::sleep_for(st);
}
}
@@ -431,35 +301,14 @@ void ClientWorld::report_player_pos() {
if (!m_client) {
return;
}
Arena arena;
auto* pos = Arena::Create<PlayerPos>(&arena);
pos->set_uuid(m_player.get_uuid());
PlayerPos pos;
pos.set_uuid(m_player.get_uuid());
glm::vec3 player_pos = m_player.get_player_pos();
auto* v3 = pos->mutable_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);
}
}
m_client->send(make_packet(pos));
}
void ClientWorld::request_chunk() {
@@ -475,7 +324,6 @@ void ClientWorld::request_chunk() {
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);
@@ -487,22 +335,25 @@ void ClientWorld::request_chunk() {
}
}
ChunkPosSet old = std::move(m_player.get_chunk_pos_set());
m_player.update_chunk_set(required_chunks);
ChunkPosVector need_send_pos;
{
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.unsafe_erase(it);
} else {
++it;
}
}
for (auto pos : required_chunks) {
chunk_cacc cacc;
if (!m_chunks.find(cacc, pos)) {
need_send_pos.emplace_back(pos);
for (auto pos : required_chunks) {
auto it = m_chunks.find(pos);
if (it == m_chunks.end()) {
need_send_pos.emplace_back(pos);
}
}
}
update_chunk(old, required_chunks);
if (need_send_pos.empty()) {
m_requesting_chunk = false;
return;
}
using enum ChunkLoadStyle;
@@ -513,8 +364,9 @@ void ClientWorld::request_chunk() {
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) {
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;
@@ -527,90 +379,48 @@ void ClientWorld::request_chunk() {
}
}
auto uuid = m_player.get_uuid();
Arena arena;
++m_chunk_task_id;
auto* req = Arena::Create<ChunkDataReq>(&arena);
ChunkDataReq req;
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();
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));
m_client->send(make_packet(req));
}
Logger::info("Send Chunk Request Success");
m_requesting_chunk = false;
}
void ClientWorld::receive_chunk(std::vector<uint8_t> raw_data,
PacketHeader header) {
void ClientWorld::receive_chunk(const ChunkDataRsp& data) {
{
std::lock_guard lock(m_chunks_mutex);
ChunkPos pos{data.pos().x(), data.pos().z()};
if (m_chunks.find(pos) != m_chunks.end()) {
Logger::warn("Chunk {} {} has already in client world", pos.x,
pos.z);
return;
}
}
// 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<ChunkDataRsp>(&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<ClientChunk> chunk =
std::make_unique<ClientChunk>(*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<float>(x), static_cast<float>(y),
static_cast<float>(z)},
glm::vec3{static_cast<float>(x + 1), static_cast<float>(y + 1),
static_cast<float>(z + 1)}};
}
void ClientWorld::request_exit() {
if (m_receive_exit) {
return;
}
Arena arena;
auto* req = Arena::Create<LogoutReq>(&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;
pool->enqueue([this, data = std::move(data)]() {
ClientChunk chunk{*this};
chunk.receive_chunk(data);
{
std::lock_guard lock(m_pending_upload_queue_mutex);
m_pending_upload_queue.emplace_back(std::move(chunk));
}
}
});
}
void ClientWorld::exit() {
LogoutReq req;
req.set_uuid(m_player.get_uuid());
m_client->send(make_packet(req));
}
void ClientWorld::update(float delta_time) {
@@ -630,76 +440,67 @@ void ClientWorld::update(float delta_time) {
}
m_pending_delete_vao.clear();
}
std::vector<std::unique_ptr<ClientChunk>> new_chunks;
std::vector<ClientChunk> new_chunks;
{
std::unique_ptr<ClientChunk> 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
}
std::lock_guard lock(m_pending_upload_queue_mutex);
for (auto& c : m_pending_upload_queue) {
// Logger::info("{} {}", c.get_chunk_pos().x, c.get_chunk_pos().z);
new_chunks.emplace_back(std::move(c));
}
m_pending_upload_queue.clear();
}
for (auto& c : new_chunks) {
c->upload_to_gpu();
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<ClientChunk> chunk;
{
chunk_acc acc;
if (m_chunks.find(acc, pos)) {
chunk = acc->second;
{
std::lock_guard lock(m_chunks_mutex);
for (auto& c : new_chunks) {
m_chunks.emplace(c.get_chunk_pos(), std::move(c));
}
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<float>(pos.x * CHUNK_SIZE) +
static_cast<float>(CHUNK_SIZE / 2),
static_cast<float>(WORLD_SIZE_Y / 2),
static_cast<float>(pos.z * CHUNK_SIZE) +
static_cast<float>(CHUNK_SIZE / 2)),
glm::vec3(static_cast<float>(CHUNK_SIZE / 2),
static_cast<float>(WORLD_SIZE_Y / 2),
static_cast<float>(CHUNK_SIZE / 2))});
}
}
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<ClientChunk> 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);
m_render_player_data.emplace_back(player.name, player.pos);
}
}
}
@@ -727,19 +528,11 @@ int ClientWorld::rendering_distance() const {
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<const ChunkRenderSnapshot*>&
ClientWorld::render_snapshots() const {
const std::vector<ChunkRenderSnapshot>& ClientWorld::render_snapshots() const {
return m_render_snapshots;
};
const std::vector<RemotePlayerRenderData>&
ClientWorld::render_player_data() const {
const std::vector<RenderPlayerData>& ClientWorld::render_player_data() const {
return m_render_player_data;
}
std::vector<glm::vec4>& ClientWorld::planes() { return m_planes; }

View File

@@ -2,10 +2,6 @@
#include "Cubed/gameplay/client_world.hpp"
#include "Cubed/tools/log.hpp"
#include <utility>
using namespace google::protobuf;
namespace Cubed {
NetworkClient::NetworkClient(ClientWorld& world)
: m_socket(m_io), m_strand(asio::make_strand(m_io)), m_world(world) {}
@@ -59,64 +55,56 @@ asio::awaitable<void> NetworkClient::read_loop() {
throw std::runtime_error("invalid packet length");
}
// maybe move, don't use it after switch!
std::vector<uint8_t> 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;
constexpr auto& to_num = std::to_underlying<PacketEnum>;
switch (header.cmd) {
case std::to_underlying(PacketEnum::LOGIN_RSP): {
auto* rsp = Arena::Create<LoginRsp>(&arena);
case to_num(PacketEnum::LOGIN_RSP): {
LoginRsp rsp;
Logger::info("Client: Receive Login rsp");
if (decode_packet(*rsp, body_data, header)) {
if (rsp->success()) {
m_world.start_client_thread(rsp->uuid());
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);
case to_num(PacketEnum::CHUNK_DATA_RSP): {
ChunkDataRsp rsp;
Logger::info("Client: Receive Chunk Data rsp, size {}mb",
body_data.size() / 1024.0f / 1024);
if (decode_packet(rsp, body_data, header)) {
m_world.receive_chunk(rsp);
}
} break;
case std::to_underlying(PacketEnum::BLOCK_CHANGE_RSP): {
auto* rsp = Arena::Create<BlockChangeRsp>(&arena);
case to_num(PacketEnum::BLOCK_CHANGE_RSP): {
BlockChangeRsp rsp;
Logger::info("Client: Receive Block Change rsp");
if (decode_packet(*rsp, body_data, header)) {
m_world.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<UpdateTime>(&arena);
if (decode_packet(*rsp, body_data, header)) {
m_world.receive_time(*rsp);
case to_num(PacketEnum::UPDATE_TIME): {
UpdateTime rsp;
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<PlayerInfoRsp>(&arena);
if (decode_packet(*rsp, body_data, header)) {
m_world.receive_remote_player(*rsp);
case to_num(PacketEnum::PLAYER_INFO_RSP): {
PlayerInfoRsp rsp;
if (decode_packet(rsp, body_data, header)) {
m_world.receive_other_player(rsp);
}
} break;
case std::to_underlying(PacketEnum::LOGOUT_RSP): {
auto* rsp = Arena::Create<LogoutRsp>(&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<S2C_ClearAllChunks>(&arena);
if (decode_packet(*rsp, body_data, header)) {
if (rsp->clear()) {
Logger::info("Client Clear All Chunk");
m_world.rebuild_world();
}
case to_num(PacketEnum::LOGOUT_RSP): {
LogoutRsp rsp;
if (decode_packet(rsp, body_data, header)) {
m_world.receive_player_logout(rsp);
}
} break;
}
@@ -142,15 +130,14 @@ asio::awaitable<void> NetworkClient::read_loop() {
co_return;
}
void NetworkClient::send(Packet packet, int priority) {
void NetworkClient::send(std::shared_ptr<std::vector<uint8_t>> packet) {
if (m_closed.load()) {
return;
}
asio::post(m_strand, [self = shared_from_this(), packet = std::move(packet),
priority]() mutable {
asio::post(m_strand, [self = shared_from_this(),
packet = std::move(packet)]() mutable {
bool idle = self->m_write_queue.empty();
self->m_write_queue.emplace(priority, self->m_sequence++,
std::move(packet));
self->m_write_queue.emplace_back(std::move(packet));
if (idle) {
self->do_write();
}
@@ -163,16 +150,15 @@ void NetworkClient::do_write() {
}
auto self = shared_from_this();
auto packet = std::move(m_write_queue.top().packet);
asio::async_write(
m_socket, asio::buffer(*packet),
m_socket, asio::buffer(*(m_write_queue.front())),
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();
self->m_write_queue.pop_front();
if (!self->m_write_queue.empty()) {
self->do_write();
}

View File

@@ -83,7 +83,6 @@ void NetworkServer::net_run() {
}
void NetworkServer::start_server(int port) {
m_port = port;
m_world.init_world();
net_run();
m_started = true;

View File

@@ -0,0 +1,804 @@
#include "Cubed/gameplay/chunk.hpp"
#include "Cubed/gameplay/world.hpp"
#include "Cubed/tools/cubed_assert.hpp"
#include "Cubed/tools/log.hpp"
#include <utility>
namespace Cubed {
using OptionalBlockVectorArray =
std::array<std::optional<std::vector<BlockType>>, 4>;
namespace {
// ────────────────────────────────────────────────────────────────────────────
// Face direction mapping
// Original DIR[6]: {+Z,+X,-Z,-X,+Y,-Y} => face index 0-5
// Axis × direction => face:
// axis=2(Z) dir=+1 => face 0 (+Z)
// axis=0(X) dir=+1 => face 1 (+X)
// axis=2(Z) dir=-1 => face 2 (-Z)
// axis=0(X) dir=-1 => face 3 (-X)
// axis=1(Y) dir=+1 => face 4 (+Y)
// axis=1(Y) dir=-1 => face 5 (-Y)
// ────────────────────────────────────────────────────────────────────────────
inline int axis_dir_to_face(int axis, int dir) {
// axis: 0=X 1=Y 2=Z
// dir: +1 or -1
static const int TABLE[3][2] = {
{3, 1}, // X: dir=-1->face3(-X), dir=+1->face1(+X)
{5, 4}, // Y: dir=-1->face5(-Y), dir=+1->face4(+Y)
{2, 0}, // Z: dir=-1->face2(-Z), dir=+1->face0(+Z)
};
return TABLE[axis][dir > 0 ? 1 : 0];
}
inline BlockType
get_block_safe(int lx, int ly, int lz, ChunkPos& chunk_pos,
const std::vector<BlockType>& blocks,
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)];
}
// 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);
const std::optional<std::vector<BlockType>>* nb = nullptr;
if (nb_cx == chunk_pos.x + 1)
nb = &neighbor_block[0];
else if (nb_cx == chunk_pos.x - 1)
nb = &neighbor_block[1];
else if (nb_cz == chunk_pos.z + 1)
nb = &neighbor_block[2];
else if (nb_cz == chunk_pos.z - 1)
nb = &neighbor_block[3];
if (!nb || !nb->has_value())
return 0; // Neighbor does not exist, treat as opaque
int nbx = world_x - nb_cx * CHUNK_SIZE;
int nby = ly;
int nbz = world_z - nb_cz * CHUNK_SIZE;
if (nbx < 0 || nby < 0 || nbz < 0 || nbx >= CHUNK_SIZE ||
nby >= WORLD_SIZE_Y || nbz >= CHUNK_SIZE)
return 0;
int idx = Chunk::index(nbx, nby, nbz);
if (static_cast<size_t>(idx) >= (*nb)->size()) {
return 0;
}
return (**nb)[idx];
}
// Determine whether the face from cur_id looking towards neighbor_id should be
// culled (does not need to be rendered)
inline bool is_face_culled(BlockType cur_id, BlockType neighbor_id) {
if (!BlockManager::is_transparent(neighbor_id))
return true; // Neighbor is opaque, blocking
// Neighbor transparency: same block type culls each other (e.g., water
// adjacent to water does not render internal faces)
if (neighbor_id == cur_id)
return true;
return false;
}
inline int choose_buf(BlockType id) {
if (!BlockManager::is_transparent(id))
return 0;
if (BlockManager::is_discard(id))
return 2;
if (BlockManager::is_blend(id)) {
return (id == 7) ? 4 : 3; // water=4, other blend=3
}
return 3; // fallback
}
} // namespace
Chunk::Chunk(World& world, ChunkPos chunk_pos, bool temp_chunk)
: m_temp_chunk(temp_chunk), m_chunk_pos(chunk_pos), m_world(world) {
for (int i = 0; i < VERTEX_DATA_SUM; i++) {
m_vertex_data.emplace_back(m_world);
}
}
Chunk::~Chunk() {}
Chunk::Chunk(Chunk&& 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)) {}
Chunk& Chunk::operator=(Chunk&& 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<const void*>(&other));
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);
m_biome = other.m_biome.load();
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<int, int, int> 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<int, int, int> 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<int, int, int> 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<int, int, int> 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<BlockType>& 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<const void*>(this));
return m_heightmap;
}
int Chunk::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 Chunk::index(const glm::vec3& pos) {
return Chunk::index(pos.x, pos.y, pos.z);
}
void Chunk::gen_vertex_data(const OptionalBlockVectorArray& neighbor_block) {
if (m_is_on_gen_vertex_data) {
return;
}
m_is_on_gen_vertex_data = true;
std::lock_guard lk(m_vertexs_data_mutex);
for (auto& data : m_vertex_data) {
data.m_vertices.clear();
}
gen_vertices(neighbor_block);
for (auto& data : m_vertex_data) {
data.update_sum();
}
m_need_upload = true;
m_is_on_gen_vertex_data = false;
}
GLuint Chunk::get_normal_vao() const { return m_vertex_data[0].m_vao; }
size_t Chunk::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 {
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 {
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 {
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 {
return m_vertex_data[4].m_sum.load();
}
void Chunk::gen_phase_one() {
// m_generator = std::make_unique<ChunkGenerator>(*this);
if (!m_generator) {
Logger::error("ChunkGenerator is Nullptr");
return;
}
m_generator->assign_chunk_biome();
m_seed = m_generator->chunk_seed();
}
void Chunk::gen_phase_two(const std::array<const Chunk*, 8>& 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<std::optional<HeightMapArray>, 8>& neighbor_heightmap,
const std::array<BiomeType, 8>& 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<std::optional<std::vector<BlockType>>, 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);
for (auto& data : m_vertex_data) {
data.upload();
}
// after fininshed it, can use
clear_dirty();
m_need_upload = false;
}
bool Chunk::is_dirty() const { return m_dirty.load(); }
void Chunk::mark_dirty() { m_dirty = true; }
void Chunk::clear_dirty() { m_dirty = false; }
bool Chunk::is_need_upload() const { return m_need_upload.load(); }
void Chunk::need_upload() { m_need_upload = true; }
void Chunk::set_chunk_block(int index, unsigned id) {
m_blocks[index] = id;
mark_dirty();
}
ChunkPos Chunk::chunk_pos() const { return m_chunk_pos; }
BiomeType Chunk::biome() const { return m_biome; }
void Chunk::biome(BiomeType b) { m_biome = b; }
HeightMapArray& Chunk::heightmap() { return m_heightmap; }
std::vector<BlockType>& Chunk::blocks() { return m_blocks; }
World& Chunk::world() { return m_world; }
unsigned Chunk::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;
}
/*
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<std::vector<BlockType>>&
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<size_t>(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<float>(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) {
// SIZE_X=SIZE_Z=CHUNK_SIZE=16, SIZE_Y=WORLD_SIZE_Y=256
// Axis order: axis 0=X, 1=Y, 2=Z
// Two slice dimensions of each axis
const int DIMS[3] = {CHUNK_SIZE, WORLD_SIZE_Y, CHUNK_SIZE};
// Maximum mask size: max(16*256, 16*16) = 4096
static thread_local FaceKey mask[CHUNK_SIZE * WORLD_SIZE_Y];
static thread_local bool visited[CHUNK_SIZE * WORLD_SIZE_Y];
for (int axis = 0; axis < 3; axis++) {
int u_axis = (axis + 1) % 3; // horizontal
int v_axis = (axis + 2) % 3; // vertical
int u = DIMS[u_axis];
int v = DIMS[v_axis];
int d = DIMS[axis]; // Depth along the normal axis
for (int face_dir : {1, -1}) {
int face_idx = axis_dir_to_face(axis, face_dir);
for (int layer = 0; layer < d; layer++) {
// ── 1. Build mask ──────────────────────────────────────────
for (int vi = 0; vi < v; vi++) {
for (int ui = 0; ui < u; ui++) {
// Current cell local coordinates
int lpos[3];
lpos[axis] = layer;
lpos[u_axis] = ui;
lpos[v_axis] = vi;
// Neighbor (offset one cell along the normal direction)
int npos[3];
npos[axis] = layer + face_dir;
npos[u_axis] = ui;
npos[v_axis] = vi;
BlockType cur_id = get_block_safe(
lpos[0], lpos[1], lpos[2], m_chunk_pos, m_blocks,
neighbor_block);
// Air / cross plane are not involved in greedy meshing
if (cur_id == 0 ||
BlockManager::is_cross_plane(cur_id)) {
mask[vi * u + ui] = {};
continue;
}
BlockType nb_id = get_block_safe(
npos[0], npos[1], npos[2], m_chunk_pos, m_blocks,
neighbor_block);
if (is_face_culled(cur_id, nb_id)) {
mask[vi * u + ui] = {};
} else {
mask[vi * u + ui] = {cur_id, face_idx};
}
}
}
// ── 2. Greedy Merge ──────────────────────────────────────
std::fill(visited, visited + u * v, false);
for (int vi = 0; vi < v; vi++) {
for (int ui = 0; ui < u; ui++) {
if (visited[vi * u + ui])
continue;
FaceKey cur = mask[vi * u + ui];
if (!cur.valid())
continue;
// Extend width in the u direction
int w = 1;
while (ui + w < u && !visited[vi * u + (ui + w)] &&
mask[vi * u + (ui + w)] == cur) {
w++;
}
// Extend height in the v direction
int h = 1;
bool can_expand = true;
while (vi + h < v && can_expand) {
for (int k = 0; k < w; k++) {
int idx = (vi + h) * u + (ui + k);
if (visited[idx] || mask[idx] != cur) {
can_expand = false;
break;
}
}
if (can_expand)
h++;
}
// mark visited
for (int dv = 0; dv < h; dv++)
for (int du = 0; du < w; du++)
visited[(vi + dv) * u + (ui + du)] = true;
// output quad
emit_quad(axis, face_dir, layer, ui, vi, w, h, u_axis,
v_axis, cur);
}
}
}
}
}
for (int x = 0; x < CHUNK_SIZE; x++) {
for (int y = 0; y < WORLD_SIZE_Y; y++) {
for (int z = 0; z < CHUNK_SIZE; z++) {
BlockType id = m_blocks[index(x, y, z)];
if (id != 0 && BlockManager::is_cross_plane(id)) {
int world_x = x + m_chunk_pos.x * CHUNK_SIZE;
int world_z = z + m_chunk_pos.z * CHUNK_SIZE;
gen_cross_plane_vertices(world_x, y, world_z, id);
}
}
}
}
}
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) {
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);
// Offsets of the four corners along the u_axis/v_axis
int su[4] = {0, w, w, 0};
int sv[4] = {0, 0, h, h};
// Each face's UV: directly read from the four corners of TEX_COORDS, then
// scaled by w/h TEX_COORDS vertex order: 0=BL, 1=TL, 2=TR, 3=TR, 4=BR, 5=BL
// (two triangles) Four unique corners correspond to indices: BL=0, TL=1,
// TR=2, BR=4 Extract the UVs of the four corners from TEX_COORDS (unique
// corners after removing duplicate vertices) Vertices 0,1,2,4 correspond to
// BL, TL, TR, BR
float u0 = TEX_COORDS[key.face][0][0]; // BL.u
float v0 = TEX_COORDS[key.face][0][1]; // BL.v
float u1 = TEX_COORDS[key.face][4][0]; // BR.u
float v1 = TEX_COORDS[key.face][4][1]; // BR.v
float u3 = TEX_COORDS[key.face][1][0]; // TL.u
float v3 = TEX_COORDS[key.face][1][1]; // TL.v
float du_u = u1 - u0; // Change in u when su increases (per block)
float dv_u = v1 - v0;
float du_v = u3 - u0; // Change in u when sv increases
float dv_v = v3 - v0;
float uvs[4][2] = {
{u0, v0}, // (0, 0 )
{u0 + du_u * (float)w, v0 + dv_u * (float)w}, // (w, 0 )
{u0 + du_u * (float)w + du_v * (float)h,
v0 + dv_u * (float)w + dv_v * (float)h}, // (w, h )
{u0 + du_v * (float)h, v0 + dv_v * (float)h}, // (0, h )
};
int tri[6] = {0, 1, 2, 0, 2, 3};
float pos[4][3];
for (int c = 0; c < 4; c++) {
pos[c][axis] = axis_val;
pos[c][u_axis] = (float)(i + su[c]);
pos[c][v_axis] = (float)(j + sv[c]);
pos[c][0] += wx_base;
pos[c][2] += wz_base;
}
float layer_id = (float)(key.block_id * 6 + key.face);
float roughness = BlockManager::roughness(key.block_id);
int buf = choose_buf(key.block_id);
for (int vi = 0; vi < 6; vi++) {
int c = tri[vi];
Vertex3D vex = {
pos[c][0],
pos[c][1],
pos[c][2],
uvs[c][0],
uvs[c][1],
layer_id,
NORMALS[key.face][0][0],
NORMALS[key.face][0][1],
NORMALS[key.face][0][2],
roughness,
TANGENTS[key.face][0][0],
TANGENTS[key.face][0][1],
TANGENTS[key.face][0][2],
};
m_vertex_data[buf].m_vertices.emplace_back(vex);
}
}
void Chunk::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,
world_y, world_z, id);
return;
}
for (int face = 0; face < 2; face++) {
for (int i = 0; i < 6; i++) {
Vertex3D vex = {
CROSS_VERTICES_POS[face][i][0] + (float)world_x * 1.0f,
CROSS_VERTICES_POS[face][i][1] + (float)world_y * 1.0f,
CROSS_VERTICES_POS[face][i][2] + (float)world_z * 1.0f,
CROSS_TEX_COORDS[face][i][0],
CROSS_TEX_COORDS[face][i][1],
static_cast<float>(BlockManager::cross_plane_index(id)),
CROSS_NORMALS[face][i][0],
CROSS_NORMALS[face][i][1],
CROSS_NORMALS[face][i][2],
BlockManager::roughness(id),
CROSS_TANGENTS[face][i][0],
CROSS_TANGENTS[face][i][1],
CROSS_TANGENTS[face][i][2]
};
m_vertex_data[1].m_vertices.emplace_back(vex);
}
}
}
void Chunk::gen_chunk() {
if (m_gening.exchange(true))
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<Chunk> 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();
OptionalBlockVectorArray neightbor_blocks;
for (int i = 0; i < 4; i++) {
neightbor_blocks[i] = neighbor[i].get_chunk_blocks();
}
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);
// 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;
}
// 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

View File

@@ -0,0 +1,544 @@
#include "Cubed/config.hpp"
#include "Cubed/debug_collector.hpp"
#include "Cubed/gameplay/player.hpp"
#include "Cubed/gameplay/world.hpp"
#include "Cubed/tools/log.hpp"
#include <GLFW/glfw3.h>
namespace Cubed {
Player::Player(World& world, const std::string& name)
: m_name(name), m_world(world) {
hot_reload();
}
Player::~Player() {}
AABB Player::get_aabb() const {
float half_width = m_size.x / 2.0f;
float half_depth = m_size.z / 2.0f;
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};
return AABB{min, max};
}
const glm::vec3& Player::get_front() const { return m_front; }
const Gait& Player::get_gait() const { return m_gait; }
const std::optional<LookBlock>& Player::get_look_block_pos() const {
return m_look_block;
}
const glm::vec3& Player::get_player_pos() const { return m_player_pos; }
const MoveState& Player::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) {
glm::vec3 dir = glm::normalize(front);
// float step = 0.1f;
glm::ivec3 cur = glm::floor(start);
int ix = cur.x;
int iy = cur.y;
int iz = cur.z;
// step direction
int step_x = (dir.x > 0) ? 1 : ((dir.x < 0) ? -1 : 0);
int step_y = (dir.y > 0) ? 1 : ((dir.y < 0) ? -1 : 0);
int step_z = (dir.z > 0) ? 1 : ((dir.z < 0) ? -1 : 0);
static const float INF = std::numeric_limits<float>::infinity();
float t_delta_x = (dir.x != 0) ? std::fabs(1.0f / dir.x) : INF;
float t_delta_y = (dir.y != 0) ? std::fabs(1.0f / dir.y) : INF;
float t_delta_z = (dir.z != 0) ? std::fabs(1.0f / dir.z) : INF;
float t_max_x, t_max_y, t_max_z;
if (dir.x > 0) {
t_max_x = (static_cast<float>(ix) + 1.0f - start.x) / dir.x;
} else if (dir.x < 0) {
t_max_x = (start.x - static_cast<float>(ix)) / (-dir.x);
} else {
t_max_x = INF;
}
if (dir.y > 0) {
t_max_y = (static_cast<float>(iy) + 1.0f - start.y) / dir.y;
} else if (dir.y < 0) {
t_max_y = (start.y - static_cast<float>(iy)) / (-dir.y);
} else {
t_max_y = INF;
}
if (dir.z > 0) {
t_max_z = (static_cast<float>(iz) + 1.0f - start.z) / dir.z;
} else if (dir.z < 0) {
t_max_z = (start.z - static_cast<float>(iz)) / (-dir.z);
} else {
t_max_z = INF;
}
float t = 0.0f;
normal = glm::vec3(0.0f, 0.0f, 0.0f);
while (t <= distance) {
if (m_world.is_solid(glm::ivec3(ix, iy, iz))) {
block_pos = glm::ivec3(ix, iy, iz);
return true;
}
if (t_max_x < t_max_y && t_max_x < t_max_z) {
t = t_max_x;
t_max_x += t_delta_x;
normal = glm::vec3(-step_x, 0.0f, 0.0f);
ix += step_x;
} else if (t_max_y < t_max_z) {
t = t_max_y;
t_max_y += t_delta_y;
normal = glm::vec3(0.0f, -step_y, 0.0f);
iy += step_y;
} else {
t = t_max_z;
t_max_z += t_delta_z;
normal = glm::vec3(0.0f, 0.0f, -step_z);
iz += step_z;
}
}
return false;
}
void Player::change_mode(GameMode mode) {
m_game_mode = mode;
Logger::info("Change GameMode to {}", to_str(mode));
if (mode == CREATIVE) {
is_fly = false;
m_gait = Gait::WALK;
} else if (mode == SPECTATOR) {
is_fly = true;
m_gait = Gait::RUN;
m_max_speed = m_max_run_speed;
}
}
void Player::hot_reload() {
auto& config = Config::get();
m_sensitivity =
static_cast<float>(config.get<double>("player.mouse_sensitivity"));
}
void Player::set_player_pos(const glm::vec3& pos) { m_player_pos = pos; }
void Player::set_place_block(unsigned id) { m_place_block = id; }
void Player::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}",
m_player_pos.x, m_player_pos.y,
m_player_pos.z));
DebugCollector::get().report("speed",
std::format("Speed: {:.2} m/s", m_xz_speed));
}
void Player::update_player_move_state(int key, int action) {
switch (key) {
case GLFW_KEY_W:
if (action == GLFW_PRESS) {
m_move_state.forward = true;
}
if (action == GLFW_RELEASE) {
m_move_state.forward = false;
if (m_game_mode != SPECTATOR) {
m_gait = Gait::WALK;
}
}
break;
case GLFW_KEY_S:
if (action == GLFW_PRESS) {
m_move_state.back = true;
}
if (action == GLFW_RELEASE) {
m_move_state.back = false;
}
break;
case GLFW_KEY_A:
if (action == GLFW_PRESS) {
m_move_state.left = true;
}
if (action == GLFW_RELEASE) {
m_move_state.left = false;
}
break;
case GLFW_KEY_D:
if (action == GLFW_PRESS) {
m_move_state.right = true;
}
if (action == GLFW_RELEASE) {
m_move_state.right = false;
}
break;
case GLFW_KEY_SPACE:
if (action == GLFW_PRESS) {
m_move_state.up = true;
if (space_on) {
if (m_game_mode == CREATIVE) {
is_fly = !is_fly ? true : false;
m_y_speed = 0.0f;
}
space_on = false;
space_on_time = 0.0f;
} else {
space_on = true;
}
}
if (action == GLFW_RELEASE) {
m_move_state.up = false;
}
break;
case GLFW_KEY_LEFT_SHIFT:
if (action == GLFW_PRESS) {
m_move_state.down = true;
}
if (action == GLFW_RELEASE) {
m_move_state.down = false;
}
break;
case GLFW_KEY_LEFT_CONTROL:
if (action == GLFW_PRESS) {
m_gait = Gait::RUN;
}
break;
case GLFW_KEY_F4:
if (action == GLFW_PRESS) {
if (m_game_mode == CREATIVE) {
change_mode(SPECTATOR);
} else {
change_mode(CREATIVE);
}
}
break;
}
}
void Player::update_front_vec(float offset_x, float offset_y) {
m_yaw += offset_x * m_sensitivity;
m_pitch += offset_y * m_sensitivity;
m_yaw = std::fmod(m_yaw, 360.0);
m_pitch = std::clamp(m_pitch, -89.0f, 89.0f);
m_front.x = sin(glm::radians(m_yaw)) * cos(glm::radians(m_pitch));
m_front.y = sin(glm::radians(m_pitch));
m_front.z = -cos(glm::radians(m_yaw)) * cos(glm::radians(m_pitch));
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() {
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);
glm::vec3 move_dir_right = glm::vec3(0.0f);
glm::vec3 move_dir = glm::vec3(0.0f);
if (m_move_state.forward) {
move_dir_front += glm::normalize(glm::vec3(m_front.x, 0.0f, m_front.z));
}
if (m_move_state.back) {
move_dir_front -= glm::normalize(glm::vec3(m_front.x, 0.0f, m_front.z));
}
if (m_move_state.left) {
move_dir_right -= glm::normalize(glm::vec3(m_right.x, 0.0f, m_right.z));
}
if (m_move_state.right) {
move_dir_right += glm::normalize(glm::vec3(m_right.x, 0.0f, m_right.z));
}
move_dir = move_dir_front + move_dir_right;
if (glm::length(move_dir) > 0.001f) {
direction = glm::normalize(move_dir);
}
}
void Player::update_lookup_block() {
// calculate the block that is looked
glm::ivec3 block_pos;
glm::vec3 block_normal;
if (ray_cast(
glm::vec3(m_player_pos.x, (m_player_pos.y + 1.6f), m_player_pos.z),
m_front, block_pos, block_normal)) {
m_look_block = LookBlock{block_pos, glm::floor(block_normal)};
} else {
m_look_block = std::nullopt;
}
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);
}
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<float>(x),
static_cast<float>(y),
static_cast<float>(z)},
glm::vec3{static_cast<float>(x + 1),
static_cast<float>(y + 1),
static_cast<float>(z + 1)}};
AABB player_box = get_aabb();
if (!player_box.intersects(block_box)) {
m_world.set_block(near_pos, m_place_block);
}
}
Input::get_input_state().mouse_state.right = false;
}
}
}
void Player::update_move(float delta_time) {
// if frame rate less than 1 frame per second, don't update
if (delta_time > 1.0f) {
return;
}
if (m_game_mode != SPECTATOR) {
if (m_gait == Gait::RUN) {
m_max_speed = m_max_run_speed;
}
if (m_gait == Gait::WALK) {
m_max_speed = m_max_walk_speed;
}
}
if (space_on) {
space_on_time += delta_time;
if (space_on_time >= MAX_SPACE_ON_TIME) {
space_on = false;
space_on_time = 0.0f;
}
}
// calculate speed
if (m_move_state.forward || m_move_state.back || m_move_state.left ||
m_move_state.right || m_move_state.up) {
direction = glm::vec3(0.0f, 0.0f, 0.0f);
m_xz_speed += m_acceleration * delta_time;
if (m_xz_speed > m_max_speed) {
m_xz_speed = m_max_speed;
}
} else {
m_xz_speed += -m_deceleration * delta_time;
if (m_xz_speed < 0) {
m_xz_speed = 0;
direction = glm::vec3(0.0f, 0.0f, 0.0f);
}
}
update_direction();
move_distance = {direction.x * m_xz_speed * delta_time, 0.0f,
direction.z * m_xz_speed * delta_time};
if (is_fly) {
if (m_move_state.up) {
m_y_speed = m_fly_y_speed;
}
if (m_move_state.down) {
m_y_speed = -m_fly_y_speed;
}
if (!m_move_state.down && !m_move_state.up) {
m_y_speed = 0.0f;
}
} else {
if (m_move_state.up && can_up) {
m_y_speed = 7.5;
can_up = false;
}
m_y_speed += -m_g * delta_time;
}
move_distance.y = m_y_speed * delta_time;
// y
update_y_move();
// x
update_x_move();
update_z_move();
if (m_player_pos.y < -15.0f) {
Logger::warn("y is tow low");
m_player_pos += glm::vec3(1.0f, 100.0f, 1.0f);
}
}
void Player::update_x_move() {
m_player_pos.x += move_distance.x;
if (m_game_mode == SPECTATOR) {
return;
}
AABB player_box = get_aabb();
int minx = std::floor(player_box.min.x);
int maxx = std::floor(player_box.max.x);
int miny = std::floor(player_box.min.y);
int maxy = std::floor(player_box.max.y);
int minz = std::floor(player_box.min.z);
int maxz = std::floor(player_box.max.z);
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<float>(x),
static_cast<float>(y),
static_cast<float>(z)},
glm::vec3{static_cast<float>(x + 1),
static_cast<float>(y + 1),
static_cast<float>(z + 1)}};
if (player_box.intersects(block_box)) {
m_gait = Gait::WALK;
m_player_pos.x -= move_distance.x;
return;
}
}
}
}
}
}
void Player::update_y_move() {
m_player_pos.y += move_distance.y;
if (m_game_mode == SPECTATOR) {
return;
}
AABB player_box = get_aabb();
int minx = std::floor(player_box.min.x);
int maxx = std::floor(player_box.max.x);
int miny = std::floor(player_box.min.y);
int maxy = std::floor(player_box.max.y);
int minz = std::floor(player_box.min.z);
int maxz = std::floor(player_box.max.z);
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<float>(x),
static_cast<float>(y),
static_cast<float>(z)},
glm::vec3{static_cast<float>(x + 1),
static_cast<float>(y + 1),
static_cast<float>(z + 1)}};
if (player_box.intersects(block_box)) {
m_player_pos.y -= move_distance.y;
m_y_speed = 0.0f;
if (move_distance.y < 0) {
can_up = true;
is_fly = false;
}
return;
}
}
}
}
}
}
void Player::update_z_move() {
m_player_pos.z += move_distance.z;
if (m_game_mode == SPECTATOR) {
return;
}
AABB player_box = get_aabb();
int minx = std::floor(player_box.min.x);
int maxx = std::floor(player_box.max.x);
int miny = std::floor(player_box.min.y);
int maxy = std::floor(player_box.max.y);
int minz = std::floor(player_box.min.z);
int maxz = std::floor(player_box.max.z);
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<float>(x),
static_cast<float>(y),
static_cast<float>(z)},
glm::vec3{static_cast<float>(x + 1),
static_cast<float>(y + 1),
static_cast<float>(z + 1)}};
if (player_box.intersects(block_box)) {
m_gait = Gait::WALK;
m_player_pos.z -= move_distance.z;
return;
}
}
}
}
}
}
void Player::update_scroll(double yoffset) {
if (m_game_mode == SPECTATOR) {
if (yoffset > 0) {
if (m_max_speed < 500.0f) {
m_max_speed += 1.0f;
}
} else {
if (m_max_speed > 1.0f) {
m_max_speed -= 1.0f;
}
}
}
if (m_game_mode == CREATIVE) {
if (yoffset < 0) {
m_place_block += 1;
if (m_place_block >= BlockManager::sums()) {
m_place_block = 1;
}
} else {
m_place_block -= 1;
if (m_place_block <= 0) {
m_place_block = BlockManager::sums() - 1;
}
}
}
}
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

View File

@@ -0,0 +1,702 @@
#include "Cubed/config.hpp"
#include "Cubed/gameplay/player.hpp"
#include "Cubed/gameplay/world.hpp"
#include "Cubed/tools/cubed_assert.hpp"
#include "Cubed/tools/cubed_hash.hpp"
#include <glm/gtc/constants.hpp>
#include <numbers>
#include <utility>
using namespace std::chrono;
using namespace std::chrono_literals;
namespace Cubed {
struct ChunkRenderData {
std::array<const std::vector<BlockType>*, 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<LookBlock>&
World::get_look_block_pos(const std::string& name) const {
static std::optional<LookBlock> 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<std::chrono::milliseconds>(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<ChunkPos> 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<ChunkPos>& 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<std::pair<ChunkPos, PendingChunk*>> 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<const ChunkPos, PendingChunk>& 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<std::mutex> 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<float>(pos.x * CHUNK_SIZE) +
static_cast<float>(CHUNK_SIZE / 2),
static_cast<float>(WORLD_SIZE_Y / 2),
static_cast<float>(pos.z * CHUNK_SIZE) +
static_cast<float>(CHUNK_SIZE / 2)),
glm::vec3(static_cast<float>(CHUNK_SIZE / 2),
static_cast<float>(WORLD_SIZE_Y / 2),
static_cast<float>(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<int>("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<float>(m_day_tick) / DAY_TIME;
float azimuth = glm::radians(90.0f - t * 360.0f);
float altitude =
glm::half_pi<float>() * sin((t - 0.25f) * glm::two_pi<float>());
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<float>(DAY_TIME / 2) * std::numbers::pi) *
90.0f;
float t = static_cast<float>(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<glm::vec4>& World::planes() { return m_planes; }
std::vector<ChunkRenderSnapshot>& 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<ThreadPool>(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

View File

@@ -13,9 +13,7 @@ ServerChunk::ServerChunk(ServerChunk&& other) noexcept
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");
}
m_seed(other.m_seed), m_conditions(other.m_conditions) {}
ServerChunk& ServerChunk::operator=(ServerChunk&& other) noexcept {
// Logger::info("other Chunk pos {} {} in Chunk& Chunk::operator=(Chunk&&
@@ -24,7 +22,6 @@ ServerChunk& ServerChunk::operator=(ServerChunk&& other) noexcept {
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);
@@ -148,13 +145,10 @@ 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<ServerChunk> neighbor;
for (int i = 0; i < 4; i++) {
@@ -175,7 +169,6 @@ void ServerChunk::gen_chunk() {
}
gen_phase_four(m_neightbor_blocks);
gen_phase_five();
m_gening = false;
}
// Logger::info("Cross Sum {}", m_cross_vertices_sum.load());

View File

@@ -31,26 +31,4 @@ bool ServerPlayer::is_disconnect(TickType current_gametick) const {
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

View File

@@ -7,169 +7,44 @@
#include "Cubed/tools/log.hpp"
#include "Cubed/tools/uuid.hpp"
#include <ranges>
#include <utility>
using namespace std::chrono;
using namespace std::chrono_literals;
using namespace google::protobuf;
namespace Cubed {
ServerWorld::ServerWorld() {}
ServerWorld::~ServerWorld() { stop(); }
void ServerWorld::stop() {
ServerWorld::~ServerWorld() {
if (!m_init) {
return;
}
if (m_stopped.exchange(true)) {
return;
}
send_server_stop();
stop_gen_thread();
stop_server_thread();
// wait_all_chunk_tasks();
wait_all_chunk_tasks();
stop_thread_pool();
m_finished_queue.clear();
m_chunks.clear();
{
std::lock_guard lock(m_chunks_mutex);
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::wait_all_chunk_tasks() {
std::lock_guard lock(m_new_chunk_mutex);
for (auto& [pos, task] : m_new_chunks) {
task.future.get();
}
}
void ServerWorld::send_time() {
Arena arena;
auto* rsp = Arena::Create<UpdateTime>(&arena);
rsp->set_day_tick(m_day_tick);
rsp->set_game_tick(m_game_ticks);
UpdateTime rsp;
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);
player.get_session()->send(make_packet(rsp));
}
}
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<ChunkDataRsp>(&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<std::vector<BlockType>>& 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<Session> 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]() {
@@ -186,17 +61,10 @@ void ServerWorld::init_world() {
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);
m_chunks.reserve(MAX_DISTANCE * MAX_DISTANCE * 4);
start_thread_pool();
auto t1 = std::chrono::system_clock::now();
@@ -213,28 +81,18 @@ void ServerWorld::init_world() {
void ServerWorld::init_chunks() { hot_reload(); }
void ServerWorld::gen_chunks_internal(const std::string& uuid) {
void ServerWorld::gen_chunks_internal(std::optional<std::string> uuid) {
// Logger::info("gen_chunks_internal");
m_chunk_gen_finished = false;
ChunkPosSet required_chunks_set;
compute_required_chunks(required_chunks_set, uuid);
ChunkPosSet required_chunks;
compute_required_chunks(required_chunks, uuid);
ASSERT_MSG(!required_chunks.empty(), "required chunks is empty!!");
std::vector<ChunkPos> 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!!");
sync_and_collect_missing_chunks(need_gen_chunks_pos, required_chunks);
Logger::info("New Gen Chunks Sum: {}", need_gen_chunks_pos.size());
@@ -243,16 +101,14 @@ void ServerWorld::gen_chunks_internal(const std::string& uuid) {
return;
}
NewChunkVector new_chunks;
// Create new chunk
for (auto& pos : need_gen_chunks_pos) {
new_chunks.emplace_back(
pos, std::make_unique<ServerChunk>(ServerChunk(*this, pos)));
{
std::lock_guard lock(m_new_chunk_mutex);
for (auto& pos : need_gen_chunks_pos) {
m_new_chunks.emplace(pos, ServerChunk(*this, pos));
}
}
submit_new_chunks(uuid, new_chunks);
submit_new_chunks(uuid);
m_chunk_gen_finished = true;
}
@@ -279,48 +135,57 @@ void ServerWorld::compute_required_chunks(
}
}
}
void ServerWorld::sync_and_collect_missing_chunks(
std::vector<ChunkPos>& 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.unsafe_erase(it);
} else {
++it;
}
}
for (auto pos : required_chunks) {
chunk_acc acc;
if (m_chunks.insert(acc, pos)) {
auto it = m_chunks.find(pos);
if (it == m_chunks.end()) {
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) {
void ServerWorld::submit_new_chunks(const std::optional<std::string>& uuid) {
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:
// Enqueue directly in random order
for (auto& task : new_chunks) {
pool_ptr->enqueue([&task, this]() {
std::unique_ptr<ServerChunk> chunk{std::move(task.chunk)};
chunk->gen_chunk();
m_finished_queue.push(std::move(chunk));
});
for (auto& [pos, task] : m_new_chunks) {
if (!task.future.valid()) {
task.future =
pool_ptr->enqueue([&task]() { task.chunk.gen_chunk(); });
}
}
break;
case CENTER: {
std::vector<std::pair<ChunkPos, PendingChunk*>> tasks;
for (auto& task : new_chunks) {
tasks.emplace_back(task.pos, &task);
for (auto& [pos, task] : m_new_chunks) {
if (!task.future.valid()) {
tasks.emplace_back(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) {
glm::vec3 player_pos;
if (uuid == std::nullopt) {
player_pos = glm::vec3{0.0f};
} else {
player_pos = get_player_pos(uuid.value());
}
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;
@@ -330,20 +195,34 @@ void ServerWorld::submit_new_chunks(const std::string& uuid,
[&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<int>(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));
});
for (auto& [pos, task] : tasks) {
if (!task->future.valid()) {
task->future =
pool_ptr->enqueue([task]() { task->chunk.gen_chunk(); });
}
}
} break;
}
}
}
void ServerWorld::poll_finished_chunks() {
m_new_finished_chunk.clear();
std::lock_guard lock(m_new_chunk_mutex);
std::erase_if(
m_new_chunks, [&](std::pair<const ChunkPos, PendingChunk>& 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;
});
}
void ServerWorld::start_gen_thread() {
@@ -364,10 +243,10 @@ void ServerWorld::start_gen_thread() {
break;
}
m_need_gen_chunk = false;
std::string uuid;
std::optional<std::string> uuid{std::nullopt};
if (!m_need_gen_queue.empty()) {
uuid = m_need_gen_queue.front();
m_need_gen_queue.pop();
m_need_gen_queue.pop_front();
}
lk.unlock();
gen_chunks_internal(uuid);
@@ -382,19 +261,10 @@ void ServerWorld::start_server_thread() {
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);
if (m_pool_threads == 0) {
change_pool_threads(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);
change_pool_threads(m_pool_threads);
}
}
@@ -421,36 +291,28 @@ void ServerWorld::stop_thread_pool() {
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");
Logger::info("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;
auto t1 = system_clock::now();
if (m_tick_running) {
++m_game_ticks;
m_day_tick = (m_day_tick + 1) % DAY_TIME;
}
update();
std::this_thread::sleep_until(next);
auto t2 = system_clock::now();
auto dt = duration_cast<microseconds>(t2 - t1);
auto st = std::max(dt, milliseconds(m_per_tick_time) - dt);
std::this_thread::sleep_for(st);
}
Logger::info("Server Thread Stopped!");
}
void ServerWorld::need_gen(std::string uuid) {
void ServerWorld::need_gen(std::optional<std::string> uuid) {
// if (!m_could_gen) {
// Logger::warn("It is generating or consuming new chunks");
@@ -459,9 +321,9 @@ void ServerWorld::need_gen(std::string uuid) {
m_could_gen = false;
{
if (uuid) {
std::lock_guard lock(m_need_gen_queue_mutex);
m_need_gen_queue.enqueue(std::move(uuid));
m_need_gen_queue.push_back(*uuid);
}
// m_gen_player_pos = get_player("TestPlayer").get_player_pos();
@@ -479,14 +341,13 @@ bool ServerWorld::set_block(const glm::ivec3& block_pos, unsigned id) {
world_z = block_pos.z;
auto [chunk_x, chunk_z] = get_chunk_pos(world_x, world_z);
chunk_acc acc;
std::lock_guard lk(m_chunks_mutex);
auto it = m_chunks.find(ChunkPos{chunk_x, chunk_z});
if (!m_chunks.find(acc, ChunkPos{chunk_x, chunk_z})) {
return false;
}
if (acc->second.state != ChunkState::READY) {
if (it == m_chunks.end()) {
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 ||
@@ -494,7 +355,7 @@ bool ServerWorld::set_block(const glm::ivec3& block_pos, unsigned id) {
return false;
}
acc->second.chunk->set_chunk_block(ServerChunk::index(x, y, z), id);
it->second.set_chunk_block(ServerChunk::index(x, y, z), id);
return true;
}
@@ -502,35 +363,48 @@ void ServerWorld::hot_reload() {
auto& config = Config::get();
int dist = config.get<int>("world.rendering_distance");
m_rendering_distance = dist <= MAX_DISTANCE ? dist : MAX_DISTANCE;
need_gen(std::nullopt);
}
void ServerWorld::rebuild_world() {
if (m_is_rebuilding.exchange(true)) {
return;
}
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();
}
{
std::lock_guard lock(m_new_chunk_mutex);
m_new_chunks.clear();
}
m_could_gen = true;
ChunkGenerator::reload();
start_thread_pool();
start_gen_thread();
need_gen(std::nullopt);
m_is_rebuilding = false;
}
void ServerWorld::update() {
// poll_finished_chunks();
poll_finished_chunks();
{
std::lock_guard lk(m_chunks_mutex);
bool consumed = false;
std::unique_ptr<ServerChunk> 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;
for (auto& x : m_new_finished_chunk) {
m_chunks.emplace(x.first, std::move(x.second));
consumed = true;
}
if (consumed) {
m_could_gen = true;
}
}
send_time();
for (auto& [id, timer] : m_timers) {
timer.update();
@@ -551,7 +425,7 @@ void ServerWorld::sync_player_pos(const std::string& uuid, float x, float y,
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<std::shared_ptr<Session>> other;
{
@@ -560,25 +434,19 @@ void ServerWorld::sync_player_pos(const std::string& uuid, float x, float y,
if (o_uuid == uuid) {
continue;
}
if (player.has_player(pos)) {
other.emplace_back(player.get_session());
}
other.emplace_back(player.get_session());
}
}
for (auto& session : other) {
if (!session) {
continue;
}
Arena arena;
auto* rsp = Arena::Create<PlayerInfoRsp>(&arena);
rsp->set_uuid(uuid);
rsp->set_name(name);
auto* pos = rsp->mutable_pos();
PlayerInfoRsp rsp;
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);
session->send(make_packet(rsp));
}
}
@@ -586,59 +454,25 @@ void ServerWorld::handle_player_login(const std::string& name,
std::shared_ptr<Session> 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(
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<LoginRsp>(&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<ChunkPos> 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<LoginRsp>(&arena);
rsp->set_success(true);
rsp->set_uuid(uuid);
session->send(make_packet(*rsp), 0);
LoginRsp rsp;
rsp.set_success(true);
rsp.set_uuid(uuid);
session->send(make_packet(rsp));
}
void ServerWorld::handle_player_exit(const std::string& uuid) {
std::shared_ptr<Session> 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);
@@ -648,14 +482,6 @@ void ServerWorld::handle_player_exit(const std::string& uuid) {
m_uuid_to_name.erase(uuid);
update_ref_count(old_set, {});
Arena arena;
auto* rsp = Arena::Create<LogoutRsp>(&arena);
rsp->set_uuid(uuid);
rsp->set_server_stop(false);
exit_session->send(make_packet(*rsp), 0);
std::vector<std::shared_ptr<Session>> sessions;
{
std::shared_lock lock(m_player_mutex);
@@ -665,9 +491,9 @@ void ServerWorld::handle_player_exit(const std::string& uuid) {
}
for (auto& s : sessions) {
if (s) {
s->send(make_packet(*rsp), 0);
}
LogoutRsp rsp;
rsp.set_uuid(uuid);
s->send(make_packet(rsp));
}
}
@@ -681,56 +507,81 @@ glm::vec3 ServerWorld::get_player_pos(const std::string& uuid) const {
return it->second.get_pos();
}
void ServerWorld::handle_chunk_req(int task_id, const std::string& uuid,
ChunkPos pos) {
void ServerWorld::handle_chunk_req(const std::string& uuid, ChunkPos pos) {
ChunkDataRsp rsq;
auto* rsq_pos = rsq.mutable_pos();
rsq_pos->set_x(pos.x);
rsq_pos->set_z(pos.z);
{
std::shared_lock lock(m_chunks_mutex);
auto it = m_chunks.find(pos);
if (it == m_chunks.end()) {
return;
}
rsq.set_chunk_seed(it->second.seed());
rsq.set_biome_type(std::to_underlying(it->second.biome()));
auto* blocks = rsq.mutable_chunk_blocks();
auto& chunk_blocks = it->second.get_chunk_blocks();
blocks->Assign(chunk_blocks.begin(), chunk_blocks.end());
auto& neighbor_blocks = it->second.get_neightbor_blocks();
auto assign = [](auto* nb,
const std::optional<std::vector<BlockType>>& blocks) {
if (!blocks) {
return;
}
if (!nb) {
return;
}
nb->Assign(blocks->begin(), blocks->end());
};
auto* nb1 = rsq.mutable_neighbor_blocks_1();
auto* nb2 = rsq.mutable_neighbor_blocks_2();
auto* nb3 = rsq.mutable_neighbor_blocks_3();
auto* nb4 = rsq.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<Session> s;
{
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);
if (it != m_players.end()) {
s = it->second.get_session();
it->second.update_sync_gametick(m_game_ticks);
}
}
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())) {
if (!s) {
Logger::error("Player {} session not exist", uuid);
return;
}
Arena arena;
BlockChangeRsp* rsp = Arena::Create<BlockChangeRsp>(&arena);
auto* pos = rsp->mutable_pos();
s->send(make_packet(rsq));
}
void ServerWorld::handle_block_change(const BlockChangeReq& req) {
float x = req.pos().x();
float y = req.pos().y();
float z = req.pos().z();
if (!set_block(glm::ivec3(x, y, z), req.block())) {
return;
}
BlockChangeRsp rsp;
auto* pos = rsp.mutable_pos();
pos->set_x(x);
pos->set_y(y);
pos->set_z(z);
rsp->set_block(req.block());
std::vector<std::shared_ptr<Session>> sessions;
auto chunk_pos = get_chunk_pos(x, z);
rsp.set_block(req.block());
{
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));
auto session = player.get_session();
if (session) {
session->send(make_packet(rsp));
}
}
}
for (auto& x : sessions) {
if (x) {
x->send(make_packet(*rsp), 1);
}
}
}
int ServerWorld::rendering_distance() const {
@@ -755,58 +606,19 @@ 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::pool_threads() const { return m_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<std::shared_ptr<ThreadPool>>& thread_pool, int threads) {
void ServerWorld::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 = 1;
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);
thread_pool.store(std::make_shared<ThreadPool>(used_thread));
return used_thread;
m_gen_thread_pool.store(std::make_shared<ThreadPool>(used_thread));
m_pool_threads = used_thread;
}
int ServerWorld::change_pool_threads(
std::atomic<std::shared_ptr<PriorityThreadPool>>& 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<PriorityThreadPool>(used_thread));
return used_thread;
}
void ServerWorld::send_server_stop() {
Arena arena;
auto* rsp = Arena::Create<LogoutRsp>(&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());
}
@@ -824,6 +636,4 @@ void ServerWorld::set_chunk_load_style(int id) {
Logger::error("Can,t Find Chunk Load Style Id {}, Nothing Will Do", id);
}
int ServerWorld::chunk_size() const { return m_chunks.size(); }
} // namespace Cubed

View File

@@ -4,7 +4,7 @@
#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)
@@ -21,12 +21,11 @@ void Session::start() {
asio::detached);
}
void Session::send(std::shared_ptr<std::vector<uint8_t>> packet, int priority) {
asio::post(m_strand, [self = shared_from_this(), packet = std::move(packet),
priority]() mutable {
void Session::send(std::shared_ptr<std::vector<uint8_t>> packet) {
asio::post(m_strand, [self = shared_from_this(),
packet = std::move(packet)]() mutable {
bool idle = self->m_write_queue.empty();
self->m_write_queue.emplace(priority, self->m_sequence++,
std::move(packet));
self->m_write_queue.emplace_back(std::move(packet));
if (idle) {
self->do_write();
}
@@ -54,44 +53,43 @@ asio::awaitable<void> Session::read_loop() {
co_await asio::async_read(m_socket, asio::buffer(body_data),
asio::use_awaitable);
}
constexpr auto& to_num = std::to_underlying<PacketEnum>;
auto cmd_id = header.cmd;
Arena arena;
if (cmd_id == std::to_underlying(PacketEnum::LOGIN_REQ)) {
auto* req = Arena::Create<LoginReq>(&arena);
if (cmd_id == to_num(PacketEnum::LOGIN_REQ)) {
LoginReq req;
Logger::info("Session: Receive Login req");
if (decode_packet(*req, body_data, header)) {
m_server_world.handle_player_login(req->name(),
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<PlayerPos>(&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 == to_num(PacketEnum::PLAYER_POS)) {
PlayerPos pos;
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<ChunkDataReq>(&arena);
if (cmd_id == to_num(PacketEnum::CHUNK_DATA_REQ)) {
ChunkDataReq req;
// Logger::info("Session: Receive Chunk Data req");
if (decode_packet(*req, body_data, header)) {
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()));
req.uuid(), ChunkPos(req.pos().x(), req.pos().z()));
}
}
if (cmd_id == std::to_underlying(PacketEnum::BLOCK_CHANGE_REQ)) {
auto* req = Arena::Create<BlockChangeReq>(&arena);
if (cmd_id == to_num(PacketEnum::BLOCK_CHANGE_REQ)) {
BlockChangeReq req;
Logger::info("Session: Receive Block Change req");
if (decode_packet(*req, body_data, header)) {
m_server_world.handle_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<LogoutReq>(&arena);
if (decode_packet(*req, body_data, header)) {
m_server_world.handle_player_exit(req->uuid());
if (cmd_id == to_num(PacketEnum::LOGOUT_REQ)) {
LogoutReq req;
if (decode_packet(req, body_data, header)) {
m_server_world.handle_player_exit(req.uuid());
}
}
}
@@ -119,16 +117,15 @@ asio::awaitable<void> Session::read_loop() {
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),
m_socket, asio::buffer(*(m_write_queue.front())),
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();
self->m_write_queue.pop_front();
if (!self->m_write_queue.empty()) {
self->do_write();
}

View File

@@ -1,14 +1,5 @@
#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));

View File

@@ -15,6 +15,5 @@ message LogoutReq {
message LogoutRsp {
string uuid = 1;
bool server_stop = 2;
}

View File

@@ -3,23 +3,17 @@ syntax = "proto3";
import "common/chunk_pos.proto";
message ChunkDataReq {
int32 task_id = 1;
string uuid = 2;
ChunkPosNet pos = 3;
string uuid = 1;
ChunkPosNet pos = 2;
}
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;
}
ChunkPosNet pos = 1;
uint32 chunk_seed = 2;
int32 biome_type = 3;
repeated uint32 chunk_blocks = 4 [packed=true];
repeated uint32 neighbor_blocks_1 = 5 [packed=true];
repeated uint32 neighbor_blocks_2 = 6 [packed=true];
repeated uint32 neighbor_blocks_3 = 7 [packed=true];
repeated uint32 neighbor_blocks_4 = 8 [packed=true];
}

View File

@@ -115,9 +115,9 @@ void Renderer::init() {
#ifdef DEBUG_MODE
glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(
[](GLenum, GLenum, GLuint, GLenum, GLsizei, const GLchar* message,
const void*) {
Logger::log(Logger::Level::L_DEBUG, std::source_location::current(),
[](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(),
"GL Debug: {}", reinterpret_cast<const char*>(message));
},
nullptr);
@@ -276,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();
const auto& block_pos = m_world.get_look_block_pos("TestPlayer");
if (block_pos != std::nullopt) {
@@ -676,42 +676,37 @@ 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);
}
}
}
@@ -767,33 +762,28 @@ 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 (!snapshot) {
continue;
}
if (Math::is_aabb_in_frustum(snapshot->center, snapshot->half_extents,
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 (!snapshot) {
continue;
}
if (!Math::is_aabb_in_frustum(snapshot->center, snapshot->half_extents,
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
@@ -802,21 +792,18 @@ 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 (!snapshot) {
continue;
}
if (!Math::is_aabb_in_frustum(snapshot->center, snapshot->half_extents,
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);
}
}
}
@@ -866,20 +853,16 @@ 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 (!snapshot) {
continue;
}
if (!Math::is_aabb_in_frustum(snapshot->center, snapshot->half_extents,
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);
}
}
@@ -919,19 +902,16 @@ 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 (!snapshot) {
continue;
}
if (!Math::is_aabb_in_frustum(snapshot->center, snapshot->half_extents,
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);
}
}
@@ -969,8 +949,8 @@ void Renderer::render_player() {
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_m_mat = glm::translate(glm::mat4(1.0f),
player.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);

105
src/tools/math_tools.cpp Normal file
View File

@@ -0,0 +1,105 @@
#include "Cubed/tools/math_tools.hpp"
#include <algorithm>
#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>
namespace Cubed {
namespace Math {
void extract_frustum_planes(const glm::mat4& mvp_matrix,
std::vector<glm::vec4>& 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<glm::vec4>& 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<float>() * 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

View File

@@ -1,3 +0,0 @@
#pragma once
#define CUBED_VERSION "@CUBED_VERSION@"

View File

@@ -1,8 +0,0 @@
add_library(glad STATIC
src/glad.c
)
target_include_directories(glad
PUBLIC
include
)

View File

@@ -1,7 +0,0 @@
{
"dependencies": [
"protobuf",
"zstd"
],
"builtin-baseline": "a0400024711b283056538ac19ced80b91a83c24c"
}