Skip to content

Commit

Permalink
Implement tick-based block update
Browse files Browse the repository at this point in the history
  • Loading branch information
AdamYuan committed Oct 31, 2023
1 parent 6109a8c commit f4d94b6
Show file tree
Hide file tree
Showing 14 changed files with 238 additions and 44 deletions.
3 changes: 2 additions & 1 deletion block/block/flowing_water.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#pragma once
#include "public/Trait.hpp"
#include "public/LiquidEvent.hpp"
#include "public/Trait.hpp"

#include <algorithm>

Expand Down Expand Up @@ -162,6 +162,7 @@ template <> struct BlockTrait<kFlowingWater> {
(LightLvl)0,
BlockCollision::kWater,
&kFlowingWaterMesh<Variant>,
&kLiquidEvent<kWater, kFlowingWater, 8, 5>,
};
}
};
3 changes: 3 additions & 0 deletions block/block/water.hpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#pragma once
#include "public/LiquidEvent.hpp"
#include "public/Trait.hpp"

template <> struct BlockTrait<kWater> : public SingleBlockTrait<kWater> {
Expand All @@ -8,5 +9,7 @@ template <> struct BlockTrait<kWater> : public SingleBlockTrait<kWater> {
BlockTransparency::kSemiTransparent,
(LightLvl)0,
BlockCollision::kWater,
nullptr,
&kLiquidEvent<kWater, kFlowingWater, 8, 5>,
};
};
1 change: 1 addition & 0 deletions client/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ add_executable(HyperCraft_client
src/ChunkSetSunlightTask.cpp
src/ChunkFloodSunlightTask.cpp
src/ChunkSetBlockTask.cpp
src/ChunkUpdateBlockTask.cpp
)

target_link_libraries(HyperCraft_client
Expand Down
25 changes: 11 additions & 14 deletions client/include/client/Chunk.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class Chunk : public std::enable_shared_from_this<Chunk> {

inline const ChunkPos3 &GetPosition() const { return m_position; }

// TODO: Protect Block & Light RW with mutexes
// Block Getter and Setter
inline const Block *GetBlockData() const { return m_blocks; }
template <typename T> inline Block GetBlock(T x, T y, T z) const { return m_blocks[ChunkXYZ2Index(x, y, z)]; }
Expand All @@ -53,6 +54,12 @@ class Chunk : public std::enable_shared_from_this<Chunk> {
inline Block &GetBlockRef(uint32_t idx) { return m_blocks[idx]; }
template <typename T> inline void SetBlock(T x, T y, T z, Block b) { m_blocks[ChunkXYZ2Index(x, y, z)] = b; }
inline void SetBlock(uint32_t idx, Block b) { m_blocks[idx] = b; }
template <std::signed_integral T> inline Block GetBlockFromNeighbour(T x, T y, T z) const {
return m_blocks[ChunkXYZ2Index((x + kSize) % kSize, (y + kSize) % kSize, (z + kSize) % kSize)];
}
template <std::unsigned_integral T> inline Block GetBlockFromNeighbour(T x, T y, T z) const {
return m_blocks[ChunkXYZ2Index(x % kSize, y % kSize, z % kSize)];
}

// Sunlight Getter and Setter
inline InnerPos1 GetSunlightHeight(uint32_t idx) const { return m_sunlight_heights[idx]; }
Expand All @@ -67,14 +74,6 @@ class Chunk : public std::enable_shared_from_this<Chunk> {
template <typename T> inline bool GetSunlight(T x, T y, T z) const {
return y >= m_sunlight_heights[ChunkXZ2Index(x, z)];
}

// Neighbours
template <std::signed_integral T> inline Block GetBlockFromNeighbour(T x, T y, T z) const {
return m_blocks[ChunkXYZ2Index((x + kSize) % kSize, (y + kSize) % kSize, (z + kSize) % kSize)];
}
template <std::unsigned_integral T> inline Block GetBlockFromNeighbour(T x, T y, T z) const {
return m_blocks[ChunkXYZ2Index(x % kSize, y % kSize, z % kSize)];
}
template <std::signed_integral T> inline bool GetSunlightFromNeighbour(T x, T y, T z) const {
return GetSunlight((x + kSize) % kSize, (y + kSize) % kSize, (z + kSize) % kSize);
}
Expand All @@ -83,20 +82,18 @@ class Chunk : public std::enable_shared_from_this<Chunk> {
}

// Creation
static inline std::shared_ptr<Chunk> Create(const ChunkPos3 &position) {
auto ret = std::make_shared<Chunk>();
ret->m_position = position;
return ret;
}
inline explicit Chunk(const ChunkPos3 &position) : m_position{position} {}
static inline std::shared_ptr<Chunk> Create(const ChunkPos3 &position) { return std::make_shared<Chunk>(position); }

// Generated Flag
inline void SetGeneratedFlag() { m_generated_flag.store(true, std::memory_order_release); }
inline bool IsGenerated() const { return m_generated_flag.load(std::memory_order_acquire); }

private:
const ChunkPos3 m_position{};

Block m_blocks[kSize * kSize * kSize];
InnerPos1 m_sunlight_heights[kSize * kSize]{};
ChunkPos3 m_position{};
std::atomic_bool m_generated_flag{false};
};

Expand Down
2 changes: 1 addition & 1 deletion client/include/client/ChunkFloodSunlightTask.inl
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ private:
public:
inline static constexpr ChunkTaskType kType = ChunkTaskType::kFloodSunlight;

void Run(ChunkTaskPool *p_task_pool, ChunkTaskRunnerData<ChunkTaskType::kFloodSunlight> &&data);
static void Run(ChunkTaskPool *p_task_pool, ChunkTaskRunnerData<ChunkTaskType::kFloodSunlight> &&data);
};

} // namespace hc::client
14 changes: 9 additions & 5 deletions client/include/client/ChunkTaskPool.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class Chunk;
class ChunkTaskPool;
class ChunkTaskPoolLocked;

enum class ChunkTaskType { kGenerate, kSetBlock, kSetSunlight, kMesh, kFloodSunlight, COUNT };
enum class ChunkTaskType { kGenerate, kSetBlock, kSetSunlight, kMesh, kFloodSunlight, kUpdateBlock, COUNT };
enum class ChunkTaskPriority { kHigh, kTick, kLow };

template <ChunkTaskType> class ChunkTaskData;
Expand All @@ -46,6 +46,7 @@ template <ChunkTaskType Type> class ChunkTaskDataBase {
#include "ChunkMeshTask.inl"
#include "ChunkSetBlockTask.inl"
#include "ChunkSetSunlightTask.inl"
#include "ChunkUpdateBlockTask.inl"

namespace hc::client {

Expand Down Expand Up @@ -90,17 +91,19 @@ class ChunkTaskPool {
World &m_world;
libcuckoo::cuckoohash_map<ChunkPos3, DataTuple> m_data_map;
moodycamel::ConcurrentQueue<RunnerDataVariant> m_high_priority_runner_data_queue, m_low_priority_runner_data_queue;
std::atomic_bool m_high_priority_producer_flag;
std::atomic_bool m_high_priority_producer_flag, m_tick_producer_flag;
std::mutex m_producer_mutex;

template <ChunkTaskPriority TaskPriority>
void produce_runner_data(ChunkTaskPoolToken *p_token, std::size_t max_tasks);
template <ChunkTaskPriority... TaskPriorities>
void produce_runner_data(moodycamel::ConcurrentQueue<RunnerDataVariant> *p_queue,
const moodycamel::ProducerToken &token, std::size_t max_tasks);

friend class ChunkTaskPoolToken;
friend class ChunkTaskPoolLocked;

public:
inline explicit ChunkTaskPool(World *p_world) : m_world{*p_world}, m_high_priority_producer_flag{false} {}
inline explicit ChunkTaskPool(World *p_world)
: m_world{*p_world}, m_high_priority_producer_flag{false}, m_tick_producer_flag{false} {}

template <ChunkTaskType TaskType, typename... Args> inline void Push(const ChunkPos3 &chunk_pos, Args &&...args) {
bool high_priority = false;
Expand All @@ -127,6 +130,7 @@ class ChunkTaskPool {
return m_low_priority_runner_data_queue.size_approx() + m_high_priority_runner_data_queue.size_approx();
}
void Run(ChunkTaskPoolToken *p_token);
inline void ProduceTickTasks() { m_tick_producer_flag.store(true, std::memory_order_release); }
};

class ChunkTaskPoolLocked {
Expand Down
58 changes: 58 additions & 0 deletions client/include/client/ChunkUpdateBlockTask.inl
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#include <client/Chunk.hpp>

#include <glm/gtx/hash.hpp>
#include <unordered_map>

namespace hc::client {

template <>
class ChunkTaskData<ChunkTaskType::kUpdateBlock> final : public ChunkTaskDataBase<ChunkTaskType::kUpdateBlock> {
private:
std::unordered_map<InnerPos3, uint64_t> m_updates;

public:
inline static constexpr ChunkTaskType kType = ChunkTaskType::kUpdateBlock;

inline void Push(InnerPos3 update, uint64_t tick) { m_updates[update] = tick; }
inline void Push(std::span<const InnerPos3> updates, uint64_t tick) {
for (const auto &pos : updates)
m_updates[pos] = tick;
}
inline constexpr ChunkTaskPriority GetPriority() const { return ChunkTaskPriority::kTick; }
inline bool IsQueued() const { return !m_updates.empty(); }
std::optional<ChunkTaskRunnerData<ChunkTaskType::kUpdateBlock>> Pop(const ChunkTaskPoolLocked &task_pool,
const ChunkPos3 &chunk_pos);

inline void OnUnload() { m_updates.clear(); }
};

template <> class ChunkTaskRunnerData<ChunkTaskType::kUpdateBlock> {
private:
std::array<std::shared_ptr<Chunk>, 27> m_chunk_ptr_array;
std::vector<InnerPos3> m_updates;

public:
inline static constexpr ChunkTaskType kType = ChunkTaskType::kUpdateBlock;

inline ChunkTaskRunnerData(std::array<std::shared_ptr<Chunk>, 27> &&chunk_ptr_array,
std::vector<InnerPos3> &&updates)
: m_chunk_ptr_array{std::move(chunk_ptr_array)}, m_updates{std::move(updates)} {}
inline const ChunkPos3 &GetChunkPos() const { return m_chunk_ptr_array[26]->GetPosition(); }
inline const std::shared_ptr<Chunk> &GetChunkPtr() const { return m_chunk_ptr_array[26]; }
inline const auto &GetChunkPtrArray() const { return m_chunk_ptr_array; }
inline const auto &GetUpdates() const { return m_updates; }
};

template <> class ChunkTaskRunner<ChunkTaskType::kUpdateBlock> {
private:
block::Block m_update_neighbours[block::kBlockUpdateMaxNeighbours];
InnerPos3 m_update_neighbour_pos[block::kBlockUpdateMaxNeighbours];
block::BlockUpdateSetBlock m_update_set_blocks[block::kBlockUpdateMaxNeighbours];

public:
inline static constexpr ChunkTaskType kType = ChunkTaskType::kUpdateBlock;

void Run(ChunkTaskPool *p_task_pool, ChunkTaskRunnerData<ChunkTaskType::kUpdateBlock> &&data);
};

} // namespace hc::client
7 changes: 7 additions & 0 deletions client/include/client/World.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class World : public std::enable_shared_from_this<World> {
friend class ChunkUpdatePool;
template <ChunkTaskType> friend class ChunkTaskRunner;

std::atomic_uint64_t m_tick;
static_assert(sizeof(ChunkPos3) <= sizeof(uint64_t));
std::atomic_uint64_t m_center_chunk_pos;
std::atomic<ChunkPos1> m_load_chunk_radius, m_unload_chunk_radius;
Expand Down Expand Up @@ -106,6 +107,12 @@ class World : public std::enable_shared_from_this<World> {
inline const std::weak_ptr<ClientBase> &GetClientWeakPtr() const { return m_client_weak_ptr; }
inline std::shared_ptr<ClientBase> LockClient() const { return m_client_weak_ptr.lock(); }

inline uint64_t GetCurrentTick() const { return m_tick.load(std::memory_order_acquire); }
inline void NextTick() {
m_tick.fetch_add(1, std::memory_order_acq_rel);
m_chunk_task_pool.ProduceTickTasks();
}

const auto &GetChunkPool() const { return m_chunk_pool; }
const auto &GetChunkTaskPool() const { return m_chunk_task_pool; }
const auto &GetChunkUpdatePool() const { return m_chunk_update_pool; }
Expand Down
3 changes: 2 additions & 1 deletion client/src/Application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,8 @@ void Application::glfw_key_callback(GLFWwindow *window, int key, int scancode, i
} else if (key == GLFW_KEY_ESCAPE) {
app->m_mouse_captured ^= 1;
glfwSetInputMode(window, GLFW_CURSOR, app->m_mouse_captured ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL);
}
} else if (key == GLFW_KEY_U)
app->m_world->NextTick();
}

void Application::glfw_framebuffer_resize_callback(GLFWwindow *window, int width, int height) {
Expand Down
4 changes: 2 additions & 2 deletions client/src/ChunkFloodSunlightTask.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ void ChunkTaskRunner<ChunkTaskType::kFloodSunlight>::Run(ChunkTaskPool *p_task_p
auto up_sl = up_chunk->GetSunlightHeight(xz_idx), sl = chunk->GetSunlightHeight(xz_idx);
if (up_sl > 0) {
if (sl != kChunkSize) {
set_sunlights.push_back({xz, kChunkSize});
set_sunlights.emplace_back(xz, kChunkSize);
xz_next_updates.push_back(xz);
}
} else {
Expand All @@ -57,7 +57,7 @@ void ChunkTaskRunner<ChunkTaskType::kFloodSunlight>::Run(ChunkTaskPool *p_task_p
;
++y;
if (sl != y)
set_sunlights.push_back({xz, y});
set_sunlights.emplace_back(xz, y);
if ((sl == 0) != (y == 0))
xz_next_updates.push_back(xz);
}
Expand Down
2 changes: 1 addition & 1 deletion client/src/ChunkGenerateTask.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ ChunkTaskData<ChunkTaskType::kGenerate>::Pop(const ChunkTaskPoolLocked &task_poo
void ChunkTaskRunner<ChunkTaskType::kGenerate>::Run(ChunkTaskPool *p_task_pool,
ChunkTaskRunnerData<ChunkTaskType::kGenerate> &&data) {
std::shared_ptr<ClientBase> client = p_task_pool->GetWorld().LockClient();
if (!client || !client->IsConnected())
if (!client)
return;

const auto &chunk_ptr = data.GetChunkPtr();
Expand Down
32 changes: 27 additions & 5 deletions client/src/ChunkSetBlockTask.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ void ChunkTaskRunner<ChunkTaskType::kSetBlock>::Run(ChunkTaskPool *p_task_pool,
std::bitset<27> neighbour_remesh_set{};

std::unordered_set<InnerPos2> flood_sunlights;
std::unordered_set<InnerPos3> block_updates[27];

for (const auto &block_change : block_changes) {
auto block_pos = block_change.first;
Expand All @@ -42,10 +43,24 @@ void ChunkTaskRunner<ChunkTaskType::kSetBlock>::Run(ChunkTaskPool *p_task_pool,
if (new_block == old_block)
continue;

neighbour_remesh_set[26] = true;
flood_sunlights.emplace(block_pos.x, block_pos.z);
chunk->SetBlock(block_idx, new_block);

const auto register_block_update = [&block_updates](InnerPos3 pos) {
auto [rel_chunk_pos, inner_pos] = ChunkInnerPosFromBlockPos(BlockPos3(pos));
uint32_t chunk_idx = CmpXYZ2NeighbourIndex(rel_chunk_pos.x, rel_chunk_pos.y, rel_chunk_pos.z);
block_updates[chunk_idx].insert(inner_pos);
};
register_block_update(block_pos);
register_block_update({block_pos.x - 1, block_pos.y, block_pos.z});
register_block_update({block_pos.x + 1, block_pos.y, block_pos.z});
register_block_update({block_pos.x, block_pos.y - 1, block_pos.z});
register_block_update({block_pos.x, block_pos.y + 1, block_pos.z});
register_block_update({block_pos.x, block_pos.y, block_pos.z - 1});
register_block_update({block_pos.x, block_pos.y, block_pos.z + 1});

flood_sunlights.emplace(block_pos.x, block_pos.z);

neighbour_remesh_set[26] = true;
for (uint32_t i = 0; i < 26; ++i) {
if (neighbour_remesh_set[i])
continue;
Expand All @@ -55,15 +70,22 @@ void ChunkTaskRunner<ChunkTaskType::kSetBlock>::Run(ChunkTaskPool *p_task_pool,
}
}

auto current_tick = p_task_pool->GetWorld().GetCurrentTick();
for (uint32_t i = 0; i < 27; ++i) {
if (!neighbour_remesh_set[i])
continue;
ChunkPos3 nei_pos;
Chunk::NeighbourIndex2CmpXYZ(i, glm::value_ptr(nei_pos));
nei_pos += chunk->GetPosition();
p_task_pool->Push<ChunkTaskType::kMesh>(nei_pos, data.IsHighPriority());
// Trigger remesh
if (neighbour_remesh_set[i])
p_task_pool->Push<ChunkTaskType::kMesh>(nei_pos, data.IsHighPriority());
// Trigger block update
if (!block_updates[i].empty()) {
p_task_pool->Push<ChunkTaskType::kUpdateBlock>(
nei_pos, std::vector<InnerPos3>(block_updates[i].begin(), block_updates[i].end()), current_tick);
}
}

// Trigger sunlight flood
auto flood_sunlight_vec = std::vector<InnerPos2>{flood_sunlights.begin(), flood_sunlights.end()};
p_task_pool->Push<ChunkTaskType::kFloodSunlight>(chunk->GetPosition(), std::span{flood_sunlight_vec});
}
Expand Down
Loading

0 comments on commit f4d94b6

Please sign in to comment.