2020-07-25 16:45:03 +00:00
|
|
|
#include "Universe.hpp"
|
2020-07-10 17:49:16 +00:00
|
|
|
|
2020-07-31 23:17:09 +00:00
|
|
|
#include <Remotery.h> // NOLINT
|
2020-07-26 20:53:14 +00:00
|
|
|
#include <filesystem>
|
2020-07-31 17:09:44 +00:00
|
|
|
|
|
|
|
#include "../contouring/Dummy.hpp"
|
|
|
|
#include "Chunk.hpp"
|
2020-07-10 17:49:16 +00:00
|
|
|
|
2020-07-25 16:45:03 +00:00
|
|
|
using namespace world;
|
|
|
|
|
2020-08-02 20:15:53 +00:00
|
|
|
const auto MAIN_AREA = 1;
|
|
|
|
|
|
|
|
Universe::Universe(const Universe::options &options): dicts("content/zstd.dict"), contouring(std::make_shared<contouring::Dummy>()) {
|
2020-07-10 17:49:16 +00:00
|
|
|
setOptions(options);
|
2020-07-30 16:35:13 +00:00
|
|
|
folderPath = options.folderPath;
|
|
|
|
struct vec_istream: std::streambuf {
|
|
|
|
vec_istream(std::vector<char> &vec) {
|
|
|
|
this->setg(&vec[0], &vec[0], &vec[0] + vec.size());
|
|
|
|
}
|
|
|
|
};
|
|
|
|
running = true;
|
2020-08-02 20:15:53 +00:00
|
|
|
|
2020-07-30 16:35:13 +00:00
|
|
|
std::filesystem::create_directories(folderPath);
|
2020-08-02 20:15:53 +00:00
|
|
|
areas.emplace(MAIN_AREA, std::make_shared<Area>(1 << 2));
|
|
|
|
areas.emplace(2, std::make_shared<Area>(1 << 2, 43));
|
2020-07-10 17:49:16 +00:00
|
|
|
|
2020-07-30 16:35:13 +00:00
|
|
|
// Load workers
|
|
|
|
for (size_t i = 0; i < 4; i++) {
|
2020-07-31 17:09:44 +00:00
|
|
|
loadWorkers.emplace_back([&] {
|
2020-07-31 22:11:08 +00:00
|
|
|
const auto ctx = dicts.make_reader();
|
2020-07-22 20:55:13 +00:00
|
|
|
while (running) {
|
2020-08-02 20:15:53 +00:00
|
|
|
std::pair<area_<chunk_pos>, std::shared_ptr<Area>> task;
|
2020-07-22 20:55:13 +00:00
|
|
|
loadQueue.wait();
|
2020-08-02 20:15:53 +00:00
|
|
|
if (loadQueue.pop(task)) {
|
2020-07-26 20:53:14 +00:00
|
|
|
//MAYBE: loadQueue.take to avoid duplicated work on fast move
|
|
|
|
rmt_ScopedCPUSample(ProcessLoad, 0);
|
2020-08-02 20:15:53 +00:00
|
|
|
const auto &pos = task.first;
|
|
|
|
const auto rcPos = glm::split(pos.second);
|
|
|
|
const auto reg = task.second->getRegion(folderPath, std::make_pair(pos.first, rcPos.first));
|
2020-07-30 16:35:13 +00:00
|
|
|
Region::data data;
|
2020-08-02 20:15:53 +00:00
|
|
|
if(reg->read(rcPos.second, ctx, data)) {
|
2020-07-26 20:53:14 +00:00
|
|
|
rmt_ScopedCPUSample(ProcessRead, 0);
|
2020-07-30 16:35:13 +00:00
|
|
|
vec_istream idata(data);
|
|
|
|
std::istream iss(&idata);
|
2020-07-31 20:26:07 +00:00
|
|
|
loadedQueue.push({pos, std::make_shared<Chunk>(iss)});
|
2020-07-26 20:53:14 +00:00
|
|
|
} else {
|
|
|
|
rmt_ScopedCPUSample(ProcessGenerate, 0);
|
2020-08-02 20:15:53 +00:00
|
|
|
loadedQueue.push({pos, std::make_shared<Chunk>(pos.second, task.second->getGenerator())});
|
2020-07-26 20:53:14 +00:00
|
|
|
}
|
2020-07-22 20:55:13 +00:00
|
|
|
}
|
|
|
|
}
|
2020-07-31 17:09:44 +00:00
|
|
|
});
|
2020-07-22 20:55:13 +00:00
|
|
|
}
|
|
|
|
|
2020-07-30 16:35:13 +00:00
|
|
|
// Save workers
|
|
|
|
for (size_t i = 0; i < 2; i++) {
|
2020-07-31 17:09:44 +00:00
|
|
|
saveWorkers.emplace_back([&] {
|
2020-07-31 22:11:08 +00:00
|
|
|
const auto ctx = dicts.make_writer();
|
2020-07-26 20:53:14 +00:00
|
|
|
while (running) {
|
2020-08-02 20:15:53 +00:00
|
|
|
save_task_t task;
|
2020-07-30 16:35:13 +00:00
|
|
|
saveQueue.wait();
|
2020-08-02 20:15:53 +00:00
|
|
|
if (saveQueue.pop(task) && task.second.second->isModified()) {
|
2020-07-26 20:53:14 +00:00
|
|
|
//MAYBE: queue.take to avoid concurent write or duplicated work on fast move
|
|
|
|
rmt_ScopedCPUSample(ProcessSave, 0);
|
2020-07-30 16:35:13 +00:00
|
|
|
std::ostringstream out;
|
2020-08-02 20:15:53 +00:00
|
|
|
task.second.second->write(out);
|
|
|
|
const auto rcPos = glm::split(task.second.first);
|
|
|
|
const auto reg = task.first.second->getRegion(folderPath, std::make_pair(task.first.first, rcPos.first));
|
|
|
|
reg->write(rcPos.second, ctx, out.str());
|
2020-07-26 20:53:14 +00:00
|
|
|
}
|
|
|
|
}
|
2020-07-31 17:09:44 +00:00
|
|
|
});
|
2020-07-26 20:53:14 +00:00
|
|
|
}
|
|
|
|
}
|
2020-07-30 16:35:13 +00:00
|
|
|
Universe::~Universe() {
|
|
|
|
contouring = NULL;
|
|
|
|
|
|
|
|
// Save all
|
2020-08-02 20:15:53 +00:00
|
|
|
for(auto& area: areas) {
|
|
|
|
for(auto& chunk: area.second->getChunks())
|
|
|
|
saveQueue.emplace(area, chunk);
|
2020-07-30 16:35:13 +00:00
|
|
|
}
|
2020-07-31 20:26:07 +00:00
|
|
|
if (auto size = saveQueue.size(); size > 0) {
|
|
|
|
std::cout << std::endl;
|
|
|
|
do {
|
|
|
|
std::cout << "\rSaving... " << size << " " << std::flush;
|
|
|
|
std::this_thread::sleep_for(std::chrono::microseconds(500));
|
|
|
|
size = saveQueue.size();
|
|
|
|
} while (size > 0);
|
|
|
|
std::cout << std::endl;
|
2020-07-30 16:35:13 +00:00
|
|
|
}
|
|
|
|
|
2020-07-26 20:53:14 +00:00
|
|
|
running = false;
|
2020-07-30 16:35:13 +00:00
|
|
|
loadQueue.notify();
|
|
|
|
saveQueue.notify();
|
2020-07-26 20:53:14 +00:00
|
|
|
|
2020-07-30 16:35:13 +00:00
|
|
|
for (auto &worker: loadWorkers) {
|
|
|
|
if (worker.joinable())
|
|
|
|
worker.join();
|
|
|
|
}
|
|
|
|
for (auto &worker: saveWorkers) {
|
2020-07-26 20:53:14 +00:00
|
|
|
if (worker.joinable())
|
|
|
|
worker.join();
|
|
|
|
}
|
|
|
|
}
|
2020-07-30 16:35:13 +00:00
|
|
|
|
2020-08-01 21:31:01 +00:00
|
|
|
void Universe::update(const voxel_pos& pos, Universe::report& rep) {
|
2020-08-02 20:15:53 +00:00
|
|
|
const chunk_pos newPos = glm::divide(pos);
|
2020-07-26 20:53:14 +00:00
|
|
|
const auto chunkChange = last_pos != newPos;
|
2020-07-10 17:49:16 +00:00
|
|
|
last_pos = newPos;
|
2020-07-25 16:45:03 +00:00
|
|
|
rmt_ScopedCPUSample(Universe, 0);
|
2020-07-10 17:49:16 +00:00
|
|
|
|
2020-08-02 20:15:53 +00:00
|
|
|
// Update alive areas
|
2020-07-10 17:49:16 +00:00
|
|
|
{
|
2020-07-18 12:54:07 +00:00
|
|
|
rmt_ScopedCPUSample(Update, 0);
|
2020-08-02 20:15:53 +00:00
|
|
|
size_t chunk_count = 0;
|
|
|
|
auto it = areas.begin();
|
|
|
|
while (it != areas.end()) {
|
|
|
|
auto diff = last_pos /*TODO: - it->second.position*/;
|
|
|
|
auto &chunks = it->second->setChunks();
|
|
|
|
if (glm::length2(diff) /*TODO: - it->second.radius * radius * CHUNK_SIZE * CHUNK_SIZE */ > keepDistance * keepDistance) {
|
|
|
|
auto it_c = chunks.begin();
|
|
|
|
while(it_c != chunks.end()) {
|
|
|
|
saveQueue.emplace(*it, *it_c);
|
|
|
|
it_c = chunks.erase(it_c);
|
|
|
|
}
|
|
|
|
it = areas.erase(it);
|
2020-07-26 20:53:14 +00:00
|
|
|
} else {
|
2020-08-02 20:15:53 +00:00
|
|
|
auto it_c = chunks.begin();
|
|
|
|
while(it_c != chunks.end()) { // Update alive chunks
|
|
|
|
if (glm::length2(diff - it_c->first) > keepDistance * keepDistance) {
|
|
|
|
saveQueue.emplace(*it, *it_c);
|
|
|
|
it_c = chunks.erase(it_c);
|
|
|
|
}else {
|
|
|
|
const auto acPos = std::make_pair(it->first, it_c->first);
|
|
|
|
if (const auto neighbors = it_c->second->update()) {
|
|
|
|
contouring->onUpdate(acPos, chunks, neighbors.value()); //TODO: it->second.position
|
|
|
|
} else if (chunkChange) {
|
|
|
|
contouring->onNotify(acPos, chunks); //TODO: it->second.position
|
|
|
|
}
|
|
|
|
++it_c;
|
|
|
|
chunk_count++;
|
|
|
|
}
|
2020-07-26 20:53:14 +00:00
|
|
|
}
|
|
|
|
++it;
|
2020-07-10 17:49:16 +00:00
|
|
|
}
|
|
|
|
}
|
2020-08-02 20:15:53 +00:00
|
|
|
rep.chunk_count.push(chunk_count);
|
2020-07-10 17:49:16 +00:00
|
|
|
}
|
2020-07-30 16:35:13 +00:00
|
|
|
rep.chunk_unload.push(saveQueue.size());
|
2020-07-22 20:55:13 +00:00
|
|
|
{
|
|
|
|
rmt_ScopedCPUSample(Contouring, 0);
|
2020-08-02 20:15:53 +00:00
|
|
|
contouring->update(pos, areas);
|
2020-07-26 20:53:14 +00:00
|
|
|
//MAYBE: if(chunkChange) contouring->notify(chunks);
|
2020-07-10 17:49:16 +00:00
|
|
|
}
|
|
|
|
|
2020-07-22 20:55:13 +00:00
|
|
|
// Find missing chunks
|
2020-07-10 17:49:16 +00:00
|
|
|
if(chunkChange) {
|
2020-07-18 12:54:07 +00:00
|
|
|
rmt_ScopedCPUSample(ToLoad, 0);
|
2020-08-02 20:15:53 +00:00
|
|
|
//TODO: for(auto& far: far_areas)
|
|
|
|
|
|
|
|
for(auto& area: areas) {
|
|
|
|
//NOTE: need dist so no easy sphere fill
|
|
|
|
auto& chunks = area.second->getChunks();
|
|
|
|
for (int x = -loadDistance; x <= loadDistance; x++) {
|
|
|
|
for (int y = -loadDistance; y <= loadDistance; y++) {
|
|
|
|
for (int z = -loadDistance; z <= loadDistance; z++) {
|
|
|
|
const auto dist2 = x * x + y * y + z * z;
|
|
|
|
if (dist2 <= loadDistance * loadDistance) {
|
|
|
|
const auto p = last_pos + chunk_pos(x, y, z) /*TODO: + area.second->getPosition() */;
|
|
|
|
if (chunks.inRange(p) && chunks.find(p) == chunks.end()) {
|
|
|
|
loadQueue.push(std::make_pair(area.first, p), area.second, -dist2);
|
|
|
|
}
|
2020-07-10 17:49:16 +00:00
|
|
|
}
|
2020-08-02 20:15:53 +00:00
|
|
|
}}}
|
|
|
|
}
|
2020-07-10 17:49:16 +00:00
|
|
|
}
|
|
|
|
|
2020-07-30 16:35:13 +00:00
|
|
|
rep.chunk_load.push(loadQueue.size());
|
2020-08-02 20:15:53 +00:00
|
|
|
// Store loaded chunks
|
2020-07-18 12:54:07 +00:00
|
|
|
{
|
|
|
|
rmt_ScopedCPUSample(Load, 0);
|
2020-08-02 20:15:53 +00:00
|
|
|
robin_hood::pair<area_<chunk_pos>, std::shared_ptr<Chunk>> loaded;
|
2020-07-30 16:35:13 +00:00
|
|
|
while (loadedQueue.pop(loaded)) {
|
2020-08-02 20:15:53 +00:00
|
|
|
if (const auto it = areas.find(loaded.first.first); it != areas.end()) {
|
|
|
|
auto &chunks = it->second->setChunks();
|
|
|
|
chunks.emplace(loaded.first.second, loaded.second);
|
|
|
|
contouring->onUpdate(loaded.first, chunks, Faces::All);
|
|
|
|
}
|
2020-07-14 17:03:32 +00:00
|
|
|
}
|
2020-07-10 17:49:16 +00:00
|
|
|
}
|
2020-07-31 20:26:07 +00:00
|
|
|
|
2020-08-01 21:31:01 +00:00
|
|
|
if(!rep.chunk_load.current() && !rep.chunk_unload.current()) {
|
2020-07-31 20:26:07 +00:00
|
|
|
rmt_ScopedCPUSample(Region, 0);
|
2020-08-02 20:15:53 +00:00
|
|
|
for(auto& area: areas) {
|
|
|
|
const auto unique = area.second->getRegions(); // MAYBE: shared then unique
|
|
|
|
rep.region_count.push(unique->size());
|
|
|
|
const auto me = glm::divide(last_pos /*TODO: + position*/);
|
|
|
|
for (auto it = unique->begin(); it != unique->end(); ++it) {
|
|
|
|
if (glm::length2(glm::lvec3(it->first) - me) > keepDistance) { //FIXME: keepDistance^1 may suck with high values
|
|
|
|
unique->erase(it);
|
|
|
|
break; //NOTE: save one max per frame
|
|
|
|
}
|
2020-07-31 20:26:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-07-10 17:49:16 +00:00
|
|
|
}
|
2020-07-25 16:45:03 +00:00
|
|
|
void Universe::setOptions(const Universe::options& options) {
|
2020-07-10 17:49:16 +00:00
|
|
|
loadDistance = options.loadDistance;
|
|
|
|
keepDistance = options.keepDistance;
|
|
|
|
}
|
|
|
|
|
2020-07-31 23:17:09 +00:00
|
|
|
void Universe::setContouring(const std::shared_ptr<contouring::Abstract>& ct) {
|
2020-07-18 15:42:45 +00:00
|
|
|
contouring = ct;
|
|
|
|
last_pos = chunk_pos(INT_MAX); // trigger chunkChange on next update
|
2020-07-10 19:37:49 +00:00
|
|
|
}
|
2020-07-25 14:29:05 +00:00
|
|
|
|
2020-08-02 20:15:53 +00:00
|
|
|
std::optional<std::pair<area_<voxel_pos>, Voxel>> Universe::raycast(const Ray &ray) const {
|
2020-07-25 14:29:05 +00:00
|
|
|
std::vector<voxel_pos> points;
|
|
|
|
ray.grid(points);
|
2020-08-02 20:15:53 +00:00
|
|
|
std::optional<std::pair<area_<voxel_pos>, Voxel>> target = std::nullopt;
|
|
|
|
size_t dist = points.size();
|
|
|
|
for(auto& area: areas) {
|
|
|
|
//TODO: if ray.insert(area.getIBox())
|
|
|
|
const auto &chunks = area.second->getChunks();
|
|
|
|
std::shared_ptr<Chunk> chunk = NULL;
|
|
|
|
chunk_pos chunk_vec(INT_MAX);
|
|
|
|
for (size_t i = 0; i < dist; i++) {
|
|
|
|
const auto pos = points[i] /*TODO: + area.position*/;
|
|
|
|
const chunk_pos cPos = glm::divide(pos);
|
|
|
|
if(cPos != chunk_vec) {
|
|
|
|
if (const auto it = chunks.find(cPos); it != chunks.end()) {
|
|
|
|
chunk = it->second;
|
|
|
|
chunk_vec = cPos;
|
|
|
|
} else {
|
|
|
|
chunk = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(chunk != NULL) {
|
|
|
|
const auto voxel = chunk->getAt(glm::modulo(pos));
|
|
|
|
if(voxel.density() > 0) {
|
|
|
|
target = {{{area.first, pos}, voxel}};
|
|
|
|
dist = i;
|
|
|
|
i = points.size();
|
|
|
|
}
|
2020-07-25 14:29:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-08-02 20:15:53 +00:00
|
|
|
return target;
|
2020-07-25 14:29:05 +00:00
|
|
|
}
|
|
|
|
|
2020-08-02 20:15:53 +00:00
|
|
|
std::optional<Item> Universe::set(const area_<voxel_pos>& pos, const Voxel& val) {
|
|
|
|
if(const auto it = areas.find(pos.first); it != areas.end()) {
|
|
|
|
auto &chunks = it->second->setChunks();
|
|
|
|
const auto split = glm::splitIdx(pos.second);
|
|
|
|
if(chunks.inRange(split.first))
|
|
|
|
if(const auto chunk = chunks.findInRange(split.first))
|
|
|
|
return {chunk.value()->replace(split.second, val)};
|
2020-07-25 14:29:05 +00:00
|
|
|
}
|
2020-08-02 20:15:53 +00:00
|
|
|
return {};
|
2020-07-25 14:29:05 +00:00
|
|
|
}
|
2020-08-02 20:15:53 +00:00
|
|
|
ItemList Universe::setCube(const area_<voxel_pos>& pos, const Voxel& val, int radius) {
|
2020-07-25 14:29:05 +00:00
|
|
|
ItemList list;
|
2020-08-02 20:15:53 +00:00
|
|
|
if(const auto it = areas.find(pos.first); it != areas.end()) {
|
|
|
|
auto& chunks = it->second->setChunks();
|
|
|
|
for (int z = -radius; z <= radius; z++) {
|
|
|
|
for (int y = -radius; y <= radius; y++) {
|
|
|
|
for (int x = -radius; x <= radius; x++) {
|
|
|
|
//TODO: list.pop(val)
|
|
|
|
const auto split = glm::splitIdx(pos.second + voxel_pos(x, y, z));
|
|
|
|
if(chunks.inRange(split.first))
|
|
|
|
if(const auto chunk = it->second->setChunks().findInRange(split.first))
|
|
|
|
list.add(chunk.value()->replace(split.second, val));
|
|
|
|
}}}
|
|
|
|
}
|
2020-07-25 14:29:05 +00:00
|
|
|
return list;
|
|
|
|
}
|