feature: multiplayer (#25)

* build: add asio library

* feat(dev_panel): add Asio to about table

* refactor(gameplay): split Chunk into server/client variants and add networking

* feat(gameplay): add Session class and UUID generation utility

* 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.

* refactor(proto): restructure protobuf definitions and update build system

* 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.

* refactor(packet): serialize directly into packet buffer with ByteSizeLong

* 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.

* feat(server): add block change packet handling and increase reserved threads

* refactor(server): enhance thread safety and session management

* fix(session): catch std::exception in read_loop and log error

* fix(network-server): stop accept loop on shutdown flag

* fix(gameplay): correct unknown error log condition and missing chunk pos update

* feat(client): add client-side chunk, player, world and network classes

* feat(client-chunk): add greedy meshing, receive_chunk, and biome field

* feat(client_player): add name constructor param, remove chunk transition

* feat(gameplay): integrate network client and thread-safe player pos

* feat(gameplay): add block change sync and coordinate utils

* refactor(server): replace Chunk with ServerChunk

* 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.

* feat(gameplay): add time sync and network improvements

* fix: correct neighbor block indexing and add time update support

* 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.

* refactor(client_world): split pending queue into data and upload queues

* feat(client): add center-based chunk loading priority

* feat(app): add client/server CLI arguments and init guards

* feat(multiplayer): add rendering of other players as colored cubes

* feat(gameplay): add thread pool for asynchronous chunk data processing

* feat(gameplay): add player logout and disconnect handling

* fix(dev_panel): add protobuf to about table libraries

* build: add ZLIB dependency

* 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.

* refactor(network): extract packet header decoding into function

* feat(packet): implement zstd compression and refactor packet decoding

* perf(gameplay): optimize protobuf message allocation with Arena and limit thread pool size

Switch to Arena allocation for protobuf messages to reduce heap allocations. Pass ChunkDataRsp by value to enable move semantics. Cap thread pool size to hardware concurrency with max 4 threads.

* refactor(gameplay): use steady clock and sleep_until for tick loop timing

* refactor(gameplay): rename remote player types and add interpolation

* refactor(server_world): use thread pool for chunk request handling

* refactor(packet): replace if-constexpr chain with explicit template specializations

Refactor `get_packet_id()` by removing the if-constexpr chain and instead
providing explicit specializations for each packet type. This simplifies the
primary template (which now only contains a static assertion) and improves
compile-time dispatch clarity. The `always_false` helper is no longer needed
in the primary template.

* feat(gameplay): add packet to clear all chunks on server rebuild

Add new packet S2C_ClearAllChunks (ID 3005) to notify clients when the server rebuilds its world. Clients respond by clearing their chunk cache and requesting fresh chunks. Shift UPDATE_TIME packet ID to 3006 to accommodate.

* feat(dev_panel): split world tab; add chunk request logging and fix flag reset

* feat(tools): add RecentQueue to replace std::deque in server_world

* feat(gameplay): add task ID to chunk requests to discard stale responses

* refactor(block): move block source to gameplay directory

* feat(toml): add shared TOML utilities and handle ip.toml

* feat: add player name configuration and rename config file

- Add --player command-line argument and 'player' field in Arguments struct
- Rename ip.toml to server.toml and corresponding internal variable to 'server'
- Fix block.cpp source path in CMakeLists.txt

* build(cmake): modularize build configuration

* fix: build fail on windows

* refactor(renderer): rename Logger level enum value DEBUG to L_DEBUG

* refactor(world): rework chunk state machine and player chunk tracking

Introduce ChunkState enum and ChunkEntity struct to manage chunk lifecycle. Store chunks as shared_ptr to avoid move operations during generation. Add clear_unused_chunks to remove chunks not referenced by any player. Implement deferred chunk request queue for safe processing after generation completes. Update player chunk set during required chunk computation. Improve thread safety with mutexes on chunk and player maps. Fix m_gening flag not reset after generation and add assertions for correctness. Change need_gen to require a player UUID, removing std::optional. Add chunk_size query method for debugging.

* fix(server): ensure chunk is ready before sending or setting block

* refactor(server_world): separate gen and net thread pools

Introduce a second thread pool for network operations and a `ThreadPoolKind` enum to distinguish between gen and net pools. Rename `pool_threads()` to `gen_pool_threads()`, add `change_pool_threads()` overload that accepts the pool kind, and update the dev panel to use the gen pool. Adjust logging and initialization to handle both pools.

* feat(client_world): implement server exit acknowledgment with timeout

* feat(gameplay): implement server stop and client exit handling

Add server_stop flag to LogoutRsp protocol. Modify client_world to check for server stop or own logout to set exit flag. Add ServerWorld::stop() to broadcast stop and cleanly shut down. Refactor chunk ownership to unique_ptr. Remove name parameter from get_look_block_pos.

* fix(app): correct port validation and remove redundant TOML helper

* fix: address unused parameter warnings and missing port assignment

* refactor(gameplay): remove old pre_remove chunk, player, and world files

* refactor(server): replace chunk set with flat_hash_set and add ref count

- Replace std::unordered_set with absl::flat_hash_set for chunk position sets.
- Add ref_count field to ChunkEntity and implement update_ref_count().
- Remove clear_unused_chunks() and use ref counting for chunk lifetime.
- Add get_chunk_pos_set() accessors to ServerPlayer.

* fix(client-world): delay chunk request to wait for server central chunk generation

* refactor(gameplay): migrate chunk storage to tbb::concurrent_hash_map

* refactor(server_world): replace future-based chunk generation with queue

Remove `std::future` per-chunk tracking and poll_finished_chunks().
Instead, generation tasks push completed chunks to a concurrent queue
consumed during update. This eliminates wait_all_chunk_tasks() and
simplifies synchronization.

* refactor(client_world): use concurrent data structures for chunk management

Replace std::shared_mutex and std::unordered_set with tbb::concurrent_hash_map for chunk storage, tbb::concurrent_queue for pending uploads, and absl::flat_hash_set for player chunk positions. This improves thread safety and reduces locking overhead. Refactor receive_chunk and request_chunk to work with the new concurrent model.

* build(windows): add Nvidia and AMD GPU high performance exports

* fix(ci): update header file pattern in clang-format check

* style(clang-format): comment out AlignPPAndNotPP option

* chore: enable AlignPPAndNotPP in clang-format and pin clang version in CI

* ci(workflow): update format check to use LLVM 22

* ci(format-check): install clang-format-22

* perf(world): cull distant players from updates and rendering

Add distance2 utility function. On client, skip rendering other players if distance squared exceeds rendering distance. On server, only send player position and block change updates to players in relevant chunks.

* refactor(math_tools): convert free functions to inline in header

* refactor(dev_panel): remove seed editing and server rebuild world, move client rebuild

- Remove text editing for perlin seed and filter function
- Remove ServerWorld::rebuild_world() method and its atomic flag
- Move 'Rebuild World' button to client world tab
- Remove unused perlin_noise_input_buffer

* fix(server_world): floor block position coordinates in handle_block_change

* refactor(gameplay): pass new_chunks vector as parameter instead of member variable

* perf(client): improve chunk rendering performance with snapshots and threaded generation

* feat(tools): add PriorityThreadPool with priority scheduling

Implement PriorityThreadPool supporting task priorities and FIFO ordering for same priority.
Update ClientWorld to use the new pool with explicit priority for chunk operations.
Fix ThreadPool stop logic with atomic exchange and remove unnecessary lambda capture.

* feat(networking): add priority and sequence ordering to packet send queues

* feat(server-world): use priority thread pool for chunk generation

Introduce PriorityThreadPool to process chunk generation tasks with priorities based on their distance from the player. Closer chunks receive higher priority, improving responsiveness.

* refactor(client): extract AABB creation and add collision check on block place

Make ClientPlayer::get_aabb and ClientWorld::get_block_aabb static.
Change player size to static constexpr M_SIZE.
Use shared_mutex for concurrent reads of other players.
Check for collision with other players before placing a block.

* feat(build): add build-time version system via CMake configure_file

* ci(release): add release build workflow for windows
This commit is contained in:
zhenyan121
2026-07-02 14:51:34 +08:00
committed by GitHub
parent d5a12869e6
commit 30f843ba6b
729 changed files with 181385 additions and 2141 deletions

46
src/CMakeLists.txt Normal file
View File

@@ -0,0 +1,46 @@
target_sources(${PROJECT_NAME}
PRIVATE
main.cpp
app.cpp
debug_collector.cpp
camera.cpp
config.cpp
dev_panel.cpp
gameplay/biome.cpp
gameplay/chunk_generator.cpp
gameplay/tree.cpp
input.cpp
map_table.cpp
renderer.cpp
shader.cpp
texture_manager.cpp
tools/cubed_random.cpp
tools/shader_tools.cpp
tools/font.cpp
tools/perlin_noise.cpp
ui/text.cpp
window.cpp
gameplay/builders/biome_builder.cpp
gameplay/builders/plain_builder.cpp
gameplay/builders/mountain_builder.cpp
gameplay/builders/river_builder.cpp
gameplay/builders/desert_builder.cpp
gameplay/builders/forest_builder.cpp
gameplay/cave_carver.cpp
gameplay/cave_path.cpp
gameplay/builders/snowy_plain_builder.cpp
gameplay/river_worm.cpp
gameplay/river_path.cpp
gameplay/block.cpp
gameplay/vertex_data.cpp
gameplay/builders/ocean_builder.cpp
gameplay/network_server.cpp
gameplay/server_world.cpp
gameplay/client_world.cpp
gameplay/server_chunk.cpp
gameplay/client_chunk.cpp
gameplay/server_player.cpp
gameplay/client_player.cpp
gameplay/session.cpp
gameplay/network_client.cpp
)

View File

@@ -2,19 +2,23 @@
#include "Cubed/config.hpp"
#include "Cubed/debug_collector.hpp"
#include "Cubed/gameplay/player.hpp"
#include "Cubed/tools/arg_parser.hpp"
#include "Cubed/tools/cubed_assert.hpp"
#include "Cubed/tools/log.hpp"
#include "Cubed/tools/system_info.hpp"
#include "version.hpp"
#include <exception>
#include <imgui_impl_glfw.h>
namespace Cubed {
App::App() {}
App::~App() {}
App::~App() {
if (m_client) {
m_client->stop();
}
}
void App::cursor_position_callback(GLFWwindow* window, double xpos,
double ypos) {
ImGuiIO& io = ImGui::GetIO();
@@ -30,9 +34,13 @@ void App::cursor_position_callback(GLFWwindow* window, double xpos,
app->m_camera.update_cursor_position_camera(xpos, ypos);
}
}
void App::init() {
void App::init(int argc, char** argv) {
handle_toml();
handle_argument(argc, argv);
m_window.init();
m_window.imgui_init();
Logger::info("Window Init Success");
glfwSetWindowUserPointer(m_window.get_glfw_window(), this);
@@ -50,6 +58,7 @@ void App::init() {
glfwSetCursorEnterCallback(m_window.get_glfw_window(),
cursor_enter_callback);
glfwSetCharCallback(m_window.get_glfw_window(), char_callback);
ChunkGenerator::init();
BlockManager::init();
m_renderer.init();
@@ -58,13 +67,94 @@ void App::init() {
// MapTable::init_map();
m_texture_manager.init_texture();
Logger::info("Texture Load Success");
m_world.init_world();
if (!m_argument.is_client) {
m_server.start_server(m_argument.port);
}
m_client = std::make_shared<NetworkClient>(m_client_world);
m_client->start(m_argument.ip, m_argument.port);
// init will send packet
m_client_world.init(m_argument.player, m_client);
Logger::info("World Init Success");
m_camera.camera_init(&m_world.get_player("TestPlayer"));
m_camera.camera_init(&m_client_world.get_player());
m_dev_panel.init();
}
void App::handle_argument(int argc, char** argv) {
static const std::unordered_map<std::string_view,
std::function<void(ArgParser&)>>
HANDLERS{
{"--client", [&](ArgParser&) { m_argument.is_client = true; }},
{"--host", [&](ArgParser&) { m_argument.is_client = false; }},
{"-p",
[&](ArgParser& p) {
auto arg = p.require_next("-p");
auto r = std::from_chars(arg.data(), arg.data() + arg.size(),
m_argument.port);
if (r.ec != std::errc{} || r.ptr != arg.data() + arg.size()) {
throw std::runtime_error(
std::format("Invalid port: {}", arg));
}
if (m_argument.port > 65535) {
throw std::runtime_error(
std::format("Port {} out of range", m_argument.port));
}
}},
{"--ip",
[&](ArgParser& p) {
auto arg = p.require_next("--ip");
m_argument.ip = arg;
}},
{"--player",
[&](ArgParser& p) {
auto arg = p.require_next("--player");
m_argument.player = arg;
}},
{"-V",
[&](ArgParser) {
std::cout << CUBED_VERSION << "\n";
exit(EXIT_SUCCESS);
}}
};
ArgParser parser(argc, argv);
while (parser.has_next()) {
auto arg = parser.next();
if (auto it = HANDLERS.find(arg); it != HANDLERS.end()) {
it->second(parser);
} else {
Logger::warn("Unknown argument: {}", arg);
}
}
}
void App::handle_toml() {
toml::table server;
try {
server = toml::parse_file("server.toml");
} catch (const toml::parse_error& e) {
// Logger::warn("Ip toml parse error {}", e.what());
return;
}
m_argument.ip =
*TOML::safe_get_value(server, "ip", std::string("127.0.01"));
m_argument.port = *TOML::safe_get_value(server, "port", 25530);
m_argument.is_client = *TOML::safe_get_value(server, "client", false);
}
void App::key_callback(GLFWwindow* window, int key, int scancode, int action,
int mods) {
ImGuiIO& io = ImGui::GetIO();
@@ -111,7 +201,7 @@ void App::key_callback(GLFWwindow* window, int key, int scancode, int action,
break;
}
app->m_world.get_player("TestPlayer").update_player_move_state(key, action);
app->m_client_world.get_player().update_player_move_state(key, action);
}
void App::mouse_button_callback(GLFWwindow* window, int button, int action,
@@ -162,8 +252,7 @@ void App::window_focus_callback(GLFWwindow* window, int focused) {
}
}
void App::window_reshape_callback(GLFWwindow* window, int new_width,
int new_height) {
void App::window_reshape_callback(GLFWwindow* window, int, int) {
App* app = static_cast<App*>(glfwGetWindowUserPointer(window));
ASSERT_MSG(app, "nullptr");
@@ -180,7 +269,7 @@ void App::mouse_scroll_callback(GLFWwindow* window, double xoffset,
ImGui_ImplGlfw_ScrollCallback(window, xoffset, yoffset);
return;
}
auto& player = app->m_world.get_player("TestPlayer");
auto& player = app->m_client_world.get_player();
player.update_scroll(yoffset);
}
@@ -218,10 +307,16 @@ void App::run() {
last_time = glfwGetTime();
while (!glfwWindowShouldClose(m_window.get_glfw_window())) {
if (m_client_world.is_receive_exit()) {
break;
}
update();
render();
}
m_client_world.request_exit();
if (!m_argument.is_client) {
m_server.server_world().stop();
}
}
static Gait player_gait = Gait::WALK;
void App::update() {
@@ -244,9 +339,9 @@ void App::update() {
std::format("RSS: {}mb", Tools::get_current_rss() / (1024 * 1024)));
}
m_texture_manager.update();
m_world.update(delta_time);
m_client_world.update(delta_time);
m_camera.update_move_camera();
const auto& player = m_world.get_player("TestPlayer");
const auto& player = m_client_world.get_player();
if (player_gait != player.get_gait()) {
player_gait = player.get_gait();
float fov = static_cast<float>(Config::get().get<double>("player.fov"));
@@ -265,7 +360,7 @@ int App::start_cubed_application(int argc, char** argv) {
App app;
try {
app.init();
app.init(argc, argv);
Logger::info("Game Init Finish Start Run...");
app.run();
@@ -288,6 +383,7 @@ DevPanel& App::dev_panel() { return m_dev_panel; }
Renderer& App::renderer() { return m_renderer; }
TextureManager& App::texture_manager() { return m_texture_manager; }
Window& App::window() { return m_window; }
World& App::world() { return m_world; }
ClientWorld& App::client_world() { return m_client_world; }
ServerWorld& App::server_world() { return m_server.server_world(); }
const App::Argument& App::argument() const { return m_argument; }
} // namespace Cubed

View File

@@ -1,7 +1,7 @@
#include "Cubed/camera.hpp"
#include "Cubed/gameplay/player.hpp"
#include "Cubed/gameplay/world.hpp"
#include "Cubed/gameplay/client_player.hpp"
#include "Cubed/gameplay/client_world.hpp"
#include "Cubed/tools/cubed_assert.hpp"
namespace Cubed {
@@ -22,7 +22,7 @@ void Camera::update_move_camera() {
}
}
void Camera::camera_init(Player* player) {
void Camera::camera_init(ClientPlayer* player) {
m_player = player;
update_move_camera();
reset_camera();

View File

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

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,10 +24,16 @@ void DebugCollector::init_text() {
Text opengl_version_text("opengl_version");
Text biome_text("biome");
Text speed_text("speed");
std::string version{"Version: " CUBED_VERSION};
#ifdef DEBUG_MODE
version.append("-debug");
#else
version.append("-release");
#endif
version_text.position(0.0f, 100.0f)
.scale(0.8f)
.color(Color::WHITE)
.text("Version: " + Config::get().get<std::string>("version"));
.text(version);
fps_text.position(0.0f, 50.0f).text("FPS: 0");
player_pos_text.position(0.0f, 150.0f)
.scale(0.8f)

View File

@@ -3,7 +3,7 @@
#include "Cubed/app.hpp"
#include "Cubed/config.hpp"
#include "Cubed/gameplay/cave_path.hpp"
#include "Cubed/gameplay/player.hpp"
#include "Cubed/gameplay/client_player.hpp"
#include "Cubed/gameplay/river.path.hpp"
#include "Cubed/tools/log.hpp"
@@ -16,7 +16,7 @@ namespace Cubed {
static constexpr const char* THEMES[] = {"Dark", "Light"};
static constexpr const char* GAITS[] = {"Walk", "Run"};
static constexpr const char* GAME_MODES[] = {"Creative", "Spectator"};
static char perlin_noise_input_buffer[64];
static constexpr const char* CHUNK_LOAD_STYLE[] = {"Random", "Center"};
constexpr float TEMP_MIN = 0.0f;
constexpr float TEMP_MAX = 1.0f;
@@ -46,20 +46,10 @@ constexpr float DELTA_ANGLE_MAX = 30.0f;
constexpr int PATH_STEP_MIN = 1;
constexpr int PATH_STEP_MAX = 1000;
static int filter_unsigned(ImGuiInputTextCallbackData* data) {
if (data->EventFlag == ImGuiInputTextFlags_CallbackCharFilter) {
char c = data->EventChar;
if (c < '0' || c > '9') {
return 1;
}
}
return 0;
}
DevPanel::DevPanel(App& app) : m_app(app) {}
void DevPanel::init() {
m_player = &m_app.world().get_player("TestPlayer");
m_player = &m_app.client_world().get_player();
update_config_view();
update_player_profile();
}
@@ -111,6 +101,9 @@ void DevPanel::show_about_table_bar() {
ImGui::Text("toml++");
ImGui::Text("Dear ImGui");
ImGui::Text("Tbb");
ImGui::Text("Asio");
ImGui::Text("protobuf");
ImGui::Text("zstd");
ImGui::Separator();
ImGui::Text("Special Thanks");
ImGui::Text("TANGERIME");
@@ -268,7 +261,7 @@ void DevPanel::show_biome_table_bar() {
}
void DevPanel::show_time_table_bar() {
World& world = m_app.world();
ServerWorld& world = m_app.server_world();
ImGui::Text("Game Tick %llu", world.game_tick());
ImGui::SameLine();
ImGui::Text("Day Tick %llu", world.day_tick());
@@ -341,17 +334,17 @@ void DevPanel::show_river_table_bar() {
}
void DevPanel::show_chunk_table_bar() {
auto& world = m_app.world();
auto& player = world.get_player("TestPlayer");
auto info = world.get_chunk_info(player.get_player_pos());
/*
auto& world = m_app.client_world();
auto& player = world.get_player();
ImGui::Text("Chunk X: %d Z: %d Info", info.pos.x, info.pos.z);
ImGui::Text("Seed: %u", info.seed);
ImGui::Text("%s", ("Biome " + get_biome_str(info.biome)).c_str());
ImGui::Text("First Random %u", info.first_random);
ImGui::Text("%s",
std::format("Has Cave Start {}", info.has_cave_start).c_str());
ImGui::Text("%s", std::format("Has Cave {}", info.has_cave).c_str());
ImGui::Text("Chunk X: %d Z: %d Info", info.pos.x, info.pos.z);
ImGui::Text("Seed: %u", info.seed);
ImGui::Text("%s", ("Biome " + get_biome_str(info.biome)).c_str());
ImGui::Text("First Random %u", info.first_random);
ImGui::Text("%s",
std::format("Has Cave Start {}", info.has_cave_start).c_str());
ImGui::Text("%s", std::format("Has Cave {}", info.has_cave).c_str());*/
}
void DevPanel::show_settings_tab_item() {
@@ -384,7 +377,7 @@ void DevPanel::show_settings_tab_item() {
128)) {
Config::get().set("world.rendering_distance",
m_config.rendering_distance);
m_app.world().hot_reload();
m_app.client_world().hot_reload();
}
if (ImGui::Checkbox("Fullscreen", &m_config.fullscreen)) {
Config::get().set("window.fullscreen", m_config.fullscreen);
@@ -455,106 +448,116 @@ void DevPanel::show_settings_tab_item() {
void DevPanel::show_world_tab_item() {
if (ImGui::BeginTabItem("world")) {
if (m_text_editing.perlin_seed) {
if (ImGui::InputText("ChunkGenerator Seed",
perlin_noise_input_buffer,
sizeof(perlin_noise_input_buffer),
ImGuiInputTextFlags_CallbackCharFilter |
ImGuiInputTextFlags_EnterReturnsTrue,
filter_unsigned)) {
ChunkGenerator::seed(static_cast<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.world().rebuild_world();
}
}
if (!m_text_editing.perlin_seed) {
ImGui::Text("ChunkGenerator Seed: %u", ChunkGenerator::seed());
if (ImGui::IsItemClicked()) {
m_text_editing.perlin_seed = true;
}
}
static int rendering_distance = m_app.world().rendering_distance();
if (ImGui::SliderInt("Render Distance", &rendering_distance, 2, 128)) {
m_app.world().rendering_distance(rendering_distance);
}
ImGui::Text(
"Pool Threads %d Max Support Threads %d Reserved Threads %d",
m_app.world().pool_threads(), m_app.world().max_threads(),
RESERVED_THREADS);
ImGui::SliderInt("Set Pool Threads", &m_threads, 1,
m_app.world().max_threads());
ImGui::SameLine();
if (ImGui::Button("Set")) {
m_app.world().change_pool_threads(m_threads);
}
if (m_threads > m_app.world().max_threads() - RESERVED_THREADS) {
ImGui::TextColored(
ImVec4(1.0f, 1.0f, 0.0f, 1.0f),
"Waring: When the threads in the thread pool exceed \n(maximum "
"threads minus reserved threads), \nit may cause stuttering.");
}
static const char* chunk_load_style[] = {"Random", "Center"};
m_chunk_style = m_app.world().chunk_load_style();
if (ImGui::Combo("ChunkLoadStyle", &m_chunk_style, chunk_load_style,
IM_ARRAYSIZE(chunk_load_style))) {
m_app.world().set_chunk_load_style(m_chunk_style);
}
if (ImGui::Button("Rebuild World")) {
m_app.world().rebuild_world();
}
ImGui::SameLine();
if (ImGui::Button("Request Chunk Build")) {
m_app.world().need_gen();
}
ImGui::SameLine();
if (ImGui::Button("Spawn Point")) {
m_player->set_player_pos({0.0f, 255.0f, 0.0f});
}
ImGui::SameLine();
if (ImGui::Checkbox("Gen Thread", &m_gen_thread_running)) {
if (m_gen_thread_running) {
m_app.world().start_gen_thread();
} else {
m_app.world().stop_gen_thread();
if (ImGui::BeginTabBar("World Kind")) {
if (!m_app.argument().is_client) {
if (ImGui::BeginTabItem("ServerWorld")) {
show_server_world_table_bar();
ImGui::EndTabItem();
}
}
}
// ImGui::Text("Chunk Build Progress\n");
// ImGui::ProgressBar(m_app.world().chunk_gen_fraction());
show_chunk_table_bar();
if (ImGui::BeginTabBar("World Settings")) {
if (ImGui::BeginTabItem("Time")) {
show_time_table_bar();
if (ImGui::BeginTabItem("Client World")) {
show_client_world_table_bar();
ImGui::EndTabItem();
}
/*
if (ImGui::BeginTabItem("Cave")) {
show_cave_table_bar();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("River")) {
show_river_table_bar();
ImGui::EndTabItem();
}*/
if (ImGui::BeginTabItem("Biome")) {
show_biome_table_bar();
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
// ImGui::Text("Chunk Build Progress\n");
// ImGui::ProgressBar(m_app.world().chunk_gen_fraction());
// show_chunk_table_bar();
ImGui::EndTabItem();
}
}
void DevPanel::show_server_world_table_bar() {
ImGui::Text("ChunkGenerator Seed: %u", ChunkGenerator::seed());
ImGui::Text("Pool Threads %d Max Support Threads %d Reserved Threads %d",
m_app.server_world().gen_pool_threads(),
m_app.server_world().max_threads(), RESERVED_THREADS);
ImGui::SliderInt("Set Pool Threads", &m_threads, 1,
m_app.server_world().max_threads());
ImGui::SameLine();
if (ImGui::Button("Set")) {
m_app.server_world().change_pool_threads(
ServerWorld::ThreadPoolKind::GEN, m_threads);
}
if (m_threads > m_app.server_world().max_threads() - RESERVED_THREADS) {
ImGui::TextColored(
ImVec4(1.0f, 1.0f, 0.0f, 1.0f),
"Waring: When the threads in the thread pool exceed \n(maximum "
"threads minus reserved threads), \nit may cause stuttering.");
}
m_chunk_style = m_app.server_world().chunk_load_style();
if (ImGui::Combo("ChunkLoadStyle", &m_chunk_style, CHUNK_LOAD_STYLE,
IM_ARRAYSIZE(CHUNK_LOAD_STYLE))) {
m_app.server_world().set_chunk_load_style(m_chunk_style);
}
if (ImGui::Button("Request Chunk Build")) {
m_app.server_world().need_gen(m_player->get_uuid());
}
ImGui::SameLine();
if (ImGui::Checkbox("Gen Thread", &m_gen_thread_running)) {
if (m_gen_thread_running) {
m_app.server_world().start_gen_thread();
} else {
m_app.server_world().stop_gen_thread();
}
}
ImGui::Text("Server Chunk Size %d", m_app.server_world().chunk_size());
if (ImGui::BeginTabBar("World Settings")) {
if (ImGui::BeginTabItem("Time")) {
show_time_table_bar();
ImGui::EndTabItem();
}
/*
if (ImGui::BeginTabItem("Cave")) {
show_cave_table_bar();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("River")) {
show_river_table_bar();
ImGui::EndTabItem();
}*/
if (ImGui::BeginTabItem("Biome")) {
show_biome_table_bar();
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
}
void DevPanel::show_client_world_table_bar() {
static int rendering_distance = m_app.client_world().rendering_distance();
if (ImGui::SliderInt("Render Distance", &rendering_distance, 2, 128)) {
m_app.client_world().rendering_distance(rendering_distance);
// Config::get().set("world.rendering_distance", rendering_distance);
}
if (ImGui::Button("Rebuild World")) {
m_app.client_world().rebuild_world();
}
ImGui::SameLine();
if (ImGui::Button("Spawn Point")) {
m_player->set_player_pos({0.0f, 255.0f, 0.0f});
}
ImGui::Text("Chunk Task Id %d", m_app.client_world().get_chunk_task_id());
ImGui::Text("Client World Chunk %d", m_app.client_world().chunk_size());
}
void DevPanel::show_player_tab_item() {
if (!m_player) {
Logger::error("Player is Nullptr");
return;
}
if (ImGui::BeginTabItem("player")) {
ImGui::Text("Player %s", m_player->get_name().c_str());
if (ImGui::Combo("GameMode", &m_player_profile.game_mode, GAME_MODES,
IM_ARRAYSIZE(GAME_MODES))) {
if (m_player_profile.game_mode == 0) {
@@ -667,8 +670,9 @@ void DevPanel::show_shader_tab_item() {
ImGui::Checkbox("Flip Y", &m_app.renderer().flip_y());
if (ImGui::SliderFloat("AmbientStrength",
&m_app.renderer().ambient_strength(), 0.0f,
0.35f))
;
0.35f)) {
}
ImGui::SliderFloat("SpecularStrength",
&m_app.renderer().specular_strength(), 0.0f, 2.0f);
ImGui::Checkbox("Discard Transparent",

View File

@@ -1,31 +1,18 @@
#include "Cubed/gameplay/block.hpp"
#include "Cubed/config.hpp"
#include "Cubed/tools/cubed_assert.hpp"
#include "Cubed/tools/log.hpp"
#include "Cubed/tools/toml.utils.hpp"
#include <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

@@ -1,39 +1,39 @@
#include "Cubed/gameplay/builders/biome_builder.hpp"
#include "Cubed/gameplay/chunk.hpp"
#include "Cubed/gameplay/chunk_generator.hpp"
#include "Cubed/gameplay/server_chunk.hpp"
namespace Cubed {
void BiomeBuilder::build_bottom() {
ChunkGenerator& chunk_generator = get_chunk_generator();
Chunk& chunk = chunk_generator.chunk();
ServerChunk& chunk = chunk_generator.chunk();
auto& m_blocks = chunk.blocks();
for (int x = 0; x < CHUNK_SIZE; x++) {
for (int y = 0; y < 5; y++) {
for (int z = 0; z < CHUNK_SIZE; z++) {
m_blocks[Chunk::index(x, y, z)] = 3;
m_blocks[ServerChunk::index(x, y, z)] = 3;
}
}
}
}
void BiomeBuilder::place_grass() {
ChunkGenerator& chunk_generator = get_chunk_generator();
Chunk& chunk = chunk_generator.chunk();
ServerChunk& chunk = chunk_generator.chunk();
auto& blocks = chunk.blocks();
const auto& heightmap = chunk.get_heightmap();
auto& random = chunk_generator.random();
for (int x = 0; x < SIZE_X; ++x) {
for (int z = 0; z < SIZE_Z; ++z) {
int y = heightmap[x][z];
BlockType top_id = blocks[Chunk::index(x, y, z)];
BlockType top_id = blocks[ServerChunk::index(x, y, z)];
if (top_id != 1) {
continue;
}
if (blocks[Chunk::index(x, y + 1, z)] != 0) {
if (blocks[ServerChunk::index(x, y + 1, z)] != 0) {
continue;
}
if (random.random_bool(0.2)) {
if (y + 1 < SIZE_Y) {
blocks[Chunk::index(x, y + 1, z)] = 9;
blocks[ServerChunk::index(x, y + 1, z)] = 9;
}
}
}
@@ -42,7 +42,7 @@ void BiomeBuilder::place_grass() {
void BiomeBuilder::ocean_water_build() {
ChunkGenerator& chunk_generator = get_chunk_generator();
Chunk& chunk = chunk_generator.chunk();
ServerChunk& chunk = chunk_generator.chunk();
auto& blocks = chunk.blocks();
const auto& heightmap = chunk.get_heightmap();
@@ -51,7 +51,7 @@ void BiomeBuilder::ocean_water_build() {
int height = heightmap[x][z];
if (height <= SEA_LEVEL) {
for (int y = height; y <= SEA_LEVEL; y++) {
blocks[Chunk::index(x, y, z)] = 7;
blocks[ServerChunk::index(x, y, z)] = 7;
}
}
}

View File

@@ -1,7 +1,7 @@
#include "Cubed/gameplay/builders/desert_builder.hpp"
#include "Cubed/gameplay/chunk.hpp"
#include "Cubed/gameplay/chunk_generator.hpp"
#include "Cubed/gameplay/server_chunk.hpp"
namespace Cubed {
DesertBuilder::DesertBuilder(ChunkGenerator& chunk_generator)
: m_chunk_generator(chunk_generator) {}
@@ -19,11 +19,11 @@ void DesertBuilder::build_blocks() {
for (int z = 0; z < CHUNK_SIZE; z++) {
int height = static_cast<int>(m_heightmap[x][z]);
for (int y = 5; y < height - 5; y++) {
m_blocks[Chunk::index(x, y, z)] = 3;
m_blocks[ServerChunk::index(x, y, z)] = 3;
}
for (int y = height - 5; y <= height; y++) {
m_blocks[Chunk::index(x, y, z)] = 4;
m_blocks[ServerChunk::index(x, y, z)] = 4;
}
}
}

View File

@@ -1,7 +1,7 @@
#include "Cubed/gameplay/builders/forest_builder.hpp"
#include "Cubed/gameplay/chunk.hpp"
#include "Cubed/gameplay/chunk_generator.hpp"
#include "Cubed/gameplay/server_chunk.hpp"
#include "Cubed/gameplay/tree.hpp"
#include <algorithm>
@@ -24,12 +24,12 @@ void ForestBuilder::build_blocks() {
for (int z = 0; z < CHUNK_SIZE; z++) {
int height = static_cast<int>(m_heightmap[x][z]);
for (int y = 5; y < height - 5; y++) {
m_blocks[Chunk::index(x, y, z)] = 3;
m_blocks[ServerChunk::index(x, y, z)] = 3;
}
for (int y = height - 5; y < height; y++) {
m_blocks[Chunk::index(x, y, z)] = 2;
m_blocks[ServerChunk::index(x, y, z)] = 2;
}
m_blocks[Chunk::index(x, height, z)] = 1;
m_blocks[ServerChunk::index(x, height, z)] = 1;
}
}
}

View File

@@ -1,7 +1,7 @@
#include "Cubed/gameplay/builders/mountain_builder.hpp"
#include "Cubed/gameplay/chunk.hpp"
#include "Cubed/gameplay/chunk_generator.hpp"
#include "Cubed/gameplay/server_chunk.hpp"
namespace Cubed {
MountainBuilder::MountainBuilder(ChunkGenerator& chunk_generator)
: m_chunk_generator(chunk_generator) {}
@@ -19,7 +19,7 @@ void MountainBuilder::build_blocks() {
for (int z = 0; z < CHUNK_SIZE; z++) {
int height = static_cast<int>(m_heightmap[x][z]);
for (int y = 5; y <= height; y++) {
m_blocks[Chunk::index(x, y, z)] = 3;
m_blocks[ServerChunk::index(x, y, z)] = 3;
}
}
}

View File

@@ -1,7 +1,7 @@
#include "Cubed/gameplay/builders/ocean_builder.hpp"
#include "Cubed/gameplay/chunk.hpp"
#include "Cubed/gameplay/chunk_generator.hpp"
#include "Cubed/gameplay/server_chunk.hpp"
namespace Cubed {
OceanBuilder::OceanBuilder(ChunkGenerator& chunk_generator)
: m_chunk_generator(chunk_generator) {}
@@ -19,7 +19,7 @@ void OceanBuilder::build_blocks() {
for (int z = 0; z < CHUNK_SIZE; z++) {
int height = static_cast<int>(m_heightmap[x][z]);
for (int y = 5; y <= height; y++) {
m_blocks[Chunk::index(x, y, z)] = 3;
m_blocks[ServerChunk::index(x, y, z)] = 3;
}
}
}

View File

@@ -1,7 +1,7 @@
#include "Cubed/gameplay/builders/plain_builder.hpp"
#include "Cubed/gameplay/chunk.hpp"
#include "Cubed/gameplay/chunk_generator.hpp"
#include "Cubed/gameplay/server_chunk.hpp"
namespace Cubed {
PlainBuilder::PlainBuilder(ChunkGenerator& chunk_generator)
: m_chunk_generator(chunk_generator) {}
@@ -19,12 +19,12 @@ void PlainBuilder::build_blocks() {
for (int z = 0; z < CHUNK_SIZE; z++) {
int height = static_cast<int>(m_heightmap[x][z]);
for (int y = 5; y < height - 5; y++) {
m_blocks[Chunk::index(x, y, z)] = 3;
m_blocks[ServerChunk::index(x, y, z)] = 3;
}
for (int y = height - 5; y < height; y++) {
m_blocks[Chunk::index(x, y, z)] = 2;
m_blocks[ServerChunk::index(x, y, z)] = 2;
}
m_blocks[Chunk::index(x, height, z)] = 1;
m_blocks[ServerChunk::index(x, height, z)] = 1;
}
}
}

View File

@@ -1,7 +1,7 @@
#include "Cubed/gameplay/builders/snowy_plain_builder.hpp"
#include "Cubed/gameplay/chunk.hpp"
#include "Cubed/gameplay/chunk_generator.hpp"
#include "Cubed/gameplay/server_chunk.hpp"
namespace Cubed {
SnowyPlainBuilder::SnowyPlainBuilder(ChunkGenerator& chunk_generator)
: m_chunk_generator(chunk_generator) {}
@@ -19,12 +19,12 @@ void SnowyPlainBuilder::build_blocks() {
for (int z = 0; z < CHUNK_SIZE; z++) {
int height = static_cast<int>(m_heightmap[x][z]);
for (int y = 5; y < height - 5; y++) {
m_blocks[Chunk::index(x, y, z)] = 3;
m_blocks[ServerChunk::index(x, y, z)] = 3;
}
for (int y = height - 5; y < height; y++) {
m_blocks[Chunk::index(x, y, z)] = 2;
m_blocks[ServerChunk::index(x, y, z)] = 2;
}
m_blocks[Chunk::index(x, height, z)] = 8;
m_blocks[ServerChunk::index(x, height, z)] = 8;
}
}
}

View File

@@ -8,10 +8,10 @@
#include "Cubed/gameplay/builders/river_builder.hpp"
#include "Cubed/gameplay/builders/snowy_plain_builder.hpp"
#include "Cubed/gameplay/cave_path.hpp"
#include "Cubed/gameplay/chunk.hpp"
#include "Cubed/gameplay/river.path.hpp"
#include "Cubed/gameplay/server_chunk.hpp"
#include "Cubed/gameplay/server_world.hpp"
#include "Cubed/gameplay/tree.hpp"
#include "Cubed/gameplay/world.hpp"
#include "Cubed/tools/cubed_assert.hpp"
#include "Cubed/tools/cubed_hash.hpp"
#include "Cubed/tools/math_tools.hpp"
@@ -99,7 +99,7 @@ void carve_worm(const std::vector<PathPoint>& points, const ChunkPos& chunk_pos,
using enum BiomeType;
constexpr int BLEND_RADIUS = 8;
ChunkGenerator::ChunkGenerator(Chunk& chunk) : m_chunk(chunk) {
ChunkGenerator::ChunkGenerator(ServerChunk& chunk) : m_chunk(chunk) {
ASSERT_MSG(is_init, "ChunksGenerator is not init");
ChunkPos pos = m_chunk.get_chunk_pos();
unsigned seed = HASH::chunk_seed_hash(pos.x, pos.z, m_generator_seed);
@@ -157,7 +157,7 @@ void ChunkGenerator::assign_chunk_biome() {
}
void ChunkGenerator::resolve_biome_adjacency_conflict(
const std::array<const Chunk*, 8>& adj_chunks) {
const std::array<const ServerChunk*, 8>& adj_chunks) {
auto m_biome = m_chunk.biome();
for (int i = 0; i < 8; i++) {
auto& chunk = adj_chunks[i];
@@ -555,7 +555,8 @@ void ChunkGenerator::blend_surface_blocks_borders(
int nx, int nz) -> BlockType {
// Search from topmost y downwards for the first non-zero block
for (int y = WORLD_HEIGHT - 1; y >= 0; --y) {
int idx = Chunk::index(nx, y,
int idx =
ServerChunk::index(nx, y,
nz); // linear index: y * area + z * size + x
if (idx >= 0 && idx < static_cast<int>(blocks.size())) {
BlockType neighbor_type = blocks[idx];
@@ -574,7 +575,7 @@ void ChunkGenerator::blend_surface_blocks_borders(
BlockType type_self = 0;
int top_y = -1;
top_y = m_heightmap[x][z];
type_self = m_blocks[Chunk::index(x, top_y, z)];
type_self = m_blocks[ServerChunk::index(x, top_y, z)];
if (top_y == -1)
continue; // no block? skip
@@ -665,7 +666,7 @@ void ChunkGenerator::blend_surface_blocks_borders(
if (final_type != type_self) {
// top block
BlockType new_surface = final_type;
m_blocks[Chunk::index(x, top_y, z)] = new_surface;
m_blocks[ServerChunk::index(x, top_y, z)] = new_surface;
// bottom block
unsigned fill_type = 2;
if (final_type == 1 || final_type == 8) {
@@ -674,7 +675,7 @@ void ChunkGenerator::blend_surface_blocks_borders(
fill_type = final_type;
}
for (int y = std::max(0, top_y - 5); y < top_y; y++) {
m_blocks[Chunk::index(x, y, z)] = fill_type;
m_blocks[ServerChunk::index(x, y, z)] = fill_type;
}
}
}
@@ -741,12 +742,12 @@ void ChunkGenerator::generate_cave() {
carve_worm(path.points(), chunk_pos,
[&](int x, int y, int z) -> void {
int idx = Chunk::index(x, y, z);
int idx = ServerChunk::index(x, y, z);
m_chunk.has_cave() = true;
if (blocks[idx] == 7)
return;
if (y < WORLD_SIZE_Y - 1 &&
blocks[Chunk::index(x, y + 1, z)] == 7)
blocks[ServerChunk::index(x, y + 1, z)] == 7)
return;
blocks[idx] = 0;
});
@@ -780,7 +781,7 @@ void ChunkGenerator::generate_river() {
carve_worm(path.points(), chunk_pos,
[&](int x, int y, int z) -> void {
int idx = Chunk::index(x, y, z);
int idx = ServerChunk::index(x, y, z);
if (y > SEA_LEVEL) {
blocks[idx] = 0;
return;
@@ -799,7 +800,7 @@ void ChunkGenerator::generate_river() {
}
}
Chunk& ChunkGenerator::chunk() { return m_chunk; }
ServerChunk& ChunkGenerator::chunk() { return m_chunk; }
Random& ChunkGenerator::random() { return m_random; }
const std::array<BiomeType, 8>& ChunkGenerator::neighbor_biome() const {

View File

@@ -1,10 +1,6 @@
#include "Cubed/gameplay/chunk.hpp"
#include "Cubed/gameplay/client_chunk.hpp"
#include "Cubed/gameplay/world.hpp"
#include "Cubed/tools/cubed_assert.hpp"
#include "Cubed/tools/log.hpp"
#include <utility>
namespace Cubed {
using OptionalBlockVectorArray =
@@ -39,14 +35,14 @@ get_block_safe(int lx, int ly, int lz, ChunkPos& chunk_pos,
const OptionalBlockVectorArray& neighbor_block) {
if (lx >= 0 && lx < CHUNK_SIZE && ly >= 0 && ly < WORLD_SIZE_Y && lz >= 0 &&
lz < CHUNK_SIZE) {
return blocks[Chunk::index(lx, ly, lz)];
return blocks[ClientChunk::index(lx, ly, lz)];
}
// Out of bounds: check neighbors
int world_x = lx + chunk_pos.x * CHUNK_SIZE;
int world_z = lz + chunk_pos.z * CHUNK_SIZE;
auto [nb_cx, nb_cz] = World::get_chunk_pos(world_x, world_z);
auto [nb_cx, nb_cz] = get_chunk_pos(world_x, world_z);
const std::optional<std::vector<BlockType>>* nb = nullptr;
if (nb_cx == chunk_pos.x + 1)
@@ -69,7 +65,7 @@ get_block_safe(int lx, int ly, int lz, ChunkPos& chunk_pos,
nby >= WORLD_SIZE_Y || nbz >= CHUNK_SIZE)
return 0;
int idx = Chunk::index(nbx, nby, nbz);
int idx = ClientChunk::index(nbx, nby, nbz);
if (static_cast<size_t>(idx) >= (*nb)->size()) {
return 0;
}
@@ -100,32 +96,28 @@ inline int choose_buf(BlockType id) {
}
} // namespace
Chunk::Chunk(World& world, ChunkPos chunk_pos, bool temp_chunk)
: m_temp_chunk(temp_chunk), m_chunk_pos(chunk_pos), m_world(world) {
ClientChunk::ClientChunk(ClientWorld& world) : m_world(world) {
for (int i = 0; i < VERTEX_DATA_SUM; i++) {
m_vertex_data.emplace_back(m_world);
}
}
ClientChunk::~ClientChunk() {}
Chunk::~Chunk() {}
Chunk::Chunk(Chunk&& other) noexcept
ClientChunk::ClientChunk(ClientChunk&& other) noexcept
: m_dirty(other.is_dirty()), m_need_upload(other.m_need_upload.load()),
m_is_on_gen_vertex_data(other.m_is_on_gen_vertex_data.load()),
m_biome(other.m_biome.load()), m_chunk_pos(std::move(other.m_chunk_pos)),
m_world(other.m_world), m_heightmap(std::move(other.m_heightmap)),
m_blocks(std::move(other.m_blocks)),
m_vertex_data(std::move(other.m_vertex_data)), m_seed(other.m_seed),
m_conditions(other.m_conditions), m_info(std::move(other.m_info)) {}
m_world(other.m_world), m_blocks(std::move(other.m_blocks)),
m_vertex_data(std::move(other.m_vertex_data)), m_seed(other.m_seed) {}
Chunk& Chunk::operator=(Chunk&& other) noexcept {
ClientChunk& ClientChunk::operator=(ClientChunk&& other) noexcept {
// Logger::info("other Chunk pos {} {} in Chunk& Chunk::operator=(Chunk&&
// other) this {}", other.m_chunk_pos.x, other.m_chunk_pos.z,
// static_cast<const void*>(&other));
if (this == &other) {
return *this;
}
m_chunk_pos = std::move(other.m_chunk_pos);
m_heightmap = std::move(other.m_heightmap);
m_blocks = std::move(other.m_blocks);
m_dirty = other.is_dirty();
m_vertex_data = std::move(other.m_vertex_data);
@@ -133,55 +125,10 @@ Chunk& Chunk::operator=(Chunk&& other) noexcept {
m_is_on_gen_vertex_data = other.m_is_on_gen_vertex_data.load();
m_need_upload = other.m_need_upload.load();
m_seed = other.m_seed;
m_conditions = other.m_conditions;
m_info = std::move(other.m_info);
return *this;
}
std::tuple<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) {
int ClientChunk::index(int x, int y, int z) {
ASSERT(!(x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y ||
z >= CHUNK_SIZE));
if ((x * WORLD_SIZE_Y + y) * CHUNK_SIZE + z < 0 ||
@@ -193,15 +140,50 @@ int Chunk::index(int x, int y, int z) {
return (x * WORLD_SIZE_Y + y) * CHUNK_SIZE + z;
}
int Chunk::index(const glm::vec3& pos) {
return Chunk::index(pos.x, pos.y, pos.z);
int ClientChunk::index(const glm::vec3& pos) {
return ClientChunk::index(pos.x, pos.y, pos.z);
}
std::tuple<int, int, int> ClientChunk::world_to_block(int world_x, int world_y,
int world_z, int chunk_x,
int chunk_z) {
int x, y, z;
y = world_y;
x = world_x - chunk_x * CHUNK_SIZE;
z = world_z - chunk_z * CHUNK_SIZE;
return {x, y, z};
}
void Chunk::gen_vertex_data(const OptionalBlockVectorArray& neighbor_block) {
if (m_is_on_gen_vertex_data) {
std::tuple<int, int, int>
ClientChunk::world_to_block(const glm::ivec3& block_pos, ChunkPos chunk_pos) {
return world_to_block(block_pos.x, block_pos.y, block_pos.z, chunk_pos.x,
chunk_pos.z);
}
std::tuple<int, int, int>
ClientChunk::block_to_world(int x, int y, int z, int chunk_x, int chunk_z) {
int world_x = x + chunk_x * CHUNK_SIZE;
int world_z = z + chunk_z * CHUNK_SIZE;
int world_y = y;
return {world_x, world_y, world_z};
}
std::tuple<int, int, int>
ClientChunk::block_to_world(const glm::ivec3& block_pos, ChunkPos chunk_pos) {
return block_to_world(block_pos.x, block_pos.y, block_pos.z, chunk_pos.x,
chunk_pos.z);
}
BiomeType ClientChunk::get_biome() const { return m_biome.load(); }
ChunkPos ClientChunk::get_chunk_pos() const { return m_chunk_pos; }
const std::vector<BlockType>& ClientChunk::get_chunk_blocks() const {
return m_blocks;
}
void ClientChunk::gen_vertex_data(
const OptionalBlockVectorArray& neighbor_block) {
if (m_is_on_gen_vertex_data.exchange(true)) {
return;
}
m_is_on_gen_vertex_data = true;
std::lock_guard lk(m_vertexs_data_mutex);
for (auto& data : m_vertex_data) {
@@ -216,108 +198,44 @@ void Chunk::gen_vertex_data(const OptionalBlockVectorArray& neighbor_block) {
m_is_on_gen_vertex_data = false;
}
GLuint Chunk::get_normal_vao() const { return m_vertex_data[0].m_vao; }
GLuint ClientChunk::get_normal_vao() const { return m_vertex_data[0].m_vao; }
size_t Chunk::get_normal_vertices_sum() const {
size_t ClientChunk::get_normal_vertices_sum() const {
if (m_vertex_data[0].m_sum == 0) {
Logger::warn("m_normal_vertices_sum is 0");
}
return m_vertex_data[0].m_sum.load();
}
GLuint Chunk::get_cross_vao() const { return m_vertex_data[1].m_vao; }
size_t Chunk::get_cross_vertices_sum() const {
GLuint ClientChunk::get_cross_vao() const { return m_vertex_data[1].m_vao; }
size_t ClientChunk::get_cross_vertices_sum() const {
return m_vertex_data[1].m_sum.load();
}
GLuint Chunk::get_normal_discard_vao() const { return m_vertex_data[2].m_vao; }
size_t Chunk::get_normal_discard_vertices_sum() const {
GLuint ClientChunk::get_normal_discard_vao() const {
return m_vertex_data[2].m_vao;
}
size_t ClientChunk::get_normal_discard_vertices_sum() const {
return m_vertex_data[2].m_sum.load();
}
GLuint Chunk::get_normal_blend_vao() const { return m_vertex_data[3].m_vao; }
size_t Chunk::get_normal_blend_vertices_sum() const {
GLuint ClientChunk::get_normal_blend_vao() const {
return m_vertex_data[3].m_vao;
}
size_t ClientChunk::get_normal_blend_vertices_sum() const {
return m_vertex_data[3].m_sum.load();
}
GLuint Chunk::get_water_vao() const { return m_vertex_data[4].m_vao; }
size_t Chunk::get_water_vertices_sum() const {
GLuint ClientChunk::get_water_vao() const { return m_vertex_data[4].m_vao; }
size_t ClientChunk::get_water_vertices_sum() const {
return m_vertex_data[4].m_sum.load();
}
void Chunk::gen_phase_one() {
m_generator = std::make_unique<ChunkGenerator>(*this);
if (!m_generator) {
Logger::error("ChunkGenerator is Nullptr");
void ClientChunk::upload_to_gpu() {
if (!is_need_upload()) {
return;
}
m_generator->assign_chunk_biome();
m_seed = m_generator->chunk_seed();
}
void Chunk::gen_phase_two(const std::array<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);
@@ -327,206 +245,64 @@ void Chunk::upload_to_gpu() {
// after fininshed it, can use
clear_dirty();
m_render_snapshot = {
get_normal_vao(),
get_normal_vertices_sum(),
get_cross_vao(),
get_cross_vertices_sum(),
get_normal_discard_vao(),
get_normal_discard_vertices_sum(),
get_normal_blend_vao(),
get_normal_blend_vertices_sum(),
get_water_vao(),
get_water_vertices_sum(),
glm::vec3(static_cast<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;
}
bool Chunk::is_dirty() const { return m_dirty.load(); }
bool ClientChunk::is_dirty() const { return m_dirty.load(); }
void Chunk::mark_dirty() { m_dirty = true; }
void ClientChunk::mark_dirty() { m_dirty = true; }
void Chunk::clear_dirty() { m_dirty = false; }
void ClientChunk::clear_dirty() { m_dirty = false; }
bool Chunk::is_need_upload() const { return m_need_upload.load(); }
bool ClientChunk::is_need_upload() const { return m_need_upload.load(); }
void Chunk::need_upload() { m_need_upload = true; }
void ClientChunk::need_upload() { m_need_upload = true; }
void Chunk::set_chunk_block(int index, unsigned id) {
void ClientChunk::set_chunk_block(int index, unsigned id) {
m_blocks[index] = id;
mark_dirty();
}
ChunkPos Chunk::chunk_pos() const { return m_chunk_pos; }
ChunkPos ClientChunk::chunk_pos() const { return m_chunk_pos; }
BiomeType Chunk::biome() const { return m_biome; }
BiomeType ClientChunk::biome() const { return m_biome; }
void Chunk::biome(BiomeType b) { m_biome = b; }
void ClientChunk::biome(BiomeType b) { m_biome = b; }
HeightMapArray& Chunk::heightmap() { return m_heightmap; }
std::vector<BlockType>& Chunk::blocks() { return m_blocks; }
World& Chunk::world() { return m_world; }
unsigned Chunk::seed() const {
std::vector<BlockType>& ClientChunk::blocks() { return m_blocks; }
ClientWorld& ClientChunk::world() { return m_world; }
unsigned ClientChunk::seed() const {
if (m_seed == 0) {
Logger::warn("Seed Not Generator");
}
return m_seed;
}
BiomeConditions& Chunk::conditions() { return m_conditions; }
ChunkInfo Chunk::get_info() const {
if (m_gening) {
return ChunkInfo{};
}
return m_info;
const ChunkRenderSnapshot* ClientChunk::get_render_snapshot() const {
return &m_render_snapshot;
}
/*
void Chunk::gen_vertices(const OptionalBlockVectorArray& neighbor_block) {
static const glm::ivec3 DIR[6] = {{0, 0, 1}, {1, 0, 0}, {0, 0, -1},
{-1, 0, 0}, {0, 1, 0}, {0, -1, 0}};
for (int x = 0; x < SIZE_X; x++) {
for (int y = 0; y < SIZE_Y; y++) {
for (int z = 0; z < SIZE_Z; z++) {
int world_x = x + m_chunk_pos.x * CHUNK_SIZE;
int world_z = z + m_chunk_pos.z * CHUNK_SIZE;
int world_y = y;
int cur_id = m_blocks[index(x, y, z)];
// air
if (cur_id == 0) {
continue;
}
for (int face = 0; face < 6; face++) {
int nx = x + DIR[face].x;
int ny = y + DIR[face].y;
int nz = z + DIR[face].z;
bool neighbor_culled = false;
if (nx < 0 || nx >= SIZE_X || ny < 0 || ny >= SIZE_Y ||
nz < 0 || nz >= SIZE_Z) {
int world_nx = world_x + DIR[face].x;
int world_ny = world_y + DIR[face].y;
int world_nz = world_z + DIR[face].z;
auto [neighbor_x, neighbor_z] =
World::get_chunk_pos(world_nx, world_nz);
auto is_culled =
[&](const std::optional<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) {
void ClientChunk::gen_vertices(const OptionalBlockVectorArray& neighbor_block) {
// SIZE_X=SIZE_Z=CHUNK_SIZE=16, SIZE_Y=WORLD_SIZE_Y=256
// Axis order: axis 0=X, 1=Y, 2=Z
@@ -648,8 +424,8 @@ void Chunk::gen_vertices(const OptionalBlockVectorArray& neighbor_block) {
}
}
}
void Chunk::emit_quad(int axis, int face_dir, int layer, int i, int j, int w,
int h, int u_axis, int v_axis, FaceKey key) {
void ClientChunk::emit_quad(int axis, int face_dir, int layer, int i, int j,
int w, int h, int u_axis, int v_axis, FaceKey key) {
float axis_val = (float)(layer + (face_dir > 0 ? 1 : 0));
float wx_base = (float)(m_chunk_pos.x * CHUNK_SIZE);
float wz_base = (float)(m_chunk_pos.z * CHUNK_SIZE);
@@ -720,8 +496,8 @@ void Chunk::emit_quad(int axis, int face_dir, int layer, int i, int j, int w,
}
}
void Chunk::gen_cross_plane_vertices(int world_x, int world_y, int world_z,
BlockType id) {
void ClientChunk::gen_cross_plane_vertices(int world_x, int world_y,
int world_z, BlockType id) {
if (!BlockManager::is_cross_plane(id)) {
Logger::warn("Block {} {} {} id {} is not cross plane", world_x,
@@ -751,55 +527,47 @@ void Chunk::gen_cross_plane_vertices(int world_x, int world_y, int world_z,
}
}
void Chunk::gen_chunk() {
if (m_gening.exchange(true))
void ClientChunk::receive_chunk(const ChunkDataRsp& data) {
OptionalBlockVectorArray neighbor;
m_chunk_pos.x = data.pos().x();
m_chunk_pos.z = data.pos().z();
m_seed = data.chunk_seed();
m_biome = get_biome_from_id(data.biome_type());
for (int i = 0; i < 4; i++) {
neighbor[i] = std::nullopt;
}
if (data.chunk_blocks_size() != BLOCK_SIZE) {
Logger::error("Bad Chunk, size {}", data.chunk_blocks_size());
return;
m_gening = true;
if (m_blocks.size() != 0) {
Logger::warn(
"Request Generator Chunk {} {} ,but the Blocks size is Not 0",
m_chunk_pos.x, m_chunk_pos.z);
}
std::vector<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();
m_blocks.reserve(BLOCK_SIZE);
OptionalBlockVectorArray neightbor_blocks;
for (int i = 0; i < 4; i++) {
neightbor_blocks[i] = neighbor[i].get_chunk_blocks();
for (const auto& b : data.chunk_blocks()) {
m_blocks.push_back(static_cast<BlockType>(b));
}
gen_phase_six(neightbor_blocks);
gen_phase_seven();
for (int i = 0; i < 4; i++) {
neightbor_blocks[i] = neighbor[i].get_chunk_blocks();
}
gen_vertex_data(neightbor_blocks);
// temp neighbor block data
auto load_neighbor = [&](int idx, const auto& blocks) {
if (blocks.size() != BLOCK_SIZE)
return;
// collect chunk info for debugging
m_info.biome = m_biome;
m_info.pos = m_chunk_pos;
m_info.seed = m_seed;
Random r(m_seed);
unsigned first = r.engine()();
m_info.first_random = first;
r.init(m_seed);
m_info.has_cave_start = r.random_bool(DEFAULT_CAVE_PROBABILITY);
m_info.has_cave = m_has_cave;
neighbor[idx].emplace();
neighbor[idx]->reserve(BLOCK_SIZE);
for (auto b : blocks) {
neighbor[idx]->push_back(static_cast<BlockType>(b));
}
};
load_neighbor(0, data.neighbor_blocks_1());
load_neighbor(1, data.neighbor_blocks_2());
load_neighbor(2, data.neighbor_blocks_3());
load_neighbor(3, data.neighbor_blocks_4());
gen_vertex_data(neighbor);
mark_dirty();
}
// Logger::info("Cross Sum {}", m_cross_vertices_sum.load());
bool Chunk::is_temp_chunk() const { return m_temp_chunk.load(); }
bool& Chunk::has_cave() { return m_has_cave; }
} // namespace Cubed
} // namespace Cubed

View File

@@ -1,48 +1,41 @@
#include "Cubed/gameplay/player.hpp"
#include "Cubed/gameplay/client_player.hpp"
#include "Cubed/config.hpp"
#include "Cubed/debug_collector.hpp"
#include "Cubed/gameplay/world.hpp"
#include "Cubed/tools/log.hpp"
#include <GLFW/glfw3.h>
#include "Cubed/gameplay/client_world.hpp"
namespace Cubed {
ClientPlayer::ClientPlayer(ClientWorld& world) : m_world(world) {}
ClientPlayer::~ClientPlayer() {}
Player::Player(World& world, const std::string& name)
: m_name(name), m_world(world) {
hot_reload();
}
Player::~Player() {}
AABB ClientPlayer::get_aabb(const glm::vec3& pos) {
float half_width = M_SIZE.x / 2.0f;
float half_depth = M_SIZE.z / 2.0f;
AABB Player::get_aabb() const {
float half_width = m_size.x / 2.0f;
float half_depth = m_size.z / 2.0f;
glm::vec3 min{pos.x - half_width, pos.y, pos.z - half_depth};
glm::vec3 min{m_player_pos.x - half_width, m_player_pos.y,
m_player_pos.z - half_depth};
glm::vec3 max{m_player_pos.x + half_width, m_player_pos.y + m_size.y,
m_player_pos.z + half_depth};
glm::vec3 max{pos.x + half_width, pos.y + M_SIZE.y, pos.z + half_depth};
return AABB{min, max};
}
const glm::vec3& ClientPlayer::get_front() const { return m_front; }
const glm::vec3& Player::get_front() const { return m_front; }
const Gait& ClientPlayer::get_gait() const { return m_gait; }
const Gait& Player::get_gait() const { return m_gait; }
const std::optional<LookBlock>& Player::get_look_block_pos() const {
const std::optional<LookBlock>& ClientPlayer::get_look_block_pos() const {
return m_look_block;
}
glm::vec3 ClientPlayer::get_player_pos() const {
const glm::vec3& Player::get_player_pos() const { return m_player_pos; }
std::shared_lock lock(m_player_pos_mutex);
return m_player_pos;
}
const MoveState& Player::get_move_state() const { return m_move_state; }
const MoveState& ClientPlayer::get_move_state() const { return m_move_state; }
bool Player::ray_cast(const glm::vec3& start, const glm::vec3& front,
glm::ivec3& block_pos, glm::vec3& normal,
float distance) {
bool ClientPlayer::ray_cast(const glm::vec3& start, const glm::vec3& front,
glm::ivec3& block_pos, glm::vec3& normal,
float distance) {
glm::vec3 dir = glm::normalize(front);
// float step = 0.1f;
glm::ivec3 cur = glm::floor(start);
@@ -113,7 +106,7 @@ bool Player::ray_cast(const glm::vec3& start, const glm::vec3& front,
return false;
}
void Player::change_mode(GameMode mode) {
void ClientPlayer::change_mode(GameMode mode) {
m_game_mode = mode;
Logger::info("Change GameMode to {}", to_str(mode));
if (mode == CREATIVE) {
@@ -125,22 +118,19 @@ void Player::change_mode(GameMode mode) {
m_max_speed = m_max_run_speed;
}
}
void Player::hot_reload() {
void ClientPlayer::hot_reload() {
auto& config = Config::get();
m_sensitivity =
static_cast<float>(config.get<double>("player.mouse_sensitivity"));
}
void ClientPlayer::set_player_pos(const glm::vec3& pos) { m_player_pos = pos; }
void Player::set_player_pos(const glm::vec3& pos) { m_player_pos = pos; }
void ClientPlayer::set_place_block(unsigned id) { m_place_block = id; }
void Player::set_place_block(unsigned id) { m_place_block = id; }
void Player::update(float delta_time) {
void ClientPlayer::update(float delta_time) {
update_move(delta_time);
update_lookup_block();
check_player_chunk_transition();
DebugCollector::get().report("player_pos",
std::format("x: {:.2f} y: {:.2f} z: {:.2f}",
@@ -150,8 +140,7 @@ void Player::update(float delta_time) {
DebugCollector::get().report("speed",
std::format("Speed: {:.2} m/s", m_xz_speed));
}
void Player::update_player_move_state(int key, int action) {
void ClientPlayer::update_player_move_state(int key, int action) {
switch (key) {
case GLFW_KEY_W:
if (action == GLFW_PRESS) {
@@ -231,7 +220,7 @@ void Player::update_player_move_state(int key, int action) {
}
}
void Player::update_front_vec(float offset_x, float offset_y) {
void ClientPlayer::update_front_vec(float offset_x, float offset_y) {
m_yaw += offset_x * m_sensitivity;
m_pitch += offset_y * m_sensitivity;
@@ -246,15 +235,7 @@ void Player::update_front_vec(float offset_x, float offset_y) {
m_front = glm::normalize(m_front);
}
void Player::check_player_chunk_transition() {
ChunkPos cur_pos = m_world.get_chunk_pos(m_player_pos.x, m_player_pos.z);
if (cur_pos != m_player_chunk_pos) {
m_world.need_gen();
m_player_chunk_pos = cur_pos;
}
}
void Player::update_direction() {
void ClientPlayer::update_direction() {
m_right = glm::normalize(glm::cross(m_front, glm::vec3(0.0f, 1.0f, 0.0f)));
glm::vec3 move_dir_front = glm::vec3(0.0f);
@@ -279,7 +260,7 @@ void Player::update_direction() {
}
}
void Player::update_lookup_block() {
void ClientPlayer::update_lookup_block() {
// calculate the block that is looked
glm::ivec3 block_pos;
glm::vec3 block_normal;
@@ -294,25 +275,17 @@ void Player::update_lookup_block() {
if (m_look_block != std::nullopt) {
if (Input::get_input_state().mouse_state.left) {
if (m_world.is_solid(m_look_block->pos)) {
m_world.set_block(m_look_block->pos, 0);
m_world.report_block_change(m_look_block->pos, 0);
}
Input::get_input_state().mouse_state.left = false;
}
if (Input::get_input_state().mouse_state.right) {
glm::ivec3 near_pos = m_look_block->pos + m_look_block->normal;
if (!m_world.is_solid(near_pos)) {
auto x = near_pos.x;
auto y = near_pos.y;
auto z = near_pos.z;
AABB block_box = {glm::vec3{static_cast<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();
AABB block_box = ClientWorld::get_block_aabb(near_pos);
AABB player_box = get_aabb(get_player_pos());
if (!player_box.intersects(block_box)) {
m_world.set_block(near_pos, m_place_block);
m_world.report_block_change(near_pos, m_place_block);
}
}
Input::get_input_state().mouse_state.right = false;
@@ -320,11 +293,19 @@ void Player::update_lookup_block() {
}
}
void Player::update_move(float delta_time) {
void ClientPlayer::update_move(float delta_time) {
// if frame rate less than 1 frame per second, don't update
if (delta_time > 1.0f) {
return;
}
// ensure the thread safe
glm::vec3 player_pos;
{
std::shared_lock lock(m_player_pos_mutex);
player_pos = m_player_pos;
}
if (m_game_mode != SPECTATOR) {
if (m_gait == Gait::RUN) {
m_max_speed = m_max_run_speed;
@@ -386,24 +367,30 @@ void Player::update_move(float delta_time) {
move_distance.y = m_y_speed * delta_time;
// y
update_y_move();
update_y_move(player_pos);
// x
update_x_move();
update_x_move(player_pos);
update_z_move();
update_z_move(player_pos);
if (m_player_pos.y < -15.0f) {
if (player_pos.y < -15.0f) {
Logger::warn("y is tow low");
m_player_pos += glm::vec3(1.0f, 100.0f, 1.0f);
player_pos += glm::vec3(1.0f, 100.0f, 1.0f);
}
{
std::lock_guard lock(m_player_pos_mutex);
m_player_pos = player_pos;
}
update_player_chunk();
}
void Player::update_x_move() {
m_player_pos.x += move_distance.x;
void ClientPlayer::update_x_move(glm::vec3& player_pos) {
player_pos.x += move_distance.x;
if (m_game_mode == SPECTATOR) {
return;
}
AABB player_box = get_aabb();
AABB player_box = get_aabb(player_pos);
int minx = std::floor(player_box.min.x);
int maxx = std::floor(player_box.max.x);
int miny = std::floor(player_box.min.y);
@@ -414,16 +401,12 @@ void Player::update_x_move() {
for (int x = minx; x <= maxx; ++x) {
for (int y = miny; y <= maxy; ++y) {
for (int z = minz; z <= maxz; ++z) {
if (!m_world.can_pass_block(glm::ivec3{x, y, z})) {
AABB block_box = {glm::vec3{static_cast<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)}};
glm::ivec3 block_pos{x, y, z};
if (!m_world.can_pass_block(block_pos)) {
AABB block_box = ClientWorld::get_block_aabb(block_pos);
if (player_box.intersects(block_box)) {
m_gait = Gait::WALK;
m_player_pos.x -= move_distance.x;
player_pos.x -= move_distance.x;
return;
}
}
@@ -432,12 +415,12 @@ void Player::update_x_move() {
}
}
void Player::update_y_move() {
m_player_pos.y += move_distance.y;
void ClientPlayer::update_y_move(glm::vec3& player_pos) {
player_pos.y += move_distance.y;
if (m_game_mode == SPECTATOR) {
return;
}
AABB player_box = get_aabb();
AABB player_box = get_aabb(player_pos);
int minx = std::floor(player_box.min.x);
int maxx = std::floor(player_box.max.x);
int miny = std::floor(player_box.min.y);
@@ -448,15 +431,11 @@ void Player::update_y_move() {
for (int x = minx; x <= maxx; ++x) {
for (int y = miny; y <= maxy; ++y) {
for (int z = minz; z <= maxz; ++z) {
if (!m_world.can_pass_block(glm::ivec3{x, y, z})) {
AABB block_box = {glm::vec3{static_cast<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)}};
glm::ivec3 block_pos{x, y, z};
if (!m_world.can_pass_block(block_pos)) {
AABB block_box = ClientWorld::get_block_aabb(block_pos);
if (player_box.intersects(block_box)) {
m_player_pos.y -= move_distance.y;
player_pos.y -= move_distance.y;
m_y_speed = 0.0f;
if (move_distance.y < 0) {
can_up = true;
@@ -470,12 +449,12 @@ void Player::update_y_move() {
}
}
void Player::update_z_move() {
m_player_pos.z += move_distance.z;
void ClientPlayer::update_z_move(glm::vec3& player_pos) {
player_pos.z += move_distance.z;
if (m_game_mode == SPECTATOR) {
return;
}
AABB player_box = get_aabb();
AABB player_box = get_aabb(player_pos);
int minx = std::floor(player_box.min.x);
int maxx = std::floor(player_box.max.x);
int miny = std::floor(player_box.min.y);
@@ -486,16 +465,12 @@ void Player::update_z_move() {
for (int x = minx; x <= maxx; ++x) {
for (int y = miny; y <= maxy; ++y) {
for (int z = minz; z <= maxz; ++z) {
if (!m_world.can_pass_block(glm::ivec3{x, y, z})) {
AABB block_box = {glm::vec3{static_cast<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)}};
glm::ivec3 block_pos{x, y, z};
if (!m_world.can_pass_block(block_pos)) {
AABB block_box = ClientWorld::get_block_aabb(block_pos);
if (player_box.intersects(block_box)) {
m_gait = Gait::WALK;
m_player_pos.z -= move_distance.z;
player_pos.z -= move_distance.z;
return;
}
}
@@ -504,7 +479,23 @@ void Player::update_z_move() {
}
}
void Player::update_scroll(double yoffset) {
void ClientPlayer::update_player_chunk() {
float x, z;
{
std::shared_lock lock(m_player_pos_mutex);
x = m_player_pos.x;
z = m_player_pos.z;
}
ChunkPos chunk_pos = get_chunk_pos(x, z);
float dist = distance2(chunk_pos, m_last_chunk_pos);
if (dist > 2) {
Logger::info("Player request new chunk");
m_world.request_chunk();
m_last_chunk_pos = chunk_pos;
}
}
void ClientPlayer::update_scroll(double yoffset) {
if (m_game_mode == SPECTATOR) {
if (yoffset > 0) {
if (m_max_speed < 500.0f) {
@@ -531,15 +522,36 @@ void Player::update_scroll(double yoffset) {
}
}
float& Player::max_walk_speed() { return m_max_walk_speed; }
float& Player::max_run_speed() { return m_max_run_speed; }
float& Player::max_speed() { return m_max_speed; }
float& Player::acceleration() { return m_acceleration; }
float& Player::deceleration() { return m_deceleration; }
float& Player::g() { return m_g; }
float& Player::fly_y_speed() { return m_fly_y_speed; }
unsigned Player::place_block() const { return m_place_block; };
Gait& Player::gait() { return m_gait; }
GameMode& Player::game_mode() { return m_game_mode; }
const World& Player::get_world() const { return m_world; }
} // namespace Cubed
void ClientPlayer::update_chunk_set(const ChunkPosSet& set) {
std::lock_guard lock(m_chunk_pos_mutex);
m_player_chunk_pos_set.clear();
m_player_chunk_pos_set.insert(set.begin(), set.end());
}
const ClientPlayer::ChunkPosSet& ClientPlayer::get_chunk_pos_set() const {
std::shared_lock lock(m_chunk_pos_mutex);
return m_player_chunk_pos_set;
}
ClientPlayer::ChunkPosSet& ClientPlayer::get_chunk_pos_set() {
std::lock_guard lock(m_chunk_pos_mutex);
return m_player_chunk_pos_set;
}
float& ClientPlayer::max_walk_speed() { return m_max_walk_speed; }
float& ClientPlayer::max_run_speed() { return m_max_run_speed; }
float& ClientPlayer::max_speed() { return m_max_speed; }
float& ClientPlayer::acceleration() { return m_acceleration; }
float& ClientPlayer::deceleration() { return m_deceleration; }
float& ClientPlayer::g() { return m_g; }
float& ClientPlayer::fly_y_speed() { return m_fly_y_speed; }
unsigned ClientPlayer::place_block() const { return m_place_block; };
Gait& ClientPlayer::gait() { return m_gait; }
GameMode& ClientPlayer::game_mode() { return m_game_mode; }
const ClientWorld& ClientPlayer::get_world() const { return m_world; }
void ClientPlayer::set_uuid(std::string_view uuid) { m_uuid = uuid; }
const std::string& ClientPlayer::get_uuid() const { return m_uuid; }
const std::string& ClientPlayer::get_name() const { return m_name; }
void ClientPlayer::init(std::string_view name) { m_name = name; }
} // namespace Cubed

View File

@@ -0,0 +1,746 @@
#include "Cubed/gameplay/client_world.hpp"
#include "Cubed/config.hpp"
#include "Cubed/gameplay/game_time.hpp"
#include "Cubed/gameplay/packet.hpp"
#include "Cubed/tools/math_tools.hpp"
#include <absl/container/inlined_vector.h>
#include <numbers>
using namespace std::chrono;
using namespace std::chrono_literals;
using namespace google::protobuf;
namespace Cubed {
namespace {
struct ChunkRenderData {
std::array<const std::vector<BlockType>*, 4> neighbor_block;
ClientChunk* chunk;
};
} // namespace
ClientWorld::ClientWorld() : m_player(*this) {}
ClientWorld::~ClientWorld() {
stop_client_thread();
stop_thread_pool();
m_chunks.clear();
{
std::lock_guard lk(m_delete_vbo_mutex);
for (auto x : m_pending_delete_vbo) {
glDeleteBuffers(1, &x);
}
m_pending_delete_vbo.clear();
}
{
std::lock_guard lk(m_delete_vao_mutex);
for (auto x : m_pending_delete_vao) {
glDeleteVertexArrays(1, &x);
}
m_pending_delete_vao.clear();
}
m_timers.clear();
}
const std::optional<LookBlock>& ClientWorld::get_look_block_pos() const {
return m_player.get_look_block_pos();
}
ClientPlayer& ClientWorld::get_player() { return m_player; }
int ClientWorld::get_block(const glm::ivec3& block_pos) const {
auto [chunk_x, chunk_z] = get_chunk_pos(block_pos.x, block_pos.z);
chunk_cacc cacc;
if (!m_chunks.find(cacc, ChunkPos{chunk_x, chunk_z})) {
return 0;
}
const auto& chunk_blocks = cacc->second->get_chunk_blocks();
auto [x, y, z] = ClientChunk::world_to_block(block_pos, {chunk_x, chunk_z});
if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y ||
z >= CHUNK_SIZE) {
return 0;
}
return chunk_blocks[ClientChunk::index(x, y, z)];
}
bool ClientWorld::is_solid(const glm::ivec3& block_pos) const {
auto [chunk_x, chunk_z] = get_chunk_pos(block_pos.x, block_pos.z);
chunk_cacc cacc;
if (!m_chunks.find(cacc, ChunkPos{chunk_x, chunk_z})) {
return false;
}
const auto& chunk_blocks = cacc->second->get_chunk_blocks();
auto [x, y, z] = ClientChunk::world_to_block(block_pos, {chunk_x, chunk_z});
if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y ||
z >= CHUNK_SIZE) {
return false;
}
auto id = chunk_blocks[ClientChunk::index(x, y, z)];
if (BlockManager::is_gas(id) || BlockManager::is_liquid(id)) {
return false;
} else {
return true;
}
}
bool ClientWorld::can_pass_block(const glm::ivec3& block_pos) const {
auto [chunk_x, chunk_z] = get_chunk_pos(block_pos.x, block_pos.z);
chunk_cacc cacc;
if (!m_chunks.find(cacc, ChunkPos{chunk_x, chunk_z})) {
return true;
}
const auto& chunk_blocks = cacc->second->get_chunk_blocks();
auto [x, y, z] = ClientChunk::world_to_block(block_pos, {chunk_x, chunk_z});
if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y ||
z >= CHUNK_SIZE) {
return true;
}
auto id = chunk_blocks[ClientChunk::index(x, y, z)];
return BlockManager::is_passable(id);
}
void ClientWorld::rebuild_world() {
if (m_is_rebuilding.exchange(true)) {
return;
}
stop_client_thread();
stop_thread_pool();
m_chunks.clear();
m_pending_upload_queue.clear();
start_thread_pool();
start_client_thread(m_player.get_uuid());
request_chunk();
m_is_rebuilding = false;
}
BlockType ClientWorld::get_block_tpye(const glm::ivec3& block_pos) const {
auto [chunk_x, chunk_z] = get_chunk_pos(block_pos.x, block_pos.z);
chunk_cacc cacc;
;
if (!m_chunks.find(cacc, ChunkPos{chunk_x, chunk_z})) {
// Logger::error("Can't Find Block {} {} {}", block_pos.x, block_pos.y,
// block_pos.z);
return 0;
}
const auto& chunk_blocks = cacc->second->get_chunk_blocks();
auto [x, y, z] = ClientChunk::world_to_block(block_pos, {chunk_x, chunk_z});
if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y ||
z >= CHUNK_SIZE) {
// Logger::error("Can't Find Block {} {} {}", block_pos.x, block_pos.y,
// block_pos.z);
return 0;
}
return chunk_blocks[ClientChunk::index(x, y, z)];
}
void ClientWorld::set_block(const glm::ivec3& block_pos, unsigned id) {
int world_x, world_y, world_z;
world_x = block_pos.x;
world_y = block_pos.y;
world_z = block_pos.z;
auto [chunk_x, chunk_z] = get_chunk_pos(world_x, world_z);
ChunkPos pos{chunk_x, chunk_z};
{
chunk_acc acc;
if (!m_chunks.find(acc, pos)) {
return;
}
auto [x, y, z] = ClientChunk::world_to_block(world_x, world_y, world_z,
chunk_x, chunk_z);
if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y ||
z >= CHUNK_SIZE) {
return;
}
acc->second->set_chunk_block(ClientChunk::index(x, y, z), id);
acc->second->mark_dirty();
}
auto pool = m_thread_pool.load();
pool->enqueue(0, [this, pos]() {
std::shared_ptr<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);
});
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);
}
}
}
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);
m_pending_delete_vbo.push_back(vbo);
}
void ClientWorld::push_delete_vao(GLuint vao) {
std::lock_guard lk(m_delete_vao_mutex);
m_pending_delete_vao.push_back(vao);
}
void ClientWorld::report_block_change(const glm::ivec3& pos,
unsigned id) const {
{
AABB block_box = get_block_aabb(pos);
std::shared_lock lock(m_other_players_mutex);
for (auto& [uuid, player] : m_other_players) {
AABB box = ClientPlayer::get_aabb(player.target_pos);
if (box.intersects(block_box)) {
return;
}
}
}
Arena arena;
auto* req = Arena::Create<BlockChangeReq>(&arena);
req->set_uuid(m_player.get_uuid());
req->set_block(id);
auto* p = req->mutable_pos();
p->set_x(pos.x);
p->set_y(pos.y);
p->set_z(pos.z);
m_client->send(make_packet(*req), 0);
}
void ClientWorld::receive_block_change(const BlockChangeRsp& rsp) {
glm::vec3 pos{rsp.pos().x(), rsp.pos().y(), rsp.pos().z()};
set_block(pos, rsp.block());
}
void ClientWorld::receive_time(const UpdateTime& rsp) {
m_game_ticks = rsp.game_tick();
m_day_tick = rsp.day_tick();
}
void ClientWorld::receive_remote_player(const PlayerInfoRsp& rsp) {
{
std::lock_guard lock(m_other_players_mutex);
glm::vec3 pos{rsp.pos().x(), rsp.pos().y(), rsp.pos().z()};
auto it = m_other_players.find(rsp.uuid());
if (it == m_other_players.end()) {
m_other_players.emplace(
std::piecewise_construct, std::forward_as_tuple(rsp.uuid()),
std::forward_as_tuple(rsp.name(), pos, pos));
} else {
it->second.target_pos = pos;
}
// Logger::info("Player {} pos Update", rsp.name());
}
}
void ClientWorld::receive_player_logout(const LogoutRsp& rsp) {
if (rsp.server_stop()) {
m_receive_exit = true;
return;
}
if (rsp.uuid() == m_player.get_uuid()) {
m_receive_exit = true;
return;
}
{
std::lock_guard lock(m_other_players_mutex);
int sum = m_other_players.erase(rsp.uuid());
if (sum == 0) {
Logger::warn("Player {} not find", rsp.uuid());
} else {
Logger::info("Player {} erase", rsp.uuid());
}
}
}
void ClientWorld::init(std::string_view player_name,
std::shared_ptr<NetworkClient> client) {
m_player.init(player_name);
m_client = client;
// timer
register_timer("player_pos", 1, [this]() { report_player_pos(); });
LoginReq req;
req.set_name(m_player.get_name());
while (!client->is_connected()) {
if (client->is_connect_error()) {
throw std::runtime_error("Can't connect to the server");
}
std::this_thread::sleep_for(milliseconds(200));
}
start_thread_pool();
// request login
Logger::info("Send Login Request");
m_client->send(make_packet(req), 0);
}
void ClientWorld::start_client_thread(std::string_view uuid) {
if (m_game_running) {
Logger::error("Game Already Running");
return;
}
// response
m_player.set_uuid(uuid);
m_client_thread = std::jthread([this](std::stop_token token) {
m_game_running = true;
client_run(token);
});
// Wait for 20 ticks, after the server's central chunk is generated, then
// request chunks
std::this_thread::sleep_for(milliseconds(20 * DEFAULT_PER_TICK_TIME));
request_chunk();
}
void ClientWorld::stop_client_thread() {
m_client_thread.request_stop();
if (m_client_thread.joinable()) {
m_client_thread.join();
}
m_game_running = false;
}
void ClientWorld::start_thread_pool() {
int max_threads = std::thread::hardware_concurrency();
int threads = std::min<size_t>(max_threads, 4);
change_pool_threads(threads);
}
void ClientWorld::stop_thread_pool() {
auto pool_ptr = m_thread_pool.load();
if (pool_ptr) {
pool_ptr->stop();
}
m_thread_pool.store(nullptr);
Logger::info("Thread Pool Stopped");
}
void ClientWorld::change_pool_threads(int threads) {
int m_max_threads = std::thread::hardware_concurrency();
if (m_max_threads < 1) {
Logger::warn("Can't Get Max Support Threads, Set Max Threads to 4");
m_max_threads = 1;
}
int used_thread = std::clamp(threads, 1, m_max_threads);
Logger::info("Create New Thread Pool Use {} Threads", used_thread);
m_thread_pool.store(std::make_shared<PriorityThreadPool>(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;
for (auto& x : m_timers) {
x.second.update();
}
std::this_thread::sleep_until(next);
}
}
void ClientWorld::report_player_pos() {
if (!m_client) {
return;
}
Arena arena;
auto* pos = Arena::Create<PlayerPos>(&arena);
pos->set_uuid(m_player.get_uuid());
glm::vec3 player_pos = m_player.get_player_pos();
auto* v3 = pos->mutable_pos();
v3->set_x(player_pos.x);
v3->set_y(player_pos.y);
v3->set_z(player_pos.z);
m_client->send(make_packet(*pos), 0);
}
void ClientWorld::update_chunk(const ChunkPosSet& old, const ChunkPosSet& now) {
// Elements in the old set that are not contained in now are not needed by
// the current player.
for (auto& pos : old) {
if (!now.contains(pos)) {
chunk_acc acc;
if (!m_chunks.find(acc, pos)) {
Logger::warn("Update Ref Count Error, can't Find old pos "
"in m_chunks");
continue;
}
m_chunks.erase(acc);
}
}
}
void ClientWorld::request_chunk() {
if (m_requesting_chunk.exchange(true)) {
Logger::warn("It is requesting new chunk!");
return;
}
ChunkPosSet required_chunks;
glm::vec3 player_pos = m_player.get_player_pos();
int x = std::floor(player_pos.x);
int z = std::floor(player_pos.z);
auto [chunk_x, chunk_z] = get_chunk_pos(x, z);
int radius = m_rendering_distance;
Logger::info("Client Chunk Radius {}", radius);
int r2 = radius * radius;
required_chunks.reserve(radius * radius);
for (int dx = -radius; dx <= radius; ++dx) {
for (int dz = -radius; dz <= radius; ++dz) {
if (dx * dx + dz * dz <= r2) {
required_chunks.emplace(chunk_x + dx, chunk_z + dz);
}
}
}
ChunkPosSet old = std::move(m_player.get_chunk_pos_set());
m_player.update_chunk_set(required_chunks);
ChunkPosVector need_send_pos;
for (auto pos : required_chunks) {
chunk_cacc cacc;
if (!m_chunks.find(cacc, pos)) {
need_send_pos.emplace_back(pos);
}
}
update_chunk(old, required_chunks);
if (need_send_pos.empty()) {
m_requesting_chunk = false;
return;
}
using enum ChunkLoadStyle;
switch (m_chunk_load_style) {
case RANDOM:
break;
case CENTER: {
glm::vec3 player_pos = m_player.get_player_pos();
ChunkPos player_chunk_pos = get_chunk_pos(player_pos.x, player_pos.z);
auto dist2 = [player_chunk_pos](ChunkPos chunk_pos) {
float dx = player_chunk_pos.x - chunk_pos.x;
float dz = player_chunk_pos.z - chunk_pos.z;
return dx * dx + dz * dz;
};
std::sort(need_send_pos.begin(), need_send_pos.end(),
[&dist2](const auto& a, const auto& b) {
return dist2(a) < dist2(b);
});
}
}
auto uuid = m_player.get_uuid();
Arena arena;
++m_chunk_task_id;
auto* req = Arena::Create<ChunkDataReq>(&arena);
for (const auto& pos : need_send_pos) {
req->set_task_id(m_chunk_task_id.load());
req->set_uuid(uuid);
auto* p = req->mutable_pos();
p->set_x(pos.x);
p->set_z(pos.z);
m_client->send(make_packet(*req));
}
Logger::info("Send Chunk Request Success");
m_requesting_chunk = false;
}
void ClientWorld::receive_chunk(std::vector<uint8_t> raw_data,
PacketHeader header) {
// vertex data will genrator in client thread pool instead of net thread;
auto pool = m_thread_pool.load();
if (!pool) {
Logger::error("Client Thread Pool is nullptr");
return;
}
pool->enqueue(
[this, raw_data = std::move(raw_data), header = std::move(header)]() {
Arena arena;
auto* data = Arena::Create<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;
}
}
}
void ClientWorld::update(float delta_time) {
m_player.update(delta_time);
{
std::lock_guard lk(m_delete_vbo_mutex);
for (auto x : m_pending_delete_vbo) {
glDeleteBuffers(1, &x);
}
m_pending_delete_vbo.clear();
}
{
std::lock_guard lk(m_delete_vao_mutex);
for (auto x : m_pending_delete_vao) {
glDeleteVertexArrays(1, &x);
}
m_pending_delete_vao.clear();
}
std::vector<std::unique_ptr<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
}
}
}
for (auto& c : new_chunks) {
c->upload_to_gpu();
}
for (auto& c : new_chunks) {
m_chunks.emplace(c->get_chunk_pos(), std::move(c));
}
m_render_snapshots.clear();
ChunkPos pos;
while (m_dirty_chunk_queue.try_pop(pos)) {
std::shared_ptr<ClientChunk> chunk;
{
chunk_acc acc;
if (m_chunks.find(acc, pos)) {
chunk = acc->second;
}
}
if (!chunk) {
continue;
}
chunk->upload_to_gpu();
}
auto chunk_pos_set = m_player.get_chunk_pos_set();
for (auto& pos : chunk_pos_set) {
std::shared_ptr<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);
}
}
}
glm::vec3 ClientWorld::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 ClientWorld::rendering_distance() const {
return m_rendering_distance.load();
}
void ClientWorld::rendering_distance(int rendering_distance) {
m_rendering_distance = rendering_distance;
Logger::info("Set Rendering dist {} , the value is {}", rendering_distance,
m_rendering_distance.load());
request_chunk();
}
int ClientWorld::get_chunk_task_id() const { return m_chunk_task_id.load(); }
const std::vector<const ChunkRenderSnapshot*>&
ClientWorld::render_snapshots() const {
return m_render_snapshots;
};
const std::vector<RemotePlayerRenderData>&
ClientWorld::render_player_data() const {
return m_render_player_data;
}
std::vector<glm::vec4>& ClientWorld::planes() { return m_planes; }
} // namespace Cubed

View File

@@ -0,0 +1,205 @@
#include "Cubed/gameplay/network_client.hpp"
#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) {}
NetworkClient::~NetworkClient() { close(); }
void NetworkClient::start(std::string ip, int port) {
if (m_net_thread.joinable()) {
return;
}
m_net_thread = std::thread([self = shared_from_this(), ip, port]() {
asio::co_spawn(self->m_strand, self->connect(ip, port), asio::detached);
self->m_io.run();
});
Logger::info("NetworkClient Started");
}
bool NetworkClient::is_connected() const { return m_connected.load(); }
bool NetworkClient::is_connect_error() const { return m_connect_error.load(); }
asio::awaitable<void> NetworkClient::connect(std::string ip, int port) {
Logger::info("Connect Begin");
try {
auto ex = co_await asio::this_coro::executor;
tcp::resolver resolver(ex);
auto eps = co_await resolver.async_resolve(ip, std::to_string(port),
asio::use_awaitable);
Logger::info("Resolve Success");
co_await async_connect(m_socket, eps, asio::use_awaitable);
Logger::info("Connect Success, Server ip {} port {}", ip, port);
asio::co_spawn(m_strand, read_loop(), asio::detached);
Logger::info("NetworkClient Read Loop Started");
m_connected = true;
co_return;
} catch (const std::exception& e) {
Logger::error("Client Error {}", e.what());
m_connect_error = true;
}
}
asio::awaitable<void> NetworkClient::read_loop() {
try {
while (true) {
std::array<uint8_t, HEADER_LEN> header_buffer;
co_await asio::async_read(m_socket, asio::buffer(header_buffer),
asio::use_awaitable);
auto header = decode_packet_header(header_buffer);
uint32_t total_len = HEADER_LEN + header.compressed_size;
if (total_len < HEADER_LEN || total_len > MAX_PACKET_SIZE) {
throw std::runtime_error("invalid packet length");
}
// maybe move, don't use it after switch!
std::vector<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;
switch (header.cmd) {
case std::to_underlying(PacketEnum::LOGIN_RSP): {
auto* rsp = Arena::Create<LoginRsp>(&arena);
Logger::info("Client: Receive Login rsp");
if (decode_packet(*rsp, body_data, header)) {
if (rsp->success()) {
m_world.start_client_thread(rsp->uuid());
} else {
Logger::error("Connected Server Fail");
}
}
} break;
case std::to_underlying(PacketEnum::CHUNK_DATA_RSP): {
// Logger::info("Client: Receive Chunk Data rsp, size {}mb",
// body_data.size() / 1024.0f / 1024);
m_world.receive_chunk(std::move(body_data), header);
} break;
case std::to_underlying(PacketEnum::BLOCK_CHANGE_RSP): {
auto* rsp = Arena::Create<BlockChangeRsp>(&arena);
Logger::info("Client: Receive Block Change rsp");
if (decode_packet(*rsp, body_data, header)) {
m_world.receive_block_change(*rsp);
}
} break;
case std::to_underlying(PacketEnum::UPDATE_TIME): {
auto* rsp = Arena::Create<UpdateTime>(&arena);
if (decode_packet(*rsp, body_data, header)) {
m_world.receive_time(*rsp);
}
} break;
case std::to_underlying(PacketEnum::PLAYER_INFO_RSP): {
auto* rsp = Arena::Create<PlayerInfoRsp>(&arena);
if (decode_packet(*rsp, body_data, header)) {
m_world.receive_remote_player(*rsp);
}
} break;
case std::to_underlying(PacketEnum::LOGOUT_RSP): {
auto* rsp = Arena::Create<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();
}
}
} break;
}
}
} catch (const asio::system_error& e) {
auto ec = e.code();
if (ec == asio::error::eof || ec == asio::error::operation_aborted) {
Logger::info("Client disconnected");
} else {
Logger::warn("Asio Error {}", e.what());
}
close();
} catch (const std::exception& e) {
Logger::error("Session Error {}", e.what());
close();
} catch (...) {
Logger::error("Unknow Error");
close();
}
co_return;
}
void NetworkClient::send(Packet packet, int priority) {
if (m_closed.load()) {
return;
}
asio::post(m_strand, [self = shared_from_this(), packet = std::move(packet),
priority]() mutable {
bool idle = self->m_write_queue.empty();
self->m_write_queue.emplace(priority, self->m_sequence++,
std::move(packet));
if (idle) {
self->do_write();
}
});
}
void NetworkClient::do_write() {
if (m_closed.load()) {
return;
}
auto self = shared_from_this();
auto packet = std::move(m_write_queue.top().packet);
asio::async_write(
m_socket, asio::buffer(*packet),
asio::bind_executor(m_strand, [self](std::error_code ec, size_t) {
if (ec) {
Logger::warn("Write Ec {}", ec.message());
self->close();
return;
}
self->m_write_queue.pop();
if (!self->m_write_queue.empty()) {
self->do_write();
}
}));
}
void NetworkClient::close() {
if (m_closed.exchange(true)) {
return;
}
std::error_code ec;
m_socket.shutdown(tcp::socket::shutdown_both, ec);
m_socket.close(ec);
Logger::info("NetworkClient Closed");
m_connected = false;
m_io.stop();
}
void NetworkClient::stop() {
close();
if (m_net_thread.joinable()) {
m_net_thread.join();
}
}
} // namespace Cubed

View File

@@ -0,0 +1,94 @@
#include "Cubed/gameplay/network_server.hpp"
#include "Cubed/tools/log.hpp"
using asio::ip::tcp;
namespace Cubed {
NetworkServer::NetworkServer(int port) : m_port(port) {}
NetworkServer::~NetworkServer() { stop(); }
void NetworkServer::stop() {
if (!m_started) {
return;
}
if (m_stopped.exchange(true)) {
return;
}
m_io.stop();
std::vector<std::shared_ptr<Session>> sessions;
{
std::lock_guard lock(m_session_mutex);
for (auto& [id, s] : m_session) {
sessions.push_back(s);
}
m_session.clear();
}
for (auto& s : sessions) {
s->close();
}
if (m_net_thread.joinable()) {
Logger::info("Server join thread={}, current={}", m_net_thread.get_id(),
std::this_thread::get_id());
m_net_thread.join();
}
Logger::info("Server Net Thread Stopped!");
}
asio::awaitable<void> NetworkServer::listen() {
try {
tcp::acceptor acceptor(m_io, tcp::endpoint(tcp::v4(), m_port));
while (!m_stopped) {
tcp::socket socket =
co_await acceptor.async_accept(asio::use_awaitable);
std::shared_ptr<Session> s =
std::make_shared<Session>(std::move(socket), m_world, m_io);
{
std::lock_guard lock(m_session_mutex);
m_session.emplace(s->uuid(), s);
}
s->start();
}
} catch (const std::exception& e) {
if (!m_stopped) {
Logger::error("accept error {}", e.what());
}
} catch (...) {
if (!m_stopped) {
Logger::error("Network Server: Unknown Error");
}
}
co_return;
}
void NetworkServer::net_run() {
if (m_net_thread.joinable()) {
return;
}
m_net_thread = std::thread([this]() {
asio::co_spawn(m_io, listen(), asio::detached);
m_io.run();
});
Logger::info("Server Started!");
}
void NetworkServer::start_server(int port) {
m_port = port;
m_world.init_world();
net_run();
m_started = true;
}
int NetworkServer::port() const { return m_port; }
ServerWorld& NetworkServer::server_world() { return m_world; }
} // namespace Cubed

View File

@@ -0,0 +1,211 @@
#include "Cubed/gameplay/server_chunk.hpp"
#include "Cubed/tools/cubed_assert.hpp"
namespace Cubed {
ServerChunk::ServerChunk(ServerWorld& world, ChunkPos chunk_pos,
bool temp_chunk)
: m_temp_chunk(temp_chunk), m_chunk_pos(chunk_pos), m_world(world) {}
ServerChunk::ServerChunk(ServerChunk&& other) noexcept
: m_gening(other.m_gening.load()), m_has_cave(other.m_has_cave),
m_biome(other.m_biome.load()), m_chunk_pos(std::move(other.m_chunk_pos)),
m_world(other.m_world), m_heightmap(std::move(other.m_heightmap)),
m_blocks(std::move(other.m_blocks)),
m_neightbor_blocks(std::move(other.m_neightbor_blocks)),
m_seed(other.m_seed), m_conditions(other.m_conditions) {
ASSERT_MSG(!other.m_gening, "Other is Gening Can't Move");
}
ServerChunk& ServerChunk::operator=(ServerChunk&& other) noexcept {
// Logger::info("other Chunk pos {} {} in Chunk& Chunk::operator=(Chunk&&
// other) this {}", other.m_chunk_pos.x, other.m_chunk_pos.z,
// static_cast<const void*>(&other));
if (this == &other) {
return *this;
}
ASSERT_MSG(!other.m_gening, "Other is Gening Can't Move");
m_chunk_pos = std::move(other.m_chunk_pos);
m_heightmap = std::move(other.m_heightmap);
m_blocks = std::move(other.m_blocks);
m_biome = other.m_biome.load();
m_seed = other.m_seed;
m_conditions = other.m_conditions;
m_neightbor_blocks = std::move(other.m_neightbor_blocks);
m_has_cave = other.m_has_cave;
m_gening = other.m_gening.load();
return *this;
}
std::tuple<int, int, int> ServerChunk::world_to_block(int world_x, int world_y,
int world_z, int chunk_x,
int chunk_z) {
int x, y, z;
y = world_y;
x = world_x - chunk_x * CHUNK_SIZE;
z = world_z - chunk_z * CHUNK_SIZE;
return {x, y, z};
}
std::tuple<int, int, int>
ServerChunk::world_to_block(const glm::ivec3& block_pos, ChunkPos chunk_pos) {
return world_to_block(block_pos.x, block_pos.y, block_pos.z, chunk_pos.x,
chunk_pos.z);
}
std::tuple<int, int, int>
ServerChunk::block_to_world(int x, int y, int z, int chunk_x, int chunk_z) {
int world_x = x + chunk_x * CHUNK_SIZE;
int world_z = z + chunk_z * CHUNK_SIZE;
int world_y = y;
return {world_x, world_y, world_z};
}
std::tuple<int, int, int>
ServerChunk::block_to_world(const glm::ivec3& block_pos, ChunkPos chunk_pos) {
return block_to_world(block_pos.x, block_pos.y, block_pos.z, chunk_pos.x,
chunk_pos.z);
}
BiomeType ServerChunk::get_biome() const { return m_biome.load(); }
ChunkPos ServerChunk::get_chunk_pos() const { return m_chunk_pos; }
const std::vector<BlockType>& ServerChunk::get_chunk_blocks() const {
return m_blocks;
}
HeightMapArray ServerChunk::get_heightmap() const {
// Logger::info("Chunk pos {} {} in get_heightmap this {}", m_chunk_pos.x,
// m_chunk_pos.z, static_cast<const void*>(this));
return m_heightmap;
}
int ServerChunk::index(int x, int y, int z) {
ASSERT(!(x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y ||
z >= CHUNK_SIZE));
if ((x * WORLD_SIZE_Y + y) * CHUNK_SIZE + z < 0 ||
(x * WORLD_SIZE_Y + y) * CHUNK_SIZE + z >=
CHUNK_SIZE * CHUNK_SIZE * WORLD_SIZE_Y) {
Logger::error("block pos x {} y {} z {} range error", x, y, z);
ASSERT(0);
}
return (x * WORLD_SIZE_Y + y) * CHUNK_SIZE + z;
}
int ServerChunk::index(const glm::vec3& pos) {
return ServerChunk::index(pos.x, pos.y, pos.z);
}
void ServerChunk::gen_phase_one() {
m_generator = std::make_unique<ChunkGenerator>(*this);
if (!m_generator) {
Logger::error("ChunkGenerator is Nullptr");
return;
}
m_generator->assign_chunk_biome();
m_seed = m_generator->chunk_seed();
}
void ServerChunk::gen_phase_two() {
if (!m_generator) {
Logger::error("ChunkGenerator is Nullptr");
return;
}
m_generator->generate_heightmap();
}
void ServerChunk::gen_phase_three() {
if (!m_generator) {
Logger::error("ChunkGenerator is Nullptr");
return;
}
m_generator->generate_terrain_blocks();
}
void ServerChunk::gen_phase_four(
const std::array<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 ServerChunk::gen_phase_five() {
if (!m_generator) {
Logger::error("ChunkGenerator is Nullptr");
return;
}
m_generator->ocean_build();
m_generator->generate_river();
m_generator->generate_cave();
m_generator->generate_vegetation();
m_generator = nullptr;
}
void ServerChunk::gen_chunk() {
if (m_gening.exchange(true))
return;
m_gening = true;
ASSERT_MSG(m_blocks.empty(),
"Blocks isn't Empty, chunk already generated!");
if (m_blocks.size() != 0) {
Logger::warn(
"Request Generator Chunk {} {} ,but the Blocks size is Not 0",
m_chunk_pos.x, m_chunk_pos.z);
return;
}
std::vector<ServerChunk> neighbor;
for (int i = 0; i < 4; i++) {
neighbor.emplace_back(m_world, m_chunk_pos + CHUNK_DIR[i], true);
}
for (auto& chunk : neighbor) {
chunk.gen_phase_one();
chunk.gen_phase_two();
chunk.gen_phase_three();
chunk.gen_phase_five();
}
gen_phase_one();
gen_phase_two();
gen_phase_three();
for (int i = 0; i < 4; i++) {
m_neightbor_blocks[i] = neighbor[i].get_chunk_blocks();
}
gen_phase_four(m_neightbor_blocks);
gen_phase_five();
m_gening = false;
}
// Logger::info("Cross Sum {}", m_cross_vertices_sum.load());
bool ServerChunk::is_temp_chunk() const { return m_temp_chunk.load(); }
bool& ServerChunk::has_cave() { return m_has_cave; }
const OptionalBlockVectorArray& ServerChunk::get_neightbor_blocks() const {
return m_neightbor_blocks;
}
void ServerChunk::set_chunk_block(int index, unsigned id) {
m_blocks[index] = id;
}
ChunkPos ServerChunk::chunk_pos() const { return m_chunk_pos; }
BiomeType ServerChunk::biome() const { return m_biome; }
void ServerChunk::biome(BiomeType b) { m_biome = b; }
HeightMapArray& ServerChunk::heightmap() { return m_heightmap; }
std::vector<BlockType>& ServerChunk::blocks() { return m_blocks; }
ServerWorld& ServerChunk::world() { return m_world; }
unsigned ServerChunk::seed() const {
if (m_seed == 0) {
Logger::warn("Seed Not Generator");
}
return m_seed;
}
BiomeConditions& ServerChunk::conditions() { return m_conditions; }
} // namespace Cubed

View File

@@ -0,0 +1,56 @@
#include "Cubed/gameplay/server_player.hpp"
#include "Cubed/gameplay/server_world.hpp"
namespace Cubed {
ServerPlayer::ServerPlayer(std::string_view name, std::string_view uuid,
ServerWorld& world, std::shared_ptr<Session> session,
TickType gametick)
: m_name(name), m_uuid(uuid), m_world(world), m_session(session),
m_last_gametick(gametick) {}
const glm::vec3& ServerPlayer::get_pos() const { return m_pos; }
const std::string& ServerPlayer::get_name() const { return m_name; }
const std::string& ServerPlayer::get_uuid() const { return m_uuid; }
std::shared_ptr<Session> ServerPlayer::get_session() const { return m_session; }
void ServerPlayer::update_pos(float x, float y, float z) {
m_pos = glm::vec3{x, y, z};
ChunkPos chunk_pos = get_chunk_pos(x, z);
float dist = distance2(chunk_pos, m_last_chunk_pos);
if (dist > 2) {
m_world.need_gen(m_uuid);
m_last_chunk_pos = chunk_pos;
}
}
void ServerPlayer::update_sync_gametick(TickType gametick) {
m_last_gametick = gametick;
}
bool ServerPlayer::is_disconnect(TickType current_gametick) const {
if (current_gametick - m_last_gametick > TIMEOUT) {
return true;
}
return false;
}
int ServerPlayer::task_id() const { return m_chunk_task_id.load(); }
void ServerPlayer::task_id(int id) { m_chunk_task_id = id; }
bool ServerPlayer::has_player(ChunkPos pos) const {
std::shared_lock lock(m_chunk_pos_mutex);
return m_player_chunk_pos_set.find(pos) != m_player_chunk_pos_set.end();
}
void ServerPlayer::update_chunk_set(const ChunkPosSet& set) {
std::lock_guard lock(m_chunk_pos_mutex);
m_player_chunk_pos_set.clear();
m_player_chunk_pos_set.insert(set.begin(), set.end());
}
const ServerPlayer::ChunkPosSet& ServerPlayer::get_chunk_pos_set() const {
std::shared_lock lock(m_chunk_pos_mutex);
return m_player_chunk_pos_set;
}
ServerPlayer::ChunkPosSet& ServerPlayer::get_chunk_pos_set() {
std::lock_guard lock(m_chunk_pos_mutex);
return m_player_chunk_pos_set;
}
} // namespace Cubed

View File

@@ -0,0 +1,829 @@
#include "Cubed/gameplay/server_world.hpp"
#include "Cubed/config.hpp"
#include "Cubed/gameplay/packet.hpp"
#include "Cubed/gameplay/session.hpp"
#include "Cubed/tools/cubed_assert.hpp"
#include "Cubed/tools/log.hpp"
#include "Cubed/tools/uuid.hpp"
#include <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() {
if (!m_init) {
return;
}
if (m_stopped.exchange(true)) {
return;
}
send_server_stop();
stop_gen_thread();
stop_server_thread();
// wait_all_chunk_tasks();
stop_thread_pool();
m_finished_queue.clear();
m_chunks.clear();
}
void ServerWorld::update_ref_count(const ChunkPosSet& old,
const ChunkPosSet& now) {
// Elements in the old set that are not contained in now are not needed by
// the current player.
for (auto& pos : old) {
if (!now.contains(pos)) {
chunk_acc acc;
if (!m_chunks.find(acc, pos)) {
Logger::warn("Update Ref Count Error, can't Find old pos "
"in m_chunks");
continue;
}
if (acc->second.ref_count == 0) {
Logger::error("Chunk {} {} error, ref count is 0", pos.x,
pos.z);
m_chunks.erase(acc);
continue;
}
if (--acc->second.ref_count == 0) {
m_chunks.erase(acc);
}
}
}
for (auto& pos : now) {
chunk_acc acc;
if (!m_chunks.find(acc, pos)) {
Logger::warn(
"Update Ref Count Error, can't Find now pos in m_chunks");
continue;
}
if (!old.contains(pos)) {
++acc->second.ref_count;
}
}
}
void ServerWorld::send_time() {
Arena arena;
auto* rsp = Arena::Create<UpdateTime>(&arena);
rsp->set_day_tick(m_day_tick);
rsp->set_game_tick(m_game_ticks);
for (auto& [uuid, player] : m_players) {
player.get_session()->send(make_packet(*rsp), 3);
}
}
void ServerWorld::send_chunk(int task_id, const std::string& uuid,
ChunkPos pos) {
{
std::shared_lock lock(m_player_mutex);
auto it = m_players.find(uuid);
if (it == m_players.end()) {
return;
}
if (task_id < it->second.task_id()) {
// Old chunk requests are simply discarded
return;
}
}
Arena arean;
ChunkDataRsp* rsp = Arena::Create<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]() {
std::vector<std::string> disconnect;
{
std::shared_lock lock(m_player_mutex);
for (auto& [uuid, player] : m_players) {
if (player.is_disconnect(m_game_ticks)) {
disconnect.emplace_back(uuid);
}
}
}
for (auto& uuid : disconnect) {
handle_player_exit(uuid);
}
});
// Periodically process pending players
register_timer("player chunk send", 1, [this]() {
PendingRequest request;
if (m_waiting_chunk_requests.try_pop(request)) {
handle_chunk_req(request.task_id, request.uuid, request.pos);
}
});
m_cave_carcer.init(ChunkGenerator::seed());
m_river_worm.init(ChunkGenerator::seed());
// m_chunks.reserve(MAX_DISTANCE * MAX_DISTANCE * 4);
start_thread_pool();
auto t1 = std::chrono::system_clock::now();
start_gen_thread();
init_chunks();
auto t2 = std::chrono::system_clock::now();
auto d = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1);
Logger::info("Chunk Block Init Finish, Time Consuming: {}", d);
start_server_thread();
m_init = true;
}
void ServerWorld::init_chunks() { hot_reload(); }
void ServerWorld::gen_chunks_internal(const std::string& uuid) {
// Logger::info("gen_chunks_internal");
m_chunk_gen_finished = false;
ChunkPosSet required_chunks_set;
compute_required_chunks(required_chunks_set, uuid);
std::vector<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!!");
Logger::info("New Gen Chunks Sum: {}", need_gen_chunks_pos.size());
if (need_gen_chunks_pos.empty()) {
m_could_gen = true;
return;
}
NewChunkVector new_chunks;
// Create new chunk
for (auto& pos : need_gen_chunks_pos) {
new_chunks.emplace_back(
pos, std::make_unique<ServerChunk>(ServerChunk(*this, pos)));
}
submit_new_chunks(uuid, new_chunks);
m_chunk_gen_finished = true;
}
void ServerWorld::compute_required_chunks(
ChunkPosSet& required_chunks, const std::optional<std::string>& uuid) {
glm::vec3 player_pos;
if (uuid == std::nullopt) {
player_pos = glm::vec3{0.0f};
} else {
player_pos = get_player_pos(uuid.value());
}
int x = std::floor(player_pos.x);
int z = std::floor(player_pos.z);
auto [chunk_x, chunk_z] = get_chunk_pos(x, z);
int radius = m_rendering_distance;
int r2 = radius * radius;
required_chunks.reserve(radius * radius);
for (int dx = -radius; dx <= radius; ++dx) {
for (int dz = -radius; dz <= radius; ++dz) {
if (dx * dx + dz * dz <= r2) {
required_chunks.emplace(chunk_x + dx, chunk_z + dz);
}
}
}
}
void ServerWorld::sync_and_collect_missing_chunks(
std::vector<ChunkPos>& need_gen_chunks_pos,
const ChunkPosSet& required_chunks) {
for (auto pos : required_chunks) {
chunk_acc acc;
if (m_chunks.insert(acc, pos)) {
need_gen_chunks_pos.push_back(pos);
acc->second = ChunkEntity{ChunkState::GENERATING, nullptr, 0};
}
}
}
void ServerWorld::submit_new_chunks(const std::string& uuid,
NewChunkVector& new_chunks) {
using enum ChunkLoadStyle;
auto pool_ptr = m_gen_thread_pool.load();
if (!pool_ptr) {
return;
}
switch (m_chunk_load_style) {
case RANDOM:
// Enqueue directly in random order
for (auto& task : new_chunks) {
pool_ptr->enqueue([&task, this]() {
std::unique_ptr<ServerChunk> chunk{std::move(task.chunk)};
chunk->gen_chunk();
m_finished_queue.push(std::move(chunk));
});
}
break;
case CENTER: {
std::vector<std::pair<ChunkPos, PendingChunk*>> tasks;
for (auto& task : new_chunks) {
tasks.emplace_back(task.pos, &task);
}
glm::vec3 player_pos = get_player_pos(uuid);
ChunkPos player_chunk_pos = get_chunk_pos(player_pos.x, player_pos.z);
auto dist2 = [player_chunk_pos](ChunkPos chunk_pos) {
float dx = player_chunk_pos.x - chunk_pos.x;
float dz = player_chunk_pos.z - chunk_pos.z;
return dx * dx + dz * dz;
};
std::sort(tasks.begin(), tasks.end(),
[&dist2](const auto& a, const auto& b) {
return dist2(a.first) < dist2(b.first);
});
const int CHUNKS_PER_PRIORITY = m_gen_pool_threads;
for (size_t i = 0; i < tasks.size(); ++i) {
int priority = 10 + static_cast<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));
});
}
} break;
}
}
void ServerWorld::start_gen_thread() {
m_gen_running = true;
Logger::info("Gen Thread Started");
m_gen_thread = std::jthread([this](std::stop_token token) {
while (!token.stop_requested()) {
std::unique_lock<std::mutex> lk(m_need_gen_queue_mutex);
m_gen_cv.wait(lk, token, [this]() {
return m_need_gen_chunk.load() || !m_gen_running ||
!m_need_gen_queue.empty();
});
if (!m_gen_running) {
break;
}
if (token.stop_requested()) {
break;
}
m_need_gen_chunk = false;
std::string uuid;
if (!m_need_gen_queue.empty()) {
uuid = m_need_gen_queue.front();
m_need_gen_queue.pop();
}
lk.unlock();
gen_chunks_internal(uuid);
}
});
}
void ServerWorld::start_server_thread() {
m_server_thread =
std::jthread([this](std::stop_token token) { serever_run(token); });
}
void ServerWorld::start_thread_pool() {
int max_thread = std::thread::hardware_concurrency();
if (m_gen_pool_threads == 0) {
m_gen_pool_threads = change_pool_threads(m_gen_thread_pool,
max_thread - RESERVED_THREADS);
} else {
m_gen_pool_threads =
change_pool_threads(m_gen_thread_pool, m_gen_pool_threads);
}
if (m_net_pool_threads == 0) {
m_net_pool_threads = change_pool_threads(m_net_thread_pool, 4);
} else {
m_net_pool_threads =
change_pool_threads(m_net_thread_pool, m_net_pool_threads);
}
}
void ServerWorld::stop_gen_thread() {
m_gen_running = false;
m_gen_cv.notify_all();
m_gen_thread.request_stop();
if (m_gen_thread.joinable()) {
m_gen_thread.join();
}
Logger::info("Gen Thread Stopped");
}
void ServerWorld::stop_server_thread() {
m_server_thread.request_stop();
if (m_server_thread.joinable()) {
m_server_thread.join();
}
}
void ServerWorld::stop_thread_pool() {
auto pool_ptr = m_gen_thread_pool.load();
if (pool_ptr) {
pool_ptr->stop();
}
m_gen_thread_pool.store(nullptr);
Logger::info("Gen Thread Pool Stopped");
auto p = m_net_thread_pool.load();
if (p) {
p->stop();
}
m_net_thread_pool.store(nullptr);
Logger::info("Net Thread Pool Stopped");
}
void ServerWorld::serever_run(std::stop_token stoken) {
Logger::info("Server Thread Started!");
using Clock = std::chrono::steady_clock;
constexpr auto TICK = std::chrono::milliseconds(DEFAULT_PER_TICK_TIME);
auto next = Clock::now();
while (!stoken.stop_requested()) {
next += TICK;
if (m_tick_running) {
++m_game_ticks;
m_day_tick = (m_day_tick + 1) % DAY_TIME;
}
update();
std::this_thread::sleep_until(next);
}
Logger::info("Server Thread Stopped!");
}
void ServerWorld::need_gen(std::string uuid) {
// if (!m_could_gen) {
// Logger::warn("It is generating or consuming new chunks");
// return;
// }
m_could_gen = false;
{
std::lock_guard lock(m_need_gen_queue_mutex);
m_need_gen_queue.enqueue(std::move(uuid));
}
// m_gen_player_pos = get_player("TestPlayer").get_player_pos();
m_need_gen_chunk = true;
m_gen_cv.notify_one();
}
bool ServerWorld::set_block(const glm::ivec3& block_pos, unsigned id) {
int world_x, world_y, world_z;
world_x = block_pos.x;
world_y = block_pos.y;
world_z = block_pos.z;
auto [chunk_x, chunk_z] = get_chunk_pos(world_x, world_z);
chunk_acc acc;
if (!m_chunks.find(acc, ChunkPos{chunk_x, chunk_z})) {
return false;
}
if (acc->second.state != ChunkState::READY) {
return false;
}
auto [x, y, z] = ServerChunk::world_to_block(world_x, world_y, world_z,
chunk_x, chunk_z);
if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y ||
z >= CHUNK_SIZE) {
return false;
}
acc->second.chunk->set_chunk_block(ServerChunk::index(x, y, z), id);
return true;
}
void ServerWorld::hot_reload() {
auto& config = Config::get();
int dist = config.get<int>("world.rendering_distance");
m_rendering_distance = dist <= MAX_DISTANCE ? dist : MAX_DISTANCE;
}
void ServerWorld::update() {
// poll_finished_chunks();
{
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;
consumed = true;
}
if (consumed) {
m_could_gen = true;
}
}
send_time();
for (auto& [id, timer] : m_timers) {
timer.update();
}
}
void ServerWorld::sync_player_pos(const std::string& uuid, float x, float y,
float z) {
std::string name;
{
std::lock_guard lock(m_player_mutex);
auto it = m_players.find(uuid);
if (it == m_players.end()) {
Logger::warn("Player {} is not in this Server", uuid);
return;
}
it->second.update_pos(x, y, z);
it->second.update_sync_gametick(m_game_ticks);
name = it->second.get_name();
}
ChunkPos pos = get_chunk_pos(x, z);
// update other player pos;
std::vector<std::shared_ptr<Session>> other;
{
std::shared_lock lock(m_player_mutex);
for (auto& [o_uuid, player] : m_players) {
if (o_uuid == uuid) {
continue;
}
if (player.has_player(pos)) {
other.emplace_back(player.get_session());
}
}
}
for (auto& session : other) {
if (!session) {
continue;
}
Arena arena;
auto* rsp = Arena::Create<PlayerInfoRsp>(&arena);
rsp->set_uuid(uuid);
rsp->set_name(name);
auto* pos = rsp->mutable_pos();
pos->set_x(x);
pos->set_y(y);
pos->set_z(z);
session->send(make_packet(*rsp), 0);
}
}
void ServerWorld::handle_player_login(const std::string& name,
std::shared_ptr<Session> session) {
std::string uuid = generate_uuid();
Logger::info("Player {} (uuid {}) join the world", name, uuid);
bool sucess = true;
{
std::lock_guard lock(m_player_mutex);
auto [_, inserted] = m_players.emplace(
std::piecewise_construct, std::forward_as_tuple(std::string(uuid)),
std::forward_as_tuple(name, uuid, *this, session, m_game_ticks));
if (!inserted) {
Logger::error("Player insert Fail");
}
sucess = inserted;
}
Arena arena;
if (!sucess) {
auto* rsp = Arena::Create<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);
}
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);
return;
}
}
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);
for (auto& [uuid, player] : m_players) {
sessions.emplace_back(player.get_session());
}
}
for (auto& s : sessions) {
if (s) {
s->send(make_packet(*rsp), 0);
}
}
}
glm::vec3 ServerWorld::get_player_pos(const std::string& uuid) const {
std::shared_lock lock(m_player_mutex);
auto it = m_players.find(uuid);
if (it == m_players.end()) {
Logger::error("Can't find player uuid {}", uuid);
return glm::vec3{0.0f};
}
return it->second.get_pos();
}
void ServerWorld::handle_chunk_req(int task_id, const std::string& uuid,
ChunkPos pos) {
{
std::shared_lock lock(m_player_mutex);
auto it = m_players.find(uuid);
if (it == m_players.end()) {
return;
}
if (it->second.task_id() < task_id) {
// task_id is an atomic variable, can be operated on directly
it->second.task_id(task_id);
}
}
auto pool = m_net_thread_pool.load();
pool->enqueue(
[task_id, uuid, pos, this]() { send_chunk(task_id, uuid, pos); });
}
void ServerWorld::handle_block_change(const BlockChangeReq& req) {
float x = std::floor(req.pos().x());
float y = std::floor(req.pos().y());
float z = std::floor(req.pos().z());
if (!set_block(glm::ivec3(x, y, z), req.block())) {
return;
}
Arena arena;
BlockChangeRsp* rsp = Arena::Create<BlockChangeRsp>(&arena);
auto* pos = rsp->mutable_pos();
pos->set_x(x);
pos->set_y(y);
pos->set_z(z);
rsp->set_block(req.block());
std::vector<std::shared_ptr<Session>> sessions;
auto chunk_pos = get_chunk_pos(x, z);
{
std::shared_lock lock(m_player_mutex);
for (auto& [uuid, player] : m_players) {
if (player.has_player(chunk_pos)) {
auto session = player.get_session();
sessions.emplace_back(std::move(session));
}
}
}
for (auto& x : sessions) {
if (x) {
x->send(make_packet(*rsp), 1);
}
}
}
int ServerWorld::rendering_distance() const {
return m_rendering_distance.load();
}
void ServerWorld::rendering_distance(int rendering_distance) {
m_rendering_distance = rendering_distance;
}
CaveCarver& ServerWorld::cave_carcer() { return m_cave_carcer; }
RiverWorm& ServerWorld::river_worm() { return m_river_worm; }
TickType ServerWorld::game_tick() const { return m_game_ticks.load(); }
TickType ServerWorld::day_tick() const { return m_day_tick.load(); }
void ServerWorld::day_tick(TickType tick) {
tick %= DAY_TIME;
m_day_tick = tick;
}
int ServerWorld::per_tick_time() const { return m_per_tick_time.load(); }
void ServerWorld::per_tick_time(int ms) { m_per_tick_time = ms; }
bool ServerWorld::is_tick_running() const { return m_tick_running.load(); }
void ServerWorld::tick_running(bool run) { m_tick_running = run; }
int ServerWorld::gen_pool_threads() const { return m_gen_pool_threads.load(); }
int ServerWorld::max_threads() const { return m_max_threads.load(); }
void ServerWorld::change_pool_threads(ThreadPoolKind kind, int threads) {
switch (kind) {
case ThreadPoolKind::NET:
m_net_pool_threads = change_pool_threads(m_net_thread_pool, threads);
break;
case ThreadPoolKind::GEN:
m_gen_pool_threads = change_pool_threads(m_gen_thread_pool, threads);
break;
}
}
int ServerWorld::change_pool_threads(
std::atomic<std::shared_ptr<ThreadPool>>& 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<ThreadPool>(used_thread));
return 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());
}
void ServerWorld::set_chunk_load_style(int id) {
using enum ChunkLoadStyle;
switch (id) {
case std::to_underlying(RANDOM):
m_chunk_load_style = RANDOM;
return;
case std::to_underlying(CENTER):
m_chunk_load_style = CENTER;
return;
}
Logger::error("Can,t Find Chunk Load Style Id {}, Nothing Will Do", id);
}
int ServerWorld::chunk_size() const { return m_chunks.size(); }
} // namespace Cubed

150
src/gameplay/session.cpp Normal file
View File

@@ -0,0 +1,150 @@
#include "Cubed/gameplay/session.hpp"
#include "Cubed/gameplay/server_world.hpp"
#include "Cubed/tools/log.hpp"
#include "Cubed/tools/uuid.hpp"
using asio::ip::tcp;
using namespace google::protobuf;
namespace Cubed {
Session::Session(tcp::socket socket, ServerWorld& server_world,
asio::io_context& io)
: m_socket(std::move(socket)), m_strand(asio::make_strand(io)),
m_uuid(generate_uuid()), m_server_world(server_world) {}
Session::~Session() {}
void Session::start() {
auto self = shared_from_this();
asio::co_spawn(
m_strand,
[self]() -> asio::awaitable<void> { co_await self->read_loop(); },
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 {
bool idle = self->m_write_queue.empty();
self->m_write_queue.emplace(priority, self->m_sequence++,
std::move(packet));
if (idle) {
self->do_write();
}
});
}
const std::string& Session::uuid() const { return m_uuid; }
asio::awaitable<void> Session::read_loop() {
try {
while (true) {
std::array<uint8_t, HEADER_LEN> header_buffer;
co_await asio::async_read(m_socket, asio::buffer(header_buffer),
asio::use_awaitable);
auto header = decode_packet_header(header_buffer);
uint32_t total_len = HEADER_LEN + header.compressed_size;
if (total_len < HEADER_LEN || total_len > MAX_PACKET_SIZE) {
throw std::runtime_error("invalid packet length");
}
std::vector<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);
}
auto cmd_id = header.cmd;
Arena arena;
if (cmd_id == std::to_underlying(PacketEnum::LOGIN_REQ)) {
auto* req = Arena::Create<LoginReq>(&arena);
Logger::info("Session: Receive Login req");
if (decode_packet(*req, body_data, header)) {
m_server_world.handle_player_login(req->name(),
shared_from_this());
}
}
if (cmd_id == std::to_underlying(PacketEnum::PLAYER_POS)) {
auto* pos = Arena::Create<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 == std::to_underlying(PacketEnum::CHUNK_DATA_REQ)) {
auto* req = Arena::Create<ChunkDataReq>(&arena);
// Logger::info("Session: Receive Chunk Data req");
if (decode_packet(*req, body_data, header)) {
m_server_world.handle_chunk_req(
req->task_id(), req->uuid(),
ChunkPos(req->pos().x(), req->pos().z()));
}
}
if (cmd_id == std::to_underlying(PacketEnum::BLOCK_CHANGE_REQ)) {
auto* req = Arena::Create<BlockChangeReq>(&arena);
Logger::info("Session: Receive Block Change req");
if (decode_packet(*req, body_data, header)) {
m_server_world.handle_block_change(*req);
}
}
if (cmd_id == std::to_underlying(PacketEnum::LOGOUT_REQ)) {
auto* req = Arena::Create<LogoutReq>(&arena);
if (decode_packet(*req, body_data, header)) {
m_server_world.handle_player_exit(req->uuid());
}
}
}
} catch (const asio::system_error& e) {
auto ec = e.code();
if (ec == asio::error::eof || ec == asio::error::operation_aborted) {
Logger::info("Client disconnected");
} else {
Logger::warn("Asio Error {}", e.what());
}
close();
} catch (const std::exception& e) {
Logger::error("Session Error {}", e.what());
close();
} catch (...) {
Logger::error("Unknow Error");
close();
}
co_return;
}
void Session::do_write() {
auto self = shared_from_this();
auto packet = std::move(m_write_queue.top().packet);
asio::async_write(
m_socket, asio::buffer(*packet),
asio::bind_executor(m_strand, [self](std::error_code ec, size_t) {
if (ec) {
Logger::warn("Write Ec {}", ec.message());
self->close();
return;
}
self->m_write_queue.pop();
if (!self->m_write_queue.empty()) {
self->do_write();
}
}));
}
void Session::close() {
if (m_closed.exchange(true)) {
return;
}
std::error_code ec;
m_socket.shutdown(tcp::socket::shutdown_both, ec);
m_socket.close(ec);
}
} // namespace Cubed

View File

@@ -1,6 +1,6 @@
#include "Cubed/gameplay/tree.hpp"
#include "Cubed/gameplay/chunk.hpp"
#include "Cubed/gameplay/server_chunk.hpp"
#include <array>
@@ -27,10 +27,10 @@ static constexpr std::array<TreeStructNode, 62> TREE{{
{{-1, 3, -2}, 6}, {{-2, 3, -1}, 6},
}};
bool build_tree(Chunk& chunk, const glm::ivec3& pos) {
bool build_tree(ServerChunk& chunk, const glm::ivec3& pos) {
auto& block = chunk.get_chunk_blocks();
if (block[Chunk::index(pos)] != 1) {
if (block[ServerChunk::index(pos)] != 1) {
return false;
}
for (const auto& d : TREE) {
@@ -42,13 +42,13 @@ bool build_tree(Chunk& chunk, const glm::ivec3& pos) {
z >= CHUNK_SIZE) {
return false;
}
if (block[Chunk::index(tree_node)] != 0) {
if (block[ServerChunk::index(tree_node)] != 0) {
return false;
}
}
for (const auto& d : TREE) {
auto tree_node = pos + d.offset;
chunk.set_chunk_block(Chunk::index(tree_node), d.id);
chunk.set_chunk_block(ServerChunk::index(tree_node), d.id);
}
return true;
}

View File

@@ -1,9 +1,9 @@
#include "Cubed/gameplay/vertex_data.hpp"
#include "Cubed/gameplay/world.hpp"
#include "Cubed/gameplay/client_world.hpp"
namespace Cubed {
VertexData::VertexData(World& world) : m_world(world) {}
VertexData::VertexData(ClientWorld& world) : m_world(world) {}
VertexData::~VertexData() {
if (m_vbo != 0) {
m_world.push_delete_vbo(m_vbo);

View File

@@ -1,703 +0,0 @@
#include "Cubed/gameplay/world.hpp"
#include "Cubed/config.hpp"
#include "Cubed/gameplay/player.hpp"
#include "Cubed/tools/cubed_assert.hpp"
#include "Cubed/tools/cubed_hash.hpp"
#include <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

@@ -1,5 +1,14 @@
#include "Cubed/app.hpp"
#ifdef _WIN32
extern "C" {
__declspec(dllexport) unsigned long NvOptimusEnablement = 1;
__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
}
#endif
int main(int argc, char** argv) {
static_assert(sizeof(int) == sizeof(int32_t));

20
src/proto/auth/auth.proto Normal file
View File

@@ -0,0 +1,20 @@
syntax = "proto3";
message LoginReq {
string name = 1;
}
message LoginRsp {
bool success = 1;
string uuid = 2;
}
message LogoutReq {
string uuid = 1;
}
message LogoutRsp {
string uuid = 1;
bool server_stop = 2;
}

View File

@@ -0,0 +1,6 @@
syntax = "proto3";
message ChunkPosNet {
int32 x = 1;
int32 z = 2;
}

View File

@@ -0,0 +1,6 @@
syntax = "proto3";
message Error {
int32 code = 1;
string mes = 2;
}

View File

@@ -0,0 +1,5 @@
syntax = "proto3";
message PlayerInfo {
string name = 1;
}

View File

@@ -0,0 +1,7 @@
syntax = "proto3";
message Vec3 {
float x = 1;
float y = 2;
float z = 3;
}

12
src/proto/packet.proto Normal file
View File

@@ -0,0 +1,12 @@
syntax = "proto3";
import "common/error.proto";
import "common/player_info.proto";
import "common/vector3.proto";
import "player/player.proto";
import "auth/auth.proto";
import "system/ping.proto";
import "system/pong.proto";
import "world/chunk_data.proto";
import "world/block_change.proto";
import "world/time.proto";

View File

@@ -0,0 +1,14 @@
syntax = "proto3";
import "common/vector3.proto";
message PlayerPos {
string uuid = 1;
Vec3 pos = 2;
}
message PlayerInfoRsp {
string uuid = 1;
string name = 2;
Vec3 pos = 3;
}

View File

@@ -0,0 +1,5 @@
syntax = "proto3";
message Ping {
uint64 timestamp = 1;
}

View File

@@ -0,0 +1,5 @@
syntax = "proto3";
message Pong {
uint64 timestamp = 1;
}

View File

@@ -0,0 +1,14 @@
syntax = "proto3";
import "common/vector3.proto";
message BlockChangeReq {
string uuid = 1;
Vec3 pos = 2;
uint32 block = 3;
}
message BlockChangeRsp {
Vec3 pos = 1;
uint32 block = 2;
}

View File

@@ -0,0 +1,25 @@
syntax = "proto3";
import "common/chunk_pos.proto";
message ChunkDataReq {
int32 task_id = 1;
string uuid = 2;
ChunkPosNet pos = 3;
}
message ChunkDataRsp {
int32 task_id = 1;
ChunkPosNet pos = 2;
uint32 chunk_seed = 3;
int32 biome_type = 4;
repeated uint32 chunk_blocks = 5 [packed=true];
repeated uint32 neighbor_blocks_1 = 6 [packed=true];
repeated uint32 neighbor_blocks_2 = 7 [packed=true];
repeated uint32 neighbor_blocks_3 = 8 [packed=true];
repeated uint32 neighbor_blocks_4 = 9 [packed=true];
}
message S2C_ClearAllChunks {
bool clear = 1;
}

View File

@@ -0,0 +1,6 @@
syntax = "proto3";
message UpdateTime {
int64 game_tick = 1;
int64 day_tick = 2;
}

View File

@@ -4,8 +4,8 @@
#include "Cubed/config.hpp"
#include "Cubed/debug_collector.hpp"
#include "Cubed/dev_panel.hpp"
#include "Cubed/gameplay/player.hpp"
#include "Cubed/gameplay/world.hpp"
#include "Cubed/gameplay/client_player.hpp"
#include "Cubed/gameplay/client_world.hpp"
#include "Cubed/primitive_data.hpp"
#include "Cubed/texture_manager.hpp"
#include "Cubed/tools/cubed_assert.hpp"
@@ -21,31 +21,34 @@
namespace Cubed {
Renderer::Renderer(const Camera& camera, World& world,
Renderer::Renderer(const Camera& camera, ClientWorld& world,
const TextureManager& texture_manager, DevPanel& dev_panel)
: m_camera(camera), m_dev_panel(dev_panel),
m_texture_manager(texture_manager), m_world(world) {}
Renderer::~Renderer() {
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDeleteBuffers(1, &m_outline_vbo);
glDeleteBuffers(1, &m_outline_indices_vbo);
glDeleteBuffers(1, &m_sky_vbo);
glDeleteBuffers(1, &m_ui_vbo);
glDeleteBuffers(1, &m_text_vbo);
glBindVertexArray(0);
glDeleteVertexArrays(NUM_VAO, m_vao.data());
glDeleteFramebuffers(1, &m_fbo);
glDeleteTextures(1, &m_screen_texture);
glDeleteTextures(1, &m_screen_depth_texture);
if (m_init) {
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDeleteBuffers(1, &m_outline_vbo);
glDeleteBuffers(1, &m_outline_indices_vbo);
glDeleteBuffers(1, &m_sky_vbo);
glDeleteBuffers(1, &m_ui_vbo);
glDeleteBuffers(1, &m_text_vbo);
glDeleteBuffers(1, &m_player_vbo);
glBindVertexArray(0);
glDeleteVertexArrays(NUM_VAO, m_vao.data());
glDeleteFramebuffers(1, &m_fbo);
glDeleteTextures(1, &m_screen_texture);
glDeleteTextures(1, &m_screen_depth_texture);
glDeleteFramebuffers(1, &m_oit_fbo);
glDeleteTextures(1, &m_accum_texture);
glDeleteTextures(1, &m_reveal_texture);
glDeleteTextures(1, &m_oit_depth_texture);
glDeleteFramebuffers(1, &m_oit_fbo);
glDeleteTextures(1, &m_accum_texture);
glDeleteTextures(1, &m_reveal_texture);
glDeleteTextures(1, &m_oit_depth_texture);
glDeleteFramebuffers(1, &m_depth_map_fbo);
glDeleteTextures(1, &m_depth_map_texture);
glDeleteFramebuffers(1, &m_depth_map_fbo);
glDeleteTextures(1, &m_depth_map_texture);
}
}
void Renderer::hot_reload() {
@@ -86,7 +89,9 @@ void Renderer::init() {
"shaders/billboard_f_shader.glsl"};
Shader water_shader{"water", "shaders/water_v_shader.glsl",
"shaders/water_f_shader.glsl"};
Shader player_shader{"player", "shaders/player_v_shader.glsl",
"shaders/player_f_shader.glsl"};
m_shaders.insert({player_shader.hash(), std::move(player_shader)});
m_shaders.insert({world_shader.hash(), std::move(world_shader)});
m_shaders.insert({outline_shader.hash(), std::move(outline_shader)});
m_shaders.insert({sky_shdaer.hash(), std::move(sky_shdaer)});
@@ -100,6 +105,7 @@ void Renderer::init() {
m_shaders.insert({depth_shader.hash(), std::move(depth_shader)});
m_shaders.insert({billboard.hash(), std::move(billboard)});
m_shaders.insert({water_shader.hash(), std::move(water_shader)});
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
@@ -109,9 +115,9 @@ void Renderer::init() {
#ifdef DEBUG_MODE
glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(
[](GLenum source, GLenum type, GLuint id, GLenum severity,
GLsizei length, const GLchar* message, const void* user_param) {
Logger::log(Logger::Level::DEBUG, std::source_location::current(),
[](GLenum, GLenum, GLuint, GLenum, GLsizei, const GLchar* message,
const void*) {
Logger::log(Logger::Level::L_DEBUG, std::source_location::current(),
"GL Debug: {}", reinterpret_cast<const char*>(message));
},
nullptr);
@@ -165,12 +171,22 @@ void Renderer::init() {
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glBindVertexArray(m_vao[5]);
glGenBuffers(1, &m_player_vbo);
glBindBuffer(GL_ARRAY_BUFFER, m_player_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(VERTICES_PLAYER), VERTICES_PLAYER,
GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
init_quad();
init_text();
hot_reload();
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
m_init = true;
}
const Shader& Renderer::get_shader(const std::string& name) const {
@@ -214,7 +230,7 @@ void Renderer::render() {
render_sky();
render_world();
render_outline();
render_player();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glEnable(GL_FRAMEBUFFER_SRGB);
glDisable(GL_DEPTH_TEST);
@@ -260,7 +276,7 @@ void Renderer::render_outline() {
const auto& shader = get_shader("outline");
shader.use();
const auto& block_pos = m_world.get_look_block_pos("TestPlayer");
const auto& block_pos = m_world.get_look_block_pos();
if (block_pos != std::nullopt) {
@@ -660,37 +676,42 @@ void Renderer::render_world() {
glActiveTexture(GL_TEXTURE1);
glEnable(GL_DEPTH_TEST);
for (const auto& snapshot : m_render_snapshots) {
if (!snapshot) {
continue;
}
glBindTexture(GL_TEXTURE_2D_ARRAY,
m_texture_manager.get_texture_array());
glBindVertexArray(snapshot.normal_vao);
glBindVertexArray(snapshot->normal_vao);
glDrawArrays(GL_TRIANGLES, 0, snapshot.normal_vertices_count);
glDrawArrays(GL_TRIANGLES, 0, snapshot->normal_vertices_count);
}
// cross_plane and discard
for (const auto& snapshot : m_render_snapshots) {
if (!snapshot) {
continue;
}
glm::vec2 camera_pos_xz{camera_pos.x, camera_pos.z};
if (snapshot.cross_vertices_count != 0) {
glm::vec2 center_xz{snapshot.center.x, snapshot.center.z};
if (snapshot->cross_vertices_count != 0) {
glm::vec2 center_xz{snapshot->center.x, snapshot->center.z};
float dist2d = glm::distance(camera_pos_xz, center_xz);
if (dist2d <= CROSS_PLANE_DISTANCE * 16) {
glBindTexture(GL_TEXTURE_2D_ARRAY,
m_texture_manager.get_cross_plane_array());
glBindVertexArray(snapshot.cross_vao);
glBindVertexArray(snapshot->cross_vao);
glDrawArrays(GL_TRIANGLES, 0,
snapshot.cross_vertices_count);
snapshot->cross_vertices_count);
}
}
if (snapshot.normal_discard_vertices_count != 0) {
if (snapshot->normal_discard_vertices_count != 0) {
glBindTexture(GL_TEXTURE_2D_ARRAY,
m_texture_manager.get_texture_array());
glBindVertexArray(snapshot.normal_discard_vao);
glBindVertexArray(snapshot->normal_discard_vao);
glDrawArrays(GL_TRIANGLES, 0,
snapshot.normal_discard_vertices_count);
snapshot->normal_discard_vertices_count);
}
}
}
@@ -746,28 +767,33 @@ void Renderer::render_world() {
glBindTexture(GL_TEXTURE_2D_ARRAY, m_texture_manager.get_pbr_texture());
normal_block_shader.set_loc("enablePBR", m_pbr);
for (const auto& snapshot : m_render_snapshots) {
if (Math::is_aabb_in_frustum(snapshot.center, snapshot.half_extents,
if (!snapshot) {
continue;
}
if (Math::is_aabb_in_frustum(snapshot->center, snapshot->half_extents,
m_planes)) {
glBindVertexArray(snapshot.normal_vao);
glBindVertexArray(snapshot->normal_vao);
glDrawArrays(GL_TRIANGLES, 0, snapshot.normal_vertices_count);
glDrawArrays(GL_TRIANGLES, 0, snapshot->normal_vertices_count);
rendered_sum++;
}
}
// discard
for (const auto& snapshot : m_render_snapshots) {
if (!Math::is_aabb_in_frustum(snapshot.center, snapshot.half_extents,
if (!snapshot) {
continue;
}
if (!Math::is_aabb_in_frustum(snapshot->center, snapshot->half_extents,
m_planes)) {
continue;
}
if (snapshot.normal_discard_vertices_count != 0) {
glBindVertexArray(snapshot.normal_discard_vao);
if (snapshot->normal_discard_vertices_count != 0) {
glBindVertexArray(snapshot->normal_discard_vao);
glDrawArrays(GL_TRIANGLES, 0,
snapshot.normal_discard_vertices_count);
snapshot->normal_discard_vertices_count);
}
}
// cross_plane
@@ -776,18 +802,21 @@ void Renderer::render_world() {
m_texture_manager.get_cross_plane_array());
normal_block_shader.set_loc("enablePBR", false);
for (const auto& snapshot : m_render_snapshots) {
if (!Math::is_aabb_in_frustum(snapshot.center, snapshot.half_extents,
if (!snapshot) {
continue;
}
if (!Math::is_aabb_in_frustum(snapshot->center, snapshot->half_extents,
m_planes)) {
continue;
}
glm::vec2 camera_pos_xz{camera_pos.x, camera_pos.z};
if (snapshot.cross_vertices_count != 0) {
glm::vec2 center_xz{snapshot.center.x, snapshot.center.z};
if (snapshot->cross_vertices_count != 0) {
glm::vec2 center_xz{snapshot->center.x, snapshot->center.z};
float dist2d = glm::distance(camera_pos_xz, center_xz);
if (dist2d <= CROSS_PLANE_DISTANCE * 16) {
glBindVertexArray(snapshot.cross_vao);
glBindVertexArray(snapshot->cross_vao);
glDrawArrays(GL_TRIANGLES, 0, snapshot.cross_vertices_count);
glDrawArrays(GL_TRIANGLES, 0, snapshot->cross_vertices_count);
}
}
}
@@ -837,16 +866,20 @@ void Renderer::render_world() {
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D_ARRAY, m_texture_manager.get_texture_array());
for (const auto& snapshot : m_render_snapshots) {
if (!Math::is_aabb_in_frustum(snapshot.center, snapshot.half_extents,
if (!snapshot) {
continue;
}
if (!Math::is_aabb_in_frustum(snapshot->center, snapshot->half_extents,
m_planes)) {
continue;
}
if (snapshot.normal_blend_vertices_count != 0) {
if (snapshot->normal_blend_vertices_count != 0) {
glBindVertexArray(snapshot.normal_blend_vao);
glBindVertexArray(snapshot->normal_blend_vao);
glDrawArrays(GL_TRIANGLES, 0, snapshot.normal_blend_vertices_count);
glDrawArrays(GL_TRIANGLES, 0,
snapshot->normal_blend_vertices_count);
}
}
@@ -886,16 +919,19 @@ void Renderer::render_world() {
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D_ARRAY, m_texture_manager.get_texture_array());
for (const auto& snapshot : m_render_snapshots) {
if (!Math::is_aabb_in_frustum(snapshot.center, snapshot.half_extents,
if (!snapshot) {
continue;
}
if (!Math::is_aabb_in_frustum(snapshot->center, snapshot->half_extents,
m_planes)) {
continue;
}
if (snapshot.water_vertices_count != 0) {
if (snapshot->water_vertices_count != 0) {
glBindVertexArray(snapshot.water_vao);
glBindVertexArray(snapshot->water_vao);
glDrawArrays(GL_TRIANGLES, 0, snapshot.water_vertices_count);
glDrawArrays(GL_TRIANGLES, 0, snapshot->water_vertices_count);
}
}
@@ -924,6 +960,26 @@ void Renderer::render_world() {
DebugCollector::get().report(
"rendered_chunk", "Rendered Chunk: " + std::to_string(rendered_sum));
}
void Renderer::render_player() {
auto& shader = get_shader("player");
shader.use();
m_v_mat = m_camera.get_camera_lookat();
auto& players = m_world.render_player_data();
for (auto& player : players) {
m_m_mat = glm::translate(
glm::mat4(1.0f), player.render_pos + glm::vec3(-0.5f, 0.0f, -0.5f));
m_mv_mat = m_v_mat * m_m_mat;
shader.set_loc("mv_matrix", m_mv_mat);
shader.set_loc("proj_matrix", m_p_mat);
glBindVertexArray(m_vao[5]);
glEnable(GL_DEPTH_TEST);
glDrawArrays(GL_TRIANGLES, 0, 36);
}
}
#pragma endregion
void Renderer::render_dev_panel() {
glDisable(GL_DEPTH_TEST);

View File

@@ -36,14 +36,16 @@ TextureManager::TextureManager() {}
TextureManager::~TextureManager() { delet_texture(); }
void TextureManager::delet_texture() {
glDeleteTextures(1, &m_texture_array);
glDeleteTextures(1, &m_block_status_array);
glDeleteTextures(1, &m_cross_plane_array);
glDeleteTextures(1, &m_normal_texture_array);
for (auto& id : m_item_textures) {
glDeleteTextures(1, &id);
if (m_init) {
glDeleteTextures(1, &m_texture_array);
glDeleteTextures(1, &m_block_status_array);
glDeleteTextures(1, &m_cross_plane_array);
glDeleteTextures(1, &m_normal_texture_array);
for (auto& id : m_item_textures) {
glDeleteTextures(1, &id);
}
Logger::info("Successfully delete all texture");
}
Logger::info("Successfully delete all texture");
}
GLuint TextureManager::get_block_status_array() const {
@@ -313,6 +315,7 @@ void TextureManager::init_texture() {
init_block();
init_block_status();
init_ui();
m_init = true;
}
void TextureManager::update() {

View File

@@ -1,105 +0,0 @@
#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

3
src/version.hpp.in Normal file
View File

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

View File

@@ -18,10 +18,14 @@ static int windowed_width = 800, windowed_height = 600;
Window::Window(Renderer& renderer) : m_renderer(renderer) {}
Window::~Window() {
if (m_imgui_init) {
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
}
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
if (ImGui::GetCurrentContext() != nullptr) {
ImGui::DestroyContext();
}
if (m_window) {
glfwDestroyWindow(m_window);
@@ -212,6 +216,8 @@ void Window::imgui_init() {
// Setup Platform/Renderer backends
ImGui_ImplGlfw_InitForOpenGL(m_window, false);
ImGui_ImplOpenGL3_Init();
m_imgui_init = true;
}
} // namespace Cubed