560 lines
25 KiB
C++
560 lines
25 KiB
C++
#include "./Universe.hpp"
|
|
|
|
#include <Tracy.hpp>
|
|
#include <common/TracySystem.hpp>
|
|
#include <filesystem>
|
|
#include <random>
|
|
#include "./Serializer.hpp"
|
|
#include "./SharedChunk.hpp"
|
|
#include "modules/core/PlanetGenerator.hpp"
|
|
#include "core/world/iterators.hpp"
|
|
|
|
#undef LOG_PREFIX
|
|
#define LOG_PREFIX "Server: "
|
|
|
|
using namespace world::server;
|
|
|
|
Universe::Universe(const Universe::options&o, Serializer* serializer, world::server_handle *const h):
|
|
handle(h), opts(o),
|
|
chunkFactory(h ? (ChunkFactory*)new SharedChunkFactory() : new StandaloneChunkFactory())
|
|
{
|
|
running = true;
|
|
|
|
std::filesystem::create_directories(opts.folderPath);
|
|
loadIndex();
|
|
|
|
// Workers
|
|
for (size_t i = 0; i < std::max<size_t>(1, std::thread::hardware_concurrency() / 2 - 1); i++) {
|
|
workers.emplace_back([&, serializer] {
|
|
#if TRACY_ENABLE
|
|
tracy::SetThreadName("Chunks");
|
|
#endif
|
|
const auto read_ctx = serializer->Dicts().make_reader();
|
|
const auto write_ctx = serializer->Dicts().make_writer();
|
|
while (running) {
|
|
if(save_task_t task; saveAreaQueue.pop(task)) {
|
|
//NOTE: must always save before load to avoid chunk regen
|
|
//MAYBE: queue.take to avoid concurent write or duplicated work on fast move
|
|
ZoneScopedN("ProcessSaveArea");
|
|
std::ostringstream out;
|
|
if (!task.second.second->isModified()) {
|
|
out.setstate(std::ios_base::badbit);
|
|
}
|
|
const auto average = task.second.second->write(out);
|
|
const auto rcPos = glm::split(task.second.first);
|
|
const auto reg = task.first.second->getRegion(opts.folderPath, area_region_ref(task.first.first.val.index(), rcPos.first));
|
|
reg->write(rcPos.second, write_ctx, out.str(), std::make_optional(average));
|
|
} else if (std::pair<part_id, std::shared_ptr<Part>> task; loadPartQueue.pop(task)) {
|
|
ZoneScopedN("ProcessLoadPart");
|
|
Region::Read(opts.folderPath, task.first.val.index(), read_ctx, [&] (const region_chunk_pos& pos, const std::vector<char>& data) {
|
|
ZoneScopedN("ProcessRead");
|
|
io::vec_istream idata(data);
|
|
std::istream iss(&idata);
|
|
loadedQueue.push({node_chunk_pos{task.first.val, pos}, chunkFactory->create(iss)});
|
|
});
|
|
} else if(std::pair<part_id, std::shared_ptr<Part>> task; savePartQueue.pop(task)) {
|
|
//NOTE: must always fully load parts before save to avoid lost chunks
|
|
ZoneScopedN("ProcessSavePart");
|
|
const auto SIZE = task.second->chunks.size();
|
|
const auto alive = std::count_if(task.second->chunks.begin(), task.second->chunks.end(),
|
|
[](const std::shared_ptr<world::EdittableChunk> &ptr) -> bool { return ptr != nullptr; });
|
|
size_t i = 0;
|
|
Region::Write(opts.folderPath, task.first.val.index(), write_ctx, alive, [&] {
|
|
while (i < SIZE && !task.second->chunks[i]) {
|
|
i++;
|
|
}
|
|
assert(i < SIZE);
|
|
std::ostringstream out;
|
|
std::static_pointer_cast<Chunk>(task.second->chunks[i])->write(out);
|
|
const auto pos = task.second->getPos(i);
|
|
i++;
|
|
return std::make_pair(pos, out.str());
|
|
});
|
|
} else if (std::pair<area_chunk_pos, std::shared_ptr<Area>> task; loadAreaQueue.pop(task)) {
|
|
//MAYBE: loadQueue.take to avoid duplicated work on fast move
|
|
ZoneScopedN("ProcessLoadArea");
|
|
const auto &pos = task.first;
|
|
const auto rcPos = glm::split(pos.chunk);
|
|
const auto reg = task.second->getRegion(opts.folderPath, area_region_ref(pos.area.val.index(), rcPos.first));
|
|
std::vector<char> data;
|
|
if(reg->read(rcPos.second, read_ctx, data)) {
|
|
ZoneScopedN("ProcessRead");
|
|
io::vec_istream idata(data);
|
|
std::istream iss(&idata);
|
|
loadedQueue.push({node_chunk_pos{pos.area.val, pos.chunk}, chunkFactory->create(iss)});
|
|
} else {
|
|
ZoneScopedN("ProcessGenerate");
|
|
loadedQueue.push({node_chunk_pos{pos.area.val, pos.chunk}, chunkFactory->create(pos.chunk, task.second->getGenerator())});
|
|
}
|
|
} else {
|
|
loadAreaQueue.wait();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
#ifndef STANDALONE_SERVER
|
|
if (handle)
|
|
handle->elements = elements.make_ref<world::Elements>();
|
|
#endif
|
|
}
|
|
Universe::~Universe() {
|
|
{
|
|
const auto elements = getElements();
|
|
for(auto& ref: elements->areas) {
|
|
const auto id = elements->withFlag(ref);
|
|
const auto area = elements->findArea(id.val)->get();
|
|
auto &chunks = area->setChunks();
|
|
for (auto it_c = chunks.begin(); it_c != chunks.end(); it_c = chunks.erase(it_c)) {
|
|
saveAreaQueue.emplace(std::make_pair(id.val, area), std::make_pair(it_c->first, std::static_pointer_cast<Chunk>(it_c->second)));
|
|
}
|
|
}
|
|
for(auto& ref: elements->parts) {
|
|
const auto id = elements->withFlag(ref);
|
|
const auto part = elements->findPart(id.val)->get();
|
|
if (!part->chunks.empty())
|
|
savePartQueue.emplace(id.val, part);
|
|
}
|
|
}
|
|
|
|
if (auto size = saveAreaQueue.size() + savePartQueue.size(); size > 0) {
|
|
const auto SAVE_CHECK_TIME = 1000;
|
|
do {
|
|
loadAreaQueue.notify_all();
|
|
LOG_I("Saving " << size << " chunks");
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(SAVE_CHECK_TIME));
|
|
size = saveAreaQueue.size() + savePartQueue.size();
|
|
} while (size > 0);
|
|
}
|
|
writeIndex();
|
|
|
|
running = false;
|
|
loadAreaQueue.notify_all();
|
|
|
|
for (auto &worker: workers) {
|
|
if (worker.joinable())
|
|
worker.join();
|
|
}
|
|
LOG_D("Universe disappeared");
|
|
}
|
|
|
|
void Universe::update(float deltaTime) {
|
|
ZoneScopedN("Update");
|
|
|
|
const auto lock = setElements();
|
|
auto &elements = *lock;
|
|
|
|
elements.hierarchy.for_each([&](Elements::hierarchy_entry &entry) {
|
|
//vel += acc * deltaTime
|
|
entry.relative.position += entry.vel.position * deltaTime;
|
|
entry.relative.rotation = glm::slerp(identity_pivot, entry.vel.rotation, deltaTime) * entry.relative.rotation;
|
|
|
|
const auto node = elements.nodes.directly_at(entry.self);
|
|
node->absolute = elements.computeAbsolute(entry.parent, entry.relative);
|
|
//TODO: full physics
|
|
});
|
|
}
|
|
|
|
void Universe::upgrade() {
|
|
ZoneScopedN("Upgrade");
|
|
|
|
const auto lock = setElements();
|
|
auto &elements = *lock;
|
|
|
|
{ // Random tick
|
|
const auto size = elements.nodes.size();
|
|
auto rng = std::mt19937(std::random_device()());
|
|
const auto randomTick = opts.randomTick;
|
|
auto dist = std::uniform_int_distribution<generational::id::idx_t>(0, randomTick);
|
|
for (size_t part = 0; part <= size / randomTick; part++) {
|
|
const node_ref ref = part * randomTick + dist(rng);
|
|
if (const auto id = elements.withFlag(ref)) {
|
|
elements.nodes.set_flag(Elements::Set<Elements::Flag::Moved>(id));
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<world_pos> movedLoaders;
|
|
elements.nodes.iter([&](const node_id &id, const Node &node) {
|
|
if (Elements::Has<Elements::Flag::ChunkLoader>(id) && (
|
|
Elements::Has<Elements::Flag::Moved>(id) || Elements::Has<Elements::Flag::Added>(id)))
|
|
movedLoaders.push_back(node.absolute.position);
|
|
});
|
|
|
|
{ // Update areas && parts
|
|
ZoneScopedN("World");
|
|
#if TRACY_ENABLE
|
|
size_t chunk_count = 0;
|
|
size_t region_count = 0;
|
|
bool allLazy = true;
|
|
#endif
|
|
const auto keepDist2 = glm::pow2<int>(opts.keepDistance);
|
|
const auto regionKeepDist2 = glm::pow2(opts.keepDistance + REGION_LENGTH * 2);
|
|
const bool queuesEmpty = loadAreaQueue.empty() && saveAreaQueue.empty();
|
|
for (const auto& ref: elements.areas) {
|
|
ZoneScopedN("Area");
|
|
const auto id = elements.withFlag(ref.val);
|
|
const auto a_node = elements.findArea(id.val);
|
|
assert(a_node);
|
|
const auto area = a_node->get();
|
|
const auto areaRange = glm::pow2(opts.keepDistance + area->getChunks().getRadius());
|
|
|
|
//MAYBE: use hierarchy
|
|
std::vector<chunk_pos> inRangeLoaders;
|
|
elements.nodes.iter([&](const node_id& id, const Node& cur) {
|
|
if (Elements::Has<Elements::Flag::ChunkLoader>(id)) {
|
|
const chunk_pos dist = glm::divide(cur.absolute.position - a_node->absolute.position);
|
|
if (glm::length2(dist) <= areaRange) //MAYBE: unique
|
|
inRangeLoaders.push_back(dist);
|
|
}
|
|
});
|
|
|
|
auto &chunks = area->setChunks();
|
|
bool lazyArea = queuesEmpty;
|
|
if (inRangeLoaders.empty()) {
|
|
if (!chunks.empty()) {
|
|
for (auto it_c = chunks.begin(); it_c != chunks.end();) {
|
|
saveAreaQueue.emplace(std::make_pair(id.val, area), std::make_pair(it_c->first, std::static_pointer_cast<Chunk>(it_c->second)));
|
|
lazyArea = false;
|
|
it_c = chunks.erase(it_c);
|
|
}
|
|
}
|
|
} else {
|
|
{ // Check alive chunks
|
|
ZoneScopedN("Alive");
|
|
for (auto it_c = chunks.begin(); it_c != chunks.end();) {
|
|
if (std::any_of(inRangeLoaders.begin(), inRangeLoaders.end(), [&] (const chunk_pos& dist) {
|
|
return glm::length2(dist - chunk_pos((glm::qua<double>)a_node->absolute.rotation * glm::dvec3(it_c->first))) <= keepDist2;
|
|
})) {
|
|
++it_c;
|
|
#if TRACY_ENABLE
|
|
chunk_count++;
|
|
#endif
|
|
} else {
|
|
saveAreaQueue.emplace(std::make_pair(id.val, area), std::make_pair(it_c->first, std::static_pointer_cast<Chunk>(it_c->second)));
|
|
lazyArea = false;
|
|
it_c = chunks.erase(it_c);
|
|
}
|
|
}
|
|
}
|
|
{ // Enqueue missing chunks
|
|
ZoneScopedN("Missing");
|
|
std::optional<load_queue_t::inserter_t> handle;
|
|
const lpivot inv_rot = glm::conjugate(a_node->absolute.rotation);
|
|
for (const auto& to: movedLoaders) {
|
|
const chunk_pos dist = glm::divide(inv_rot * (to - a_node->absolute.position));
|
|
if (glm::length2(dist) > areaRange)
|
|
continue;
|
|
|
|
//TODO: need dist so no easy sphere fill
|
|
const auto loadDistance = opts.loadDistance;
|
|
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 = dist + chunk_pos(x, y, z);
|
|
if (chunks.inRange(p) && chunks.find(p) == chunks.end()) {
|
|
if (!handle) { handle.emplace(loadAreaQueue.inserter()); }
|
|
handle.value().first(area_chunk_pos{id.val, p}, area, -dist2);
|
|
lazyArea = false;
|
|
}
|
|
}
|
|
}}}
|
|
}
|
|
}
|
|
}
|
|
#if TRACY_ENABLE
|
|
allLazy &= lazyArea;
|
|
#endif
|
|
if (lazyArea) { // Clear un-used regions
|
|
ZoneScopedN("Region");
|
|
const auto unique = area->getRegions(); // MAYBE: shared then unique
|
|
#if TRACY_ENABLE
|
|
region_count += unique->size();
|
|
#endif
|
|
for (auto it_r = unique->begin(); it_r != unique->end(); ++it_r) {
|
|
if(std::none_of(inRangeLoaders.begin(), inRangeLoaders.end(), [&] (const chunk_pos& dist) {
|
|
return glm::length2(dist - chunk_pos(it_r->first) * chunk_pos(REGION_LENGTH)) <= regionKeepDist2;
|
|
})) {
|
|
unique->erase(it_r); //FIXME: may wait for os file access (long)
|
|
break; //NOTE: save one only max per update
|
|
}
|
|
}
|
|
} else {
|
|
loadAreaQueue.notify_all();
|
|
}
|
|
}
|
|
// PARTS
|
|
for (const auto& ref: elements.parts) {
|
|
ZoneScopedN("Part");
|
|
const auto id = elements.withFlag(ref.val);
|
|
const auto r_node = elements.findPart(id.val);
|
|
assert(r_node);
|
|
const auto part = r_node->get();
|
|
|
|
//FIXME: pivot point
|
|
const auto partOffset = part->chunkSize() / chunk_pos(2);
|
|
|
|
//MAYBE: use hierarchy
|
|
const auto inRangeLoaders = elements.nodes.any([&](const node_id& id, const Node& cur) {
|
|
if (Elements::Has<Elements::Flag::ChunkLoader>(id)) {
|
|
const chunk_pos dist = glm::divide(cur.absolute.position - r_node->absolute.position);
|
|
return glm::length2(dist + partOffset) <= keepDist2;
|
|
}
|
|
return false;
|
|
});
|
|
|
|
if (!inRangeLoaders) {
|
|
if (!part->chunks.empty()) {
|
|
savePartQueue.emplace(id.val, part);
|
|
r_node->content = std::make_shared<Part>(part->size);
|
|
}
|
|
} else {
|
|
if (part->chunks.empty()) {
|
|
loadPartQueue.emplace(id.val, part);
|
|
part->allocate();
|
|
}
|
|
}
|
|
}
|
|
#if TRACY_ENABLE
|
|
TracyPlot("ChunkCount", static_cast<int64_t>(chunk_count));
|
|
if(allLazy) {
|
|
TracyPlot("Region", static_cast<int64_t>(region_count));
|
|
}
|
|
TracyPlot("AreaLoad", static_cast<int64_t>(loadAreaQueue.size()));
|
|
TracyPlot("AreaUnload", static_cast<int64_t>(saveAreaQueue.size()));
|
|
TracyPlot("PartLoad", static_cast<int64_t>(loadPartQueue.size()));
|
|
TracyPlot("PartUnload", static_cast<int64_t>(savePartQueue.size()));
|
|
#endif
|
|
loadAreaQueue.notify_all();
|
|
}
|
|
/* FIXME: { // Update entities
|
|
ZoneScopedN("Entities");
|
|
size_t item_count = 0;
|
|
entities.remove([&](entity_id type, Entity &val) {
|
|
//TODO: find mass center
|
|
const auto OFFSET = glm::vec3(val.size) * val.scale / 2.f;
|
|
const auto DIST2 = glm::pow2(loadDistance); //NOTE: must be < keepDistance + glm::length(OFFSET) to avoid collision miss
|
|
val.instances.remove([&](entity_id, Entity::Instance &inst) {
|
|
if (type == PLAYER_ENTITY_ID) {
|
|
//MAYBE: update players ?
|
|
item_count++;
|
|
return false;
|
|
}
|
|
|
|
//TODO: partition space
|
|
auto pos = inst.pos + OFFSET;
|
|
for(const auto& area: areas) {
|
|
//NOTE: consider entity mass negligible
|
|
inst.velocity += area.second->getGravity((pos - area.second->getOffset()).as_cell()) * deltaTime;
|
|
}
|
|
//MAYBE: friction
|
|
if (move(pos, inst.velocity, 1, glm::max_axis(OFFSET))) {
|
|
inst.velocity = glm::vec3(0);
|
|
}
|
|
inst.pos = pos - OFFSET;
|
|
if (entities.at(PLAYER_ENTITY_ID).instances.contains([&](const entity_id&, const Entity::Instance& player) {
|
|
return glm::length2(glm::divide((player.pos - inst.pos).as_cell())) <= DIST2;
|
|
})) {
|
|
item_count++;
|
|
return false;
|
|
}
|
|
//TODO: Save to files if !temporary
|
|
return true;
|
|
});
|
|
return !val.permanant && val.instances.empty();
|
|
});
|
|
TracyPlot("EntityCount", static_cast<int64_t>(item_count));
|
|
|
|
}*/
|
|
|
|
{ // Store loaded chunks
|
|
ZoneScopedN("Load");
|
|
loadedQueue.extractor([&](std::pair<node_chunk_pos, std::shared_ptr<Chunk>> &loaded) {
|
|
if (Elements::Is<Elements::Type::Area>(loaded.first.node)) {
|
|
if (const auto area = elements.findArea(loaded.first.node.val))
|
|
area->get()->setChunks().emplace(loaded.first.chunk, std::move(loaded.second));
|
|
} else {
|
|
if (const auto part = elements.findPart(loaded.first.node.val))
|
|
part->get()->emplace(loaded.first.chunk, std::move(loaded.second));
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
constexpr auto INDEX_FILE = "/nodes.idx";
|
|
void Universe::loadIndex() {
|
|
const auto elements = setElements();
|
|
assert(elements->nodes.empty());
|
|
io::ifstream index(opts.folderPath + INDEX_FILE);
|
|
if(index.good()) {
|
|
{ // Spawn
|
|
node_ref spawnParent;
|
|
index.read_cast(spawnParent);
|
|
spawnPoint.parent = generational::id(spawnParent.val, 0);
|
|
index.read_cast(spawnPoint.position);
|
|
}
|
|
elements->readIndex(index);
|
|
} else {
|
|
LOG_E("No index file!!! Probably a new world...");
|
|
spawnPoint.position = world_pos(100, 0, 0);
|
|
const auto p = elements->createInstance(Elements::start_point(generational::id(), transform(), velocity(offset_pos(), glm::angleAxis(.1f, glm::vec3(0, 0, 1)))), generational::id(0));
|
|
elements->createInstance(Elements::start_point(generational::id(), transform(), velocity(offset_pos(1))), generational::id(0));
|
|
elements->createInstance(Elements::start_point(p.val, transform(glm::vec3(1000, 0, 0))), generational::id(0));
|
|
//TODO: generate universe
|
|
{
|
|
const auto radius = 1 << 6;
|
|
const auto surface = radius * CHUNK_LENGTH * 3 / 4;
|
|
generator::RoundPlanet::Params opts(surface);
|
|
Area::params params{radius, 0, std::vector<char>(sizeof(opts))};
|
|
memcpy(params.params.data(), &opts, params.params.size());
|
|
elements->createArea(Elements::start_point(generational::id(), transform(world_pos(-surface, 0, 0)), velocity(offset_pos(), glm::angleAxis(.01f, glm::vec3(0, 1, 0)))), params);
|
|
}
|
|
}
|
|
index.close();
|
|
}
|
|
void Universe::writeIndex() {
|
|
io::ofstream index(opts.folderPath + INDEX_FILE, std::ios::out | std::ios::binary);
|
|
if(index.good()) {
|
|
index.write_cast(spawnPoint.parent.val.index());
|
|
index.write_cast(spawnPoint.position);
|
|
getElements()->writeIndex(index);
|
|
if(!index.good())
|
|
LOG_E("Index file write error");
|
|
} else {
|
|
LOG_E("Failed to open index file");
|
|
}
|
|
|
|
index.close();
|
|
}
|
|
|
|
std::shared_ptr<Chunk> Universe::findChunk(const node_chunk_pos& p) const {
|
|
const auto lock = getElements();
|
|
if (Elements::Is<Elements::Type::Area>(p.node)) {
|
|
if (auto area = lock->findArea(p.node.val)) {
|
|
return std::static_pointer_cast<Chunk>(area->get()->getChunks().findInRange(p.chunk));
|
|
}
|
|
} else {
|
|
if (auto part = lock->findPart(p.node.val)) {
|
|
return std::static_pointer_cast<Chunk>(part->get()->findInRange(p.chunk));
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
const world::Node* Universe::getPlayer(node_id id) const {
|
|
//FIXME: assert(IsPlayer(id))
|
|
return getElements()->findNode(id);
|
|
}
|
|
world::Node* Universe::setPlayer(node_id id) {
|
|
//FIXME: assert(IsPlayer(id))
|
|
return setElements()->findNode(id);
|
|
}
|
|
|
|
void Universe::fill(const action::FillShape& fill, fill_edits *const edits) {
|
|
const auto lock = setElements();
|
|
ZoneScopedN("Fill");
|
|
//TODO: ItemList inventory;
|
|
//TODO: multipart
|
|
const auto anchor_id = lock->withFlag(fill.pos.node);
|
|
const auto node = lock->findNode(anchor_id);
|
|
if (!node)
|
|
return;
|
|
|
|
const auto applyCB = [&](std::shared_ptr<Chunk>& ck, chunk_pos ck_pos, chunk_voxel_idx idx, Voxel/*prev*/, Voxel next, float delay) {
|
|
if (edits)
|
|
(*edits)[ck_pos].push_back(ChunkEdits::Edit{next, delay, idx});
|
|
//TODO: apply break table
|
|
//TODO: inventory
|
|
ck->replace(idx, next, delay);
|
|
};
|
|
const auto splitCBer = [&](const Elements::start_point& rtf, const auto& getter) {
|
|
return [&] (const robin_hood::unordered_set<voxel_pos> &voxels) {
|
|
//MAYBE: make it void after air propagation setup
|
|
const auto cleanVoxel = Voxel(0, Voxel::DENSITY_MAX);
|
|
voxel_pos min = voxel_pos(INT64_MAX);
|
|
voxel_pos max = voxel_pos(INT64_MIN);
|
|
for (const auto& full: voxels) {
|
|
min = glm::min<3, long long>(min, full);
|
|
max = glm::max<3, long long>(max, full);
|
|
}
|
|
auto start = rtf;
|
|
start.relative.position += min;
|
|
const auto part_id = lock->createPart(start, max - min);
|
|
const auto node = lock->findPart(part_id);
|
|
auto& part = *node->get();
|
|
{
|
|
const auto chunk_size = part.chunkSize();
|
|
const auto chunk_count = chunk_size.x * chunk_size.y * chunk_size.z;
|
|
part.chunks.reserve(chunk_count);
|
|
for (long i = 0; i < chunk_count; i++)
|
|
part.chunks.push_back(chunkFactory->create());
|
|
}
|
|
world::iterator::last_chunk<Chunk> src_ck;
|
|
world::iterator::last_chunk<Chunk> dst_ck{nullptr, chunk_pos(-1)};
|
|
Voxel cur;
|
|
for (const auto& full: voxels) {
|
|
const auto src_split = glm::splitIdx(full);
|
|
if (getter(src_split, cur, src_ck) && cur.is_solid()) {
|
|
if (edits)
|
|
(*edits)[src_ck.pos].push_back(ChunkEdits::Edit{cleanVoxel, fill.radius * world::iterator::FILL_DELAY, src_split.second});
|
|
|
|
src_ck.ptr->replace(src_split.second, cleanVoxel);
|
|
const auto dst_split = glm::splitIdx(full - min);
|
|
if (dst_split.first != dst_ck.pos) {
|
|
dst_ck.ptr = std::static_pointer_cast<Chunk>(part.findInRange(dst_split.first));
|
|
assert(dst_ck.ptr);
|
|
dst_ck.pos = dst_split.first;
|
|
}
|
|
dst_ck.ptr->set(dst_split.second, cur);
|
|
}
|
|
}
|
|
};
|
|
};
|
|
switch (Elements::GetType(anchor_id)) {
|
|
case Elements::Type::Area: {
|
|
const auto& chunks = NodeOf<Area>::Make(node)->get()->getChunks();
|
|
const auto rtf = Elements::start_point(anchor_id, transform());
|
|
world::iterator::Apply<Chunk>(chunks, fill, applyCB);
|
|
world::iterator::Split<Chunk>(chunks, fill, opts.floodFillLimit, splitCBer(rtf,
|
|
[&](const std::pair<glm::lvec3, glm::idx>& split, Voxel& out, world::iterator::last_chunk<Chunk>& ck) {
|
|
return world::iterator::GetVoxel<Chunk>(chunks, split, out, ck);
|
|
}
|
|
));
|
|
break;
|
|
}
|
|
/* TODO: case Elements::Type::Part: {
|
|
const auto& chunks = *NodeOf<Part>::Make(node)->get()->chunks;
|
|
const auto entry = lock->findHierarchy(anchor_id);
|
|
const auto rtf = start_point(entry->parent, entry->relative, entry->velocity);
|
|
world::iterator::Apply<Chunk>(chunks, fill, applyCB);
|
|
world::iterator::Split<Chunk>(chunks, fill, opts.floodFillLimit, splitCBer(rtf,
|
|
[&](const std::pair<glm::lvec3, glm::idx>& split, Voxel& out, world::iterator::last_chunk<Chunk>& ck) {
|
|
return world::iterator::GetVoxel<Chunk>(chunks, split, out, ck);
|
|
}
|
|
));
|
|
break;
|
|
}*/
|
|
default:
|
|
LOG_E("Unhandled fill target type " << (int)Elements::GetType(anchor_id));
|
|
break;
|
|
}
|
|
}
|
|
|
|
world::node_id Universe::addPlayer(std::optional<relative_pos> spawn) {
|
|
const auto &sp = spawn.value_or(spawnPoint);
|
|
const auto CHUNK_LOADER = Elements::Set<Elements::Flag::ChunkLoader>(0);
|
|
return setElements()->createInstance(Elements::start_point(sp), generational::id(0, 0), CHUNK_LOADER).val;
|
|
}
|
|
void Universe::removePlayer(node_id id) {
|
|
//FIXME: save player infos ?
|
|
setElements()->remove(id);
|
|
}
|
|
bool Universe::movePlayer(node_id id, const relative_pos& to) {
|
|
if (auto player = getPlayer(id)) {
|
|
//auto target = elements.computeAbsolute(elements.findParent(id), world::transform(to.position));
|
|
//TODO: check dist + collision from a to b
|
|
setElements()->move(id, to);
|
|
return true;
|
|
}
|
|
return false;
|
|
} |