Save to disk
This commit is contained in:
parent
978334c659
commit
8267793035
6
TODO.md
6
TODO.md
|
@ -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
|
||||
- [x] Document
|
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <vector>
|
||||
|
||||
/// Interger sphere fill
|
||||
struct SphereIterator {
|
||||
SphereIterator(const glm::ivec3 ¢er, 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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#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;
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#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 *>(¤t.Density), sizeof(current.Density));
|
||||
str.write(reinterpret_cast<char *>(¤t.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);
|
||||
|
|
|
@ -1,26 +1,54 @@
|
|||
#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;
|
||||
|
|
Loading…
Reference in New Issue