Various minor changes, added visualizer to tile updates.

This commit is contained in:
2025-01-10 17:50:07 -05:00
parent c456d04da5
commit bd8ce016bf
10 changed files with 140 additions and 89 deletions

View File

@@ -47,13 +47,12 @@ namespace CaveGame::Client
bool IsChunkCellWithinViewport(const Vector2& coords) const;
// TODO: Chunk should render itself, no? Logical Separation issue here aswell...
void RenderChunk(const Vector2& coords, const Core::Chunk& chunk);
void RenderChunk(const Vector2& coords);
void LookForChunksNeedLoading();
void LookForChunksNeedUnloading();
void RenderChunkTexture(const Vector2 &coords, JGL::RenderTarget* destination, const Core::Chunk& chunk);
void RenderChunkTexture(const Vector2 &coords, JGL::RenderTarget* destination, Core::Chunk* chunk);
/// Render chunk boundaries and relative coordinates, around the camera.
void DrawChunkGrid() const;

View File

@@ -9,7 +9,7 @@
namespace CaveGame::Client {
LocalWorld::LocalWorld(const std::string& world_name, int seed, bool overwrite)
: World(world_name, seed, overwrite) {
: World(world_name, seed, overwrite) {
Logs::Info("Spinning up ChunkServerThread");
chunk_thread = std::thread(&LocalWorld::ChunkServerThread, this);
chunk_thread.detach();
@@ -46,21 +46,21 @@ namespace CaveGame::Client {
void LocalWorld::CheckCachedChunkSprites()
{
for (auto&[chunk_pos, chunk] : loaded_chunks) {
for (auto [chunk_pos, chunk] : loaded_chunks) {
if (IsChunkCellWithinViewport(chunk_pos))
{
// No rendertarget for this chunk.
if (!cached_chunk_sprites.contains(chunk_pos))
{
chunk.touched = false;
chunk->touched = false;
auto* target = new JGL::RenderTarget({Core::Chunk::ChunkSize, Core::Chunk::ChunkSize}, {0,0,0,0});
RenderChunkTexture(chunk_pos, target, chunk);
cached_chunk_sprites.insert({chunk_pos, target});
}
// rendertarget needs updating.
else if (chunk.touched) {
else if (chunk->touched) {
// TODO: Modify RenderTarget in place.
chunk.touched = false;
chunk->touched = false;
auto* target = cached_chunk_sprites[chunk_pos];
@@ -167,10 +167,10 @@ namespace CaveGame::Client {
// Draw the cached RenderTargets for our chunks.
for (const auto&[chunk_pos, chunk] : loaded_chunks)
for (auto[chunk_pos, chunk] : loaded_chunks)
{
if (IsChunkCellWithinViewport(chunk_pos))
RenderChunk(chunk_pos, chunk);
RenderChunk(chunk_pos);
//JGL::J2D::DrawString(Colors::Black, std::format("{}, {}", chunk_pos.x, chunk_pos.y), chunk.GetChunkRealCoordinates().x, chunk.GetChunkRealCoordinates().y,
// 1, 8, font);
@@ -215,9 +215,11 @@ namespace CaveGame::Client {
void LocalWorld::RenderChunkTexture(const Vector2 &coords, JGL::RenderTarget* destination, const Core::Chunk& chunk)
void LocalWorld::RenderChunkTexture(const Vector2 &coords, JGL::RenderTarget* destination, Core::Chunk* chunk)
{
#define DEBUG_TILE_UPDATES
using CaveGame::Core::TileID;
TileID t_id;
@@ -235,12 +237,20 @@ namespace CaveGame::Client {
{
for (int y = 0; y < Core::Chunk::ChunkSize; y++)
{
t_id = chunk.GetTile(x, y);
t_id = chunk->GetTile(x, y);
Vector2 relative_tile_coords = Vector2(x, y);
Vector2 tile_coords = relative_tile_coords;
#ifdef DEBUG_TILE_UPDATES
if (chunk->GetTileUpdateFlag(x, y))
JGL::J2D::DrawPoint(Color4(255, 0, 0, 128), tile_coords);
if (chunk->GetTileUpdateBufferFlag(x, y))
JGL::J2D::DrawPoint(Color4(255, 0, 0, 128), tile_coords);
#endif
if (t_id == TileID::AIR || t_id == TileID::VOID) // Air
continue;
@@ -255,15 +265,20 @@ namespace CaveGame::Client {
}
JGL::J2D::DrawPoint(t_color, tile_coords);
}
}
JGL::J2D::End();
}
void LocalWorld::RenderChunk(const Vector2& coords, const Core::Chunk& chunk) {
Vector2 GetChunkRealCoordinates(const Vector2& cell) {
return cell * Core::Chunk::ChunkSize;
}
void LocalWorld::RenderChunk(const Vector2& coords) {
auto it = cached_chunk_sprites.find(coords);
if (it != cached_chunk_sprites.end())
JGL::J2D::DrawRenderTarget(it->second, chunk.GetChunkRealCoordinates());
JGL::J2D::DrawRenderTarget(it->second, GetChunkRealCoordinates(coords));
}
void LocalWorld::LookForChunksNeedUnloading()
@@ -335,12 +350,12 @@ namespace CaveGame::Client {
// Check our generator queue for complete chunks, and pull them into our loaded chunks.
while (!ServedChunks.empty())
{
Core::Chunk c = Core::Chunk({0,0});
Core::Chunk* c = new Core::Chunk({0,0});
ServedChunks.front_pop(c);
loaded_chunks.emplace(c.GetChunkCell(), c);
loaded_chunks.emplace(c->GetChunkCell(), c);
if (std::find(chunks_in_waiting.begin(), chunks_in_waiting.end(), c.GetChunkCell()) != chunks_in_waiting.end()) {
chunks_in_waiting.erase(std::remove(chunks_in_waiting.begin(), chunks_in_waiting.end(), c.GetChunkCell()), chunks_in_waiting.end());
if (std::find(chunks_in_waiting.begin(), chunks_in_waiting.end(), c->GetChunkCell()) != chunks_in_waiting.end()) {
chunks_in_waiting.erase(std::remove(chunks_in_waiting.begin(), chunks_in_waiting.end(), c->GetChunkCell()), chunks_in_waiting.end());
}
}
}

View File

@@ -19,6 +19,7 @@
namespace CaveGame::Core
{
class Chunk {
public:
static constexpr int ChunkSize = 128;
@@ -28,12 +29,13 @@ namespace CaveGame::Core
~Chunk() { }
/// Constructs a chunk with empty (air) tiles, located at the given chunk-cell.
/// @param cell_coords Specifies the chunk-cell this chunk will occupy.
explicit Chunk(const Vector2 &cell_coords);
explicit Chunk(const Vector2 &cell);
/// Constructs a chunk from a filesystem path. This constructor will load and parse the chunk file.
explicit Chunk(const Vector2 &cell_coords, const std::filesystem::path &file);
explicit Chunk(const Vector2 &cell, const std::filesystem::path &file);
/// Returns the TileID enumeration that occupies the given tile-cell.
TileID GetTile(int x, int y) const;
@@ -48,6 +50,10 @@ namespace CaveGame::Core
/// Returns the value of the update flag field at the given tile-cell.
bool GetTileUpdateFlag(int x, int y) const;
bool GetTileUpdateBufferFlag(int x, int y) const;
void SetTileUpdateBufferFlag(int x, int y, bool flag = true);
/// Sets the value of the update flag field at the given tile-cell.A
void SetTileUpdateFlag(int x, int y, bool flag = true);
@@ -68,12 +74,16 @@ namespace CaveGame::Core
[[nodiscard]] static constexpr std::size_t BufferSize();
void SwapTileUpdateBuffers();
public:
bool touched;
protected:
Vector2 cell;
TileID tiles[ChunkSize][ChunkSize];
bool tagged_for_update[ChunkSize][ChunkSize];
bool update_buffer[ChunkSize][ChunkSize];
};

View File

@@ -156,7 +156,7 @@ namespace CaveGame::Core
float hiScale, float loScale, float noise, float ramp);
TileID ComputeTile(int wx, int wy);
void FirstPass(Chunk& chunk);
void FirstPass(Chunk* chunk);
uint ColorMap(int range, int wx, int wy);

View File

@@ -7,21 +7,20 @@ namespace CaveGame::Core
{
using TileState = uint16_t;
/// A b
class ITileMap
{
public:
virtual TileID GetTile(int x, int y) const = 0;
virtual void SetTile(int x, int y, TileID tile) = 0;
virtual bool GetTileUpdateFlag(int x, int y) = 0;
[[nodiscard]] virtual TileID GetTile(int x, int y) const = 0;
virtual void SetTile(int x, int y, TileID tile, bool flag_update = true) = 0;
[[nodiscard]] virtual bool GetTileUpdateFlag(int x, int y) const = 0;
virtual void SetTileUpdateFlag(int x, int y, bool flag = true) = 0;
virtual TileState GetTileState(int x, int y) const = 0;
//[[nodiscard]] virtual bool GetTileUpdateBufferFlag(int x, int y) const = 0;
//virtual void SetTileUpdateBufferFlag(int x, int y, bool flag = true);
[[nodiscard]] virtual TileState GetTileState(int x, int y) const = 0;
virtual void SetTileState(int x, int y, TileState state) = 0;
bool GrassSpreadable(int x, int y) const;
bool IsNonSolidTile(int x, int y) const;
bool GrassShouldSuffocate(int x, int y) const;
bool IsSolidTile(int x, int y) const;
bool HasAdjacentOrDiagonalAirBlock(int x, int y) const;
bool HasAdjacentAirBlock(int x, int y) const;
[[nodiscard]] bool IsNonSolidTile(int x, int y) const;
[[nodiscard]] bool IsSolidTile(int x, int y) const;
[[nodiscard]] bool HasAdjacentOrDiagonalAirBlock(int x, int y) const;
[[nodiscard]] bool HasAdjacentAirBlock(int x, int y) const;
};
}

View File

@@ -252,11 +252,13 @@ namespace CaveGame::Core
bool DoesForcedTicc() const override { return true;}
bool DoesRandomTicc() const override { return true;}
void ForcedTicc(ITileMap *world, TileState state, int x, int y) override {
if (world->GetTile(x, y+1) == TileID::AIR) {
if (world->GetTile(x, y+2) == TileID::AIR)
{
world->SetTile(x, y+2, numeric_id);
world->SetTile(x, y+1, TileID::AIR);
world->SetTile(x, y, TileID::AIR);
return;
}
@@ -266,19 +268,23 @@ namespace CaveGame::Core
return;
}
if (world->GetTile(x+1, y+2) == TileID::AIR) {
world->SetTile(x+1, y+2, numeric_id);
world->SetTile(x, y, TileID::AIR);
return;
if (world->GetTile(x+1, y+1) == TileID::AIR) {
if (world->GetTile(x+1, y+2) == TileID::AIR) {
world->SetTile(x+1, y+2, numeric_id);
world->SetTile(x, y, TileID::AIR);
return;
}
}
if (world->GetTile(x-1, y+2) == TileID::AIR) {
world->SetTile(x-1, y+2, numeric_id);
world->SetTile(x, y, TileID::AIR);
if (world->GetTile(x-1, y+1) == TileID::AIR) {
if (world->GetTile(x-1, y+2) == TileID::AIR) {
world->SetTile(x-1, y+2, numeric_id);
world->SetTile(x, y, TileID::AIR);
return;
}
return;
}
if (world->GetTile(x, y+1) == TileID::WATER) {
world->SetTile(x, y+1, numeric_id);
world->SetTile(x, y, TileID::WATER);

View File

@@ -45,7 +45,7 @@ namespace CaveGame::Core
TileID GetTile(int x, int y) const override;
void SetTile(int x, int y, TileID t) override;
void SetTile(int x, int y, TileID t, bool flag_update = true) override;
bool GetTileSimulationEnabled() const;
@@ -65,14 +65,14 @@ namespace CaveGame::Core
void SetTileUpdateFlag(int x, int y, bool flag) override;
bool GetTileUpdateFlag(int x, int y) override;
bool GetTileUpdateFlag(int x, int y) const override;
// TODO: Doesn't really belong here.
Vector2 ToUnitDirection(float rotation);
Chunk GetChunkAtCell(const Vector2 &cell);
Chunk* GetChunkAtCell(const Vector2 &cell);
std::unordered_map<Vector2, Chunk> GetChunkList();
std::unordered_map<Vector2, Chunk*> GetChunkList();
void DoRandomTileTicks();
@@ -117,13 +117,12 @@ namespace CaveGame::Core
void DropChunk(const Vector2& cell);
protected:
void SaveChunkToFile(const Vector2 &cell, const Chunk &chunk);
std::future<bool> SaveChunkToFileAsync(const Vector2& cell, const Chunk& chunk);
void SaveChunkToFile(const Vector2 &cell, Chunk* chunk);
bool ValidCoords(const Vector2 &cell) const;
protected:
std::vector<Entity*> entities;
const std::filesystem::path worlds {"worlds"};
std::unordered_map<Vector2, Chunk> loaded_chunks; // TODO: Consider pointers.
std::unordered_map<Vector2, Chunk*> loaded_chunks; // TODO: Consider pointers.
Generator generator;
float time_of_day;
std::string world_name;
@@ -134,7 +133,7 @@ namespace CaveGame::Core
std::atomic<bool> run_chunk_thread = true;
ConcurrentQueue<Vector2> RequestedChunks;
std::vector<Vector2> chunks_in_waiting;
ConcurrentQueue<Core::Chunk> ServedChunks;
ConcurrentQueue<Core::Chunk*> ServedChunks;
float tile_ticc_counter;

View File

@@ -18,13 +18,7 @@ namespace CaveGame::Core
if (trigger_tile_updates)
{
SetTileUpdateFlag(x, y);
//SetTileUpdateFlag(x-1, y);
//SetTileUpdateFlag(x+1, y);
//SetTileUpdateFlag(x, y-1);
//SetTileUpdateFlag(x, y+1);
}
}
}
@@ -36,6 +30,14 @@ namespace CaveGame::Core
return tagged_for_update[x][y];
}
bool Chunk::GetTileUpdateBufferFlag(int x, int y) const {
return update_buffer[x][y];
}
void Chunk::SetTileUpdateBufferFlag(int x, int y, bool flag) {
update_buffer[x][y] = flag;
}
void Chunk::SetTileUpdateFlag(int x, int y, bool flag)
{
tagged_for_update[x][y] = flag;
@@ -96,6 +98,18 @@ namespace CaveGame::Core
}
}
void Chunk::SwapTileUpdateBuffers() {
std::swap(update_buffer, tagged_for_update);
//std::memset(tagged_for_update, 0, sizeof(tagged_for_update));
for (int x = 0; x < ChunkSize; x++) {
for (int y = 0; y < ChunkSize; y++) {
tagged_for_update[x][y] = false;
}
}
}
Vector2 Chunk::GetChunkCell() const { return cell;}
}

View File

@@ -232,8 +232,8 @@ namespace CaveGame::Core
return base;
}
void Generator::FirstPass(CaveGame::Core::Chunk &chunk) {
Vector2 real_coords = chunk.GetChunkRealCoordinates();
void Generator::FirstPass(CaveGame::Core::Chunk* chunk) {
Vector2 real_coords = chunk->GetChunkRealCoordinates();
for (int x = 0; x < Chunk::ChunkSize; x++) {
for (int y = 0; y < Chunk::ChunkSize; y++) {
@@ -241,7 +241,7 @@ namespace CaveGame::Core
int wx = real_coords.x + x;
int wy = real_coords.y + y;
chunk.SetTile(x, y, ComputeTile(wx, wy));
chunk->SetTile(x, y, ComputeTile(wx, wy));
}
}
}

View File

@@ -55,12 +55,12 @@ namespace CaveGame::Core
Vector2 coords = Vector2(chunkX, chunkY);
if (HasChunkAtCell(coords))
return loaded_chunks.at(coords).GetTile(tileX, tileY);
return loaded_chunks.at(coords)->GetTile(tileX, tileY);
return TileID::VOID;
}
void World::SetTile(int x, int y, TileID t) {
void World::SetTile(int x, int y, TileID t, bool flag_update) {
float chunkX = Math::Floor((float)x / Chunk::ChunkSize);
float chunkY = Math::Floor((float)y / Chunk::ChunkSize);
float tileX = Math::Mod(x, Chunk::ChunkSize);
@@ -75,12 +75,16 @@ namespace CaveGame::Core
if (HasChunkAtCell(coords))
{
loaded_chunks.at(coords).SetTile(tileX, tileY, t);
loaded_chunks.at(coords)->SetTile(tileX, tileY, t, flag_update);
SetTileUpdateFlag(x, y-1, true);
SetTileUpdateFlag(x, y+1, true);
SetTileUpdateFlag(x-1, y, true);
SetTileUpdateFlag(x+1, y, true);
//SetTileUpdateFlag(x, y, true);
if (flag_update) {
SetTileUpdateFlag(x, y, true);
SetTileUpdateFlag(x, y-1, true);
SetTileUpdateFlag(x, y+1, true);
SetTileUpdateFlag(x-1, y, true);
SetTileUpdateFlag(x+1, y, true);
}
}
}
@@ -91,7 +95,7 @@ namespace CaveGame::Core
simulate_tiles = enabled;
}
Chunk World::GetChunkAtCell(const Vector2 &cell) {
Chunk* World::GetChunkAtCell(const Vector2 &cell) {
if (ValidCoords(cell))
return loaded_chunks.at(cell);
}
@@ -111,7 +115,7 @@ namespace CaveGame::Core
return false;
}
std::unordered_map<Vector2, Chunk> World::GetChunkList() { return loaded_chunks; }
std::unordered_map<Vector2, Chunk*> World::GetChunkList() { return loaded_chunks; }
void World::SetTileUpdateFlag(int x, int y, bool flag) {
float chunkX = Math::Floor((float)x / Chunk::ChunkSize);
@@ -129,10 +133,10 @@ namespace CaveGame::Core
const Vector2 coords = Vector2(chunkX, chunkY);
if (HasChunkAtCell(coords))
loaded_chunks.at(coords).SetTileUpdateFlag(tileX, tileY, flag);
loaded_chunks.at(coords)->SetTileUpdateFlag(tileX, tileY, flag);
}
bool World::GetTileUpdateFlag(int x, int y) {
bool World::GetTileUpdateFlag(int x, int y) const {
float chunkX = Math::Floor((float)x / Chunk::ChunkSize);
float chunkY = Math::Floor((float)y / Chunk::ChunkSize);
@@ -148,7 +152,7 @@ namespace CaveGame::Core
if (HasChunkAtCell(coords))
return loaded_chunks.at(coords).GetTileUpdateFlag(tileX, tileY);
return loaded_chunks.at(coords)->GetTileUpdateFlag(tileX, tileY);
return false;
}
@@ -193,8 +197,8 @@ namespace CaveGame::Core
return std::format("{:%F %T}", std::chrono::system_clock::now());
}
World::World(const std::string &worldName, int seed, bool overwrite) : world_name(worldName), generator(seed) {
World::World(const std::string &worldName, int seed, bool overwrite) : world_name(worldName),
generator(seed) {
namespace fs = std::filesystem;
namespace json = JJX::json;
@@ -202,10 +206,9 @@ namespace CaveGame::Core
if (!fs::exists(worlds))
fs::create_directory(worlds);
fs::path current_world_path = worlds/worldName;
fs::path current_world_path = worlds / worldName;
if (overwrite)
{
if (overwrite) {
if (fs::exists(current_world_path))
fs::remove_all(current_world_path);
}
@@ -215,10 +218,10 @@ namespace CaveGame::Core
fs::create_directory(current_world_path);
/// Create directory for chunks if not already there.
if (!fs::exists(current_world_path/"chunks"))
fs::create_directory(current_world_path/"chunks");
if (!fs::exists(current_world_path / "chunks"))
fs::create_directory(current_world_path / "chunks");
fs::path metadata_path = current_world_path/"meta.json";
fs::path metadata_path = current_world_path / "meta.json";
if (!fs::exists(metadata_path)) {
/// Fill metadata file with base if it is not present.
@@ -231,9 +234,8 @@ namespace CaveGame::Core
});
write_text_file(metadata_path, json::deparse(world_meta_template));
} else
{
auto[val, errcode] = json::parse(read_text_file(metadata_path));
} else {
auto [val, errcode] = json::parse(read_text_file(metadata_path));
}
}
@@ -247,15 +249,15 @@ namespace CaveGame::Core
if (HasChunkOnFile(cell)) {
//std::thread([&]() {
ServedChunks.emplace(Core::Chunk(cell, GetChunkFullPath(cell)));
ServedChunks.push(new Core::Chunk(cell, GetChunkFullPath(cell)));
//}).detach();
} else {
//std::thread( [&]() {
Core::Chunk chunk(cell);
Core::Chunk* chunk = new Chunk(cell);
generator.FirstPass(chunk);
//SaveChunkToFile(cell, chunk);
ServedChunks.emplace(chunk);
ServedChunks.push(chunk);
//}).detach();
}
@@ -314,11 +316,11 @@ namespace CaveGame::Core
entities.push_back(std::move(e));
}
void World::SaveChunkToFile(const Vector2& cell, const Chunk& chunk)
void World::SaveChunkToFile(const Vector2& cell, Chunk* chunk)
{
std::ofstream fout;
fout.open(GetChunkFullPath(cell), std::ios::out | std::ios::binary);
fout.write(reinterpret_cast<const char*>(chunk.ptr()), chunk.BufferSizeBytes());
fout.write(reinterpret_cast<const char*>(chunk->ptr()), chunk->BufferSizeBytes());
fout.close();
}
@@ -340,15 +342,20 @@ namespace CaveGame::Core
Tile* tile = nullptr;
for (auto [coords, chunk] : loaded_chunks) {
chunk->SwapTileUpdateBuffers();
for (int x = 0; x < Core::Chunk::ChunkSize; x++) {
for (int y = 0; y < Core::Chunk::ChunkSize; y++) {
int wx = coords.x*Chunk::ChunkSize + x;
int wy = coords.y*Chunk::ChunkSize + y;
if (!chunk.GetTileUpdateFlag(x, y))
if (!chunk->GetTileUpdateBufferFlag(x, y))
continue;
TileID at = chunk.GetTile(x, y);
chunk->SetTileUpdateBufferFlag(x, y, false);
TileID at = chunk->GetTile(x, y);
if (at == TileID::AIR)
continue;
@@ -358,11 +365,13 @@ namespace CaveGame::Core
if (tile != nullptr) {
if (tile->DoesForcedTicc()) {
tile->ForcedTicc(this, 0, wx, wy);
chunk.SetTileUpdateFlag(x, y, false);
//chunk->SetTileUpdateFlag(x, y, true);
}
}
}
}
//chunk->SwapTileUpdateBuffers();
}
}
@@ -386,7 +395,7 @@ namespace CaveGame::Core
int wx = coords.x*Chunk::ChunkSize + x;
int wy = coords.y*Chunk::ChunkSize + y;
TileID at = chunk.GetTile(x, y);
TileID at = chunk->GetTile(x, y);
if (at == TileID::AIR)
continue;