Commit 82677930 authored by Shu's avatar Shu 🍠
Browse files

Save to disk

parent 978334c6
......@@ -5,7 +5,9 @@
- [x] Density
- [x] Robin hood map
- [ ] Octree world
- [ ] Serialize
- [x] Serialize
- [ ] Variable length encoding
- [ ] Group files
- [ ] In memory RLE
- [x] Edition
- [ ] Entity
......@@ -57,4 +59,4 @@
- [x] Frustum Culling
- [ ] Occlusion Culling
- [ ] Document
\ No newline at end of file
- [x] Document
\ No newline at end of file
#pragma once
#include <glm/glm.hpp>
#include <vector>
/// Interger sphere fill
struct SphereIterator {
SphereIterator(const glm::ivec3 &center, int radius): center(center), radius(radius) { }
glm::ivec3 center;
int radius;
void vector(std::vector<glm::ivec3>& out) {
int top = center.y - radius, bottom = center.y + radius;
for (int y = top; y <= bottom; y++) {
int dy = y - center.y, dxz = floor(sqrt(radius * radius - dy * dy));
int minx = center.x - dxz, maxx = center.x + dxz;
int minz = center.z - dxz, maxz = center.z + dxz;
out.reserve(out.size() + dxz * dxz);
for (int z = minz; z <= maxz; z++) {
for (int x = minx; x <= maxx; x++) {
out.push_back(glm::ivec3(x, y, z));
}}
}
}
};
......@@ -80,7 +80,7 @@ namespace data {
}
std::vector<std::pair<K, W>> heap;
robin_hood::unordered_set<K> set;
robin_hood::unordered_flat_set<K> set;
std::mutex mutex;
std::condition_variable cv;
......
......@@ -3,6 +3,7 @@
#include <queue>
#include <mutex>
#include <condition_variable>
#include <robin_hood.h>
namespace data {
/// Thread safe queue
......
#pragma once
#include <queue>
#include <unordered_set>
#include <robin_hood.h>
#include <mutex>
#include <condition_variable>
......@@ -11,7 +11,7 @@ namespace data {
class safe_unique_queue {
private:
std::queue<T> queue;
std::unordered_set<T> set;
robin_hood::unordered_flat_set<T> set;
std::mutex mutex;
std::condition_variable cv;
......
#pragma once
#include <queue>
#include <unordered_set>
#include <robin_hood.h>
#include <cassert>
namespace data {
......@@ -10,7 +10,7 @@ namespace data {
template <class T>
struct unique_queue {
std::queue<T> queue;
std::unordered_set<T> set;
robin_hood::unordered_flat_set<T> set;
bool push(T in) {
if(set.insert(in).second) {
......
......@@ -106,6 +106,7 @@ UI::Actions UI::draw(options &options, state &state, const reports &reports, GLu
ImGui::PlotHistogram("Loading", reports.world.chunk_load.buffer.get(), reports.world.chunk_load.size, 0, std::to_string(reports.world.chunk_load.current()).c_str(), 0);
ImGui::PlotHistogram("Saving", reports.world.chunk_unload.buffer.get(), reports.world.chunk_unload.size, 0, std::to_string(reports.world.chunk_unload.current()).c_str(), 0);
ImGui::Separator();
ImGui::Text("Path: %s", options.world.folderPath.c_str());
if (ImGui::SliderInt("Load distance", &options.world.loadDistance, 1, options.world.keepDistance) |
ImGui::SliderInt("Keep distance", &options.world.keepDistance, options.world.loadDistance + 1, 21)) {
actions = actions | Actions::World;
......
......@@ -49,6 +49,7 @@ struct options {
world.loadDistance = config["world"]["load_distance"].value_or(world.loadDistance);
world.keepDistance = config["world"]["keep_distance"].value_or(world.keepDistance);
world.folderPath = config["world"]["path"].value_or(world.folderPath);
voxel_size = config["world"]["unit_size"].value_or(1.f);
culling = config["mesh"]["culling"].value_or(true);
......@@ -98,7 +99,8 @@ struct options {
config.insert_or_assign("world", toml::table({
{"load_distance", world.loadDistance},
{"keep_distance", world.keepDistance},
{"unit_size", voxel_size}
{"unit_size", voxel_size},
{"path", world.folderPath}
}));
config.insert_or_assign("mesh", toml::table({
{"culling", culling},
......
......@@ -19,8 +19,47 @@ Chunk::Chunk(const chunk_pos& pos, Generator& rnd) {
FastNoiseSIMD::FreeNoiseSet(densitySet);
FastNoiseSIMD::FreeNoiseSet(materialSet);
}
Chunk::Chunk(std::ifstream& str) {
ushort i = 0;
while(!str.eof()) {
ushort count;
Voxel voxel;
str.read(reinterpret_cast<char *>(&count), sizeof(count));
str.read(reinterpret_cast<char *>(&voxel.Density), sizeof(Voxel::Density));
str.read(reinterpret_cast<char *>(&voxel.Material), sizeof(Voxel::Material));
str.peek();
for (; count > 0; count--) {
voxels[i] = voxel;
i++;
}
}
assert(i == CHUNK_SIZE-1);
}
Chunk::~Chunk() { }
void Chunk::write(std::ofstream& str) const {
//TODO: variable length encoding (short ones)
auto it = voxels.begin();
ushort counter = 1;
Voxel current = *it;
while(true) {
it++;
const auto end = (it == voxels.end());
if(end || current.Density != it->Density || current.Material != it->Material) {
str.write(reinterpret_cast<char *>(&counter), sizeof(counter));
str.write(reinterpret_cast<char *>(&current.Density), sizeof(current.Density));
str.write(reinterpret_cast<char *>(&current.Material), sizeof(current.Material));
if(end)
break;
current = *it;
counter = 1;
} else {
counter++;
}
}
}
std::optional<Faces> Chunk::update() {
if(upToDate) {
return {};
......
......@@ -3,6 +3,7 @@
#include "Generator.hpp"
#include "Voxel.hpp"
#include "../data/geometry/Faces.hpp"
#include <fstream>
/// Chunk length
#define CHUNK_LENGTH 32
......@@ -15,6 +16,7 @@ namespace world {
struct Chunk {
public:
Chunk(const chunk_pos& pos, Generator& rnd);
Chunk(std::ifstream& str);
~Chunk();
/// Update voxels
......@@ -48,6 +50,11 @@ namespace world {
set(idx, val);
return Item{res.Density, res.Material};
}
// Is player modified
inline bool isModified() const { return modified; }
// Write to file.
// Using RLE
void write(std::ofstream& str) const;
static inline chunk_voxel_pos getPosition(ushort idx) {
return chunk_voxel_pos(idx / CHUNK_LENGTH2, (idx / CHUNK_LENGTH) % CHUNK_LENGTH, idx % CHUNK_LENGTH);
......
#include "Universe.hpp"
#include "../contouring/Dummy.hpp"
#include "../data/geometry/Sphere.hpp"
#include <Remotery.h>
#include <filesystem>
#include <fstream>
using namespace world;
Universe::Universe(const Universe::options &options): loadPool(2), contouring(std::make_shared<contouring::Dummy>()) {
Universe::Universe(const Universe::options &options): loadPool(4, options.folderPath), savePool(2, options.folderPath),
contouring(std::make_shared<contouring::Dummy>()) {
setOptions(options);
}
Universe::~Universe() {
contouring = NULL;
// Save all
for(auto& pair: chunks) {
savePool.push(pair);
}
if(savePool.size() > 0) {
std::cout << "Saving..." << std::endl;
}
while(savePool.size() > 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
Universe::LoadPool::LoadPool(size_t count) {
Universe::LoadPool::LoadPool(size_t count, const std::string& folderPath): folderPath(folderPath) {
for (size_t i = 0; i < count; i++) {
workers.push_back(std::thread([&] {
while (running) {
chunk_pos ctx;
loadQueue.wait();
if (loadQueue.pop(ctx)) {
rmt_ScopedCPUSample(ProcessGenerate, 0);
loadedQueue.push({ctx, std::make_shared<Chunk>(ctx, generator)});
//MAYBE: loadQueue.take to avoid duplicated work on fast move
rmt_ScopedCPUSample(ProcessLoad, 0);
const auto path = folderPath + '/' + std::to_string(ctx.x) + '.' +
std::to_string(ctx.y) + '.' + std::to_string(ctx.z) + ".map";
std::ifstream str(path);
if(str.good()) {
rmt_ScopedCPUSample(ProcessRead, 0);
loadedQueue.push({ctx, std::make_shared<Chunk>(str)});
if(str.bad()) {
std::cout << "Fail reading '" << path << "'" << std::endl;
}
} else {
rmt_ScopedCPUSample(ProcessGenerate, 0);
loadedQueue.push({ctx, std::make_shared<Chunk>(ctx, generator)});
}
}
}
}));
......@@ -39,48 +67,81 @@ inline void Universe::LoadPool::push(const chunk_pos &pos, int weight) { loadQue
inline bool Universe::LoadPool::pop(robin_hood::pair<chunk_pos, std::shared_ptr<Chunk>> &out) { return loadedQueue.pop(out); }
inline size_t Universe::LoadPool::size() { return loadQueue.size(); }
Universe::SavePool::SavePool(size_t count, const std::string& folderPath): folderPath(folderPath) {
std::filesystem::create_directories(folderPath);
for (size_t i = 0; i < count; i++) {
workers.push_back(std::thread([&] {
while (running) {
robin_hood::pair<chunk_pos, std::shared_ptr<Chunk>> ctx;
queue.wait();
if (queue.pop(ctx) && ctx.second->isModified()) {
//MAYBE: queue.take to avoid concurent write or duplicated work on fast move
rmt_ScopedCPUSample(ProcessSave, 0);
const auto path = folderPath + '/' + std::to_string(ctx.first.x) + '.' +
std::to_string(ctx.first.y) + '.' + std::to_string(ctx.first.z) + ".map";
std::ofstream ofs(path);
if(!ofs.good()) {
std::cout << "Fail opening '" << path << "' to save" << std::endl;
continue;
}
ctx.second->write(ofs);
ofs.flush();
ofs.close();
if(!ofs.good()) {
std::cout << "Fail closing '" << path << "' to save" << std::endl;
}
}
}
}));
}
}
Universe::SavePool::~SavePool() {
running = false;
queue.notify();
for (auto &worker : workers) {
if (worker.joinable())
worker.join();
}
}
inline void Universe::SavePool::push(const robin_hood::pair<chunk_pos, std::shared_ptr<Chunk>> &pos) { queue.push(pos); }
inline size_t Universe::SavePool::size() { return queue.size(); }
void Universe::update(const camera_pos& pos, Universe::report& rep) {
const chunk_pos newPos = glm::divide(pos, chunk_voxel_pos(CHUNK_LENGTH));
const auto chunkChange = newPos != last_pos;
const auto chunkChange = last_pos != newPos;
last_pos = newPos;
rmt_ScopedCPUSample(Universe, 0);
// Update alive chunks
{
rmt_ScopedCPUSample(Update, 0);
for (auto &[chunkPos, chunk]: chunks) {
if (glm::length2(last_pos - chunkPos) > keepDistance * keepDistance
&& unloadQueue.push(chunkPos)) {
//TODO: unloadCount++;
continue;
}
if (const auto neighbors = chunk->update()) {
contouring->onUpdate(chunkPos, chunks, neighbors.value()); //TODO: get update update_type(simple(pos), complex)
} else if (chunkChange) { //NOTE: must be solved before octrees
contouring->onNotify(chunkPos, chunks);
auto it = chunks.begin();
while (it != chunks.end()) {
if (glm::length2(last_pos - it->first) > keepDistance * keepDistance) {
savePool.push(*it);
it = chunks.erase(it);
} else {
if (const auto neighbors = it->second->update()) {
contouring->onUpdate(it->first, chunks, neighbors.value()); //TODO: get update update_type(simple(pos), complex)
} else if (chunkChange) {
contouring->onNotify(it->first, chunks);
}
++it;
}
}
}
rep.chunk_unload.push(savePool.size());
{
rmt_ScopedCPUSample(Contouring, 0);
contouring->update(pos);
}
rep.chunk_unload.push(unloadQueue.size());
// Unload dead chunks
{
rmt_ScopedCPUSample(Unload, 0);
for (size_t i = 0; i < 256 && !unloadQueue.empty(); i++) {
chunks.erase(unloadQueue.pop());
//TODO: save to file
}
//MAYBE: if(chunkChange) contouring->notify(chunks);
}
// Find missing chunks
if(chunkChange) {
rmt_ScopedCPUSample(ToLoad, 0);
//TODO: circle point algo
//NOTE: need dist so no easy sphere fill
for (int x = -loadDistance; x <= loadDistance; x++) {
for (int y = -loadDistance; y <= loadDistance; y++) {
for (int z = -loadDistance; z <= loadDistance; z++) {
......
......@@ -2,7 +2,7 @@
#include <memory>
#include <thread>
#include "../data/unique_queue.hpp"
#include <string>
#include "../data/safe_queue.hpp"
#include "../data/safe_priority_queue.hpp"
#include "../data/circular_buffer.hpp"
......@@ -27,6 +27,8 @@ namespace world {
int loadDistance = 5;
/// Radius in chunks to keep in memory
int keepDistance = 6;
/// Storage path
std::string folderPath = "world";
};
/// Reports to UI
struct report {
......@@ -77,7 +79,7 @@ namespace world {
/// Generating worker pool
class LoadPool {
public:
LoadPool(size_t size);
LoadPool(size_t size, const std::string& folderPath);
~LoadPool();
Generator generator;
......@@ -86,6 +88,7 @@ namespace world {
inline size_t size();
private:
std::string folderPath;
std::vector<std::thread> workers;
bool running = true;
......@@ -93,10 +96,28 @@ namespace world {
safe_queue<robin_hood::pair<chunk_pos, std::shared_ptr<Chunk>>> loadedQueue;
};
LoadPool loadPool;
unique_queue<chunk_pos> unloadQueue;
/// Write to file worker pool
class SavePool {
public:
SavePool(size_t size, const std::string& folderPath);
~SavePool();
inline void push(const robin_hood::pair<chunk_pos, std::shared_ptr<Chunk>> &chunk);
inline size_t size();
private:
std::string folderPath;
std::vector<std::thread> workers;
bool running = true;
safe_queue<robin_hood::pair<chunk_pos, std::shared_ptr<Chunk>>> queue; //NOTE: consider const Chunk
};
SavePool savePool;
int loadDistance;
int keepDistance;
std::string folderPath;
/// Contouring worker
std::shared_ptr<contouring::Abstract> contouring;
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment