VoxelEngine/src/files/WorldConverter.cpp
2024-09-02 23:24:59 +03:00

263 lines
7.3 KiB
C++

#include "WorldConverter.hpp"
#include <iostream>
#include <memory>
#include <stdexcept>
#include <utility>
#include "content/ContentReport.hpp"
#include "data/dynamic.hpp"
#include "debug/Logger.hpp"
#include "files/files.hpp"
#include "objects/Player.hpp"
#include "util/ThreadPool.hpp"
#include "voxels/Chunk.hpp"
#include "items/Inventory.hpp"
#include "WorldFiles.hpp"
namespace fs = std::filesystem;
static debug::Logger logger("world-converter");
class ConverterWorker : public util::Worker<ConvertTask, int> {
std::shared_ptr<WorldConverter> converter;
public:
ConverterWorker(std::shared_ptr<WorldConverter> converter)
: converter(std::move(converter)) {
}
int operator()(const std::shared_ptr<ConvertTask>& task) override {
converter->convert(*task);
return 0;
}
};
void WorldConverter::addRegionsTasks(
RegionLayerIndex layerid,
ConvertTaskType taskType
) {
const auto& regions = wfile->getRegions();
auto regionsFolder = regions.getRegionsFolder(layerid);
if (!fs::is_directory(regionsFolder)) {
return;
}
for (const auto& file : fs::directory_iterator(regionsFolder)) {
int x, z;
std::string name = file.path().stem().string();
if (!WorldRegions::parseRegionFilename(name, x, z)) {
logger.error() << "could not parse region name " << name;
continue;
}
tasks.push(ConvertTask {taskType, file.path(), x, z});
}
}
void WorldConverter::createUpgradeTasks() {
const auto& regions = wfile->getRegions();
for (auto& issue : report->getIssues()) {
if (issue.issueType != ContentIssueType::REGION_FORMAT_UPDATE) {
continue;
}
if (issue.regionLayer == REGION_LAYER_VOXELS) {
addRegionsTasks(issue.regionLayer, ConvertTaskType::UPGRADE_VOXELS);
} else {
addRegionsTasks(issue.regionLayer, ConvertTaskType::UPGRADE_SIMPLE);
}
}
}
void WorldConverter::createConvertTasks() {
auto handleReorder = [=](ContentType contentType) {
switch (contentType) {
case ContentType::BLOCK:
addRegionsTasks(
REGION_LAYER_VOXELS,
ConvertTaskType::VOXELS
);
break;
case ContentType::ITEM:
addRegionsTasks(
REGION_LAYER_INVENTORIES,
ConvertTaskType::INVENTORIES
);
break;
default:
break;
}
};
const auto& regions = wfile->getRegions();
for (auto& issue : report->getIssues()) {
switch (issue.issueType) {
case ContentIssueType::REGION_FORMAT_UPDATE:
break;
case ContentIssueType::MISSING:
throw std::runtime_error("issue can't be resolved");
case ContentIssueType::REORDER:
handleReorder(issue.contentType);
break;
}
}
tasks.push(ConvertTask {ConvertTaskType::PLAYER, wfile->getPlayerFile()});
}
WorldConverter::WorldConverter(
const std::shared_ptr<WorldFiles>& worldFiles,
const Content* content,
std::shared_ptr<ContentReport> reportPtr,
bool upgradeMode
)
: wfile(worldFiles),
report(std::move(reportPtr)),
content(content),
upgradeMode(upgradeMode)
{
if (upgradeMode) {
createUpgradeTasks();
} else {
createConvertTasks();
}
}
WorldConverter::~WorldConverter() {
}
std::shared_ptr<Task> WorldConverter::startTask(
const std::shared_ptr<WorldFiles>& worldFiles,
const Content* content,
const std::shared_ptr<ContentReport>& report,
const runnable& onDone,
bool upgradeMode,
bool multithreading
) {
auto converter = std::make_shared<WorldConverter>(
worldFiles, content, report, upgradeMode);
if (!multithreading) {
converter->setOnComplete([=]() {
converter->write();
onDone();
});
return converter;
}
auto pool = std::make_shared<util::ThreadPool<ConvertTask, int>>(
"converter-pool",
[=]() { return std::make_shared<ConverterWorker>(converter); },
[=](int&) {}
);
auto& converterTasks = converter->tasks;
while (!converterTasks.empty()) {
const ConvertTask& task = converterTasks.front();
auto ptr = std::make_shared<ConvertTask>(task);
pool->enqueueJob(ptr);
converterTasks.pop();
}
pool->setOnComplete([=]() {
converter->write();
onDone();
});
return pool;
}
void WorldConverter::upgradeSimple(const fs::path& file, int x, int z) const {
throw std::runtime_error("unsupported region format");
}
void WorldConverter::upgradeVoxels(const fs::path& file, int x, int z) const {
throw std::runtime_error("unsupported region format");
}
void WorldConverter::convertVoxels(const fs::path& file, int x, int z) const {
logger.info() << "converting voxels region " << x << "_" << z;
wfile->getRegions().processRegion(x, z, REGION_LAYER_VOXELS, CHUNK_DATA_LEN,
[=](std::unique_ptr<ubyte[]> data, uint32_t*) {
Chunk::convert(data.get(), report.get());
return data;
});
}
void WorldConverter::convertInventories(const fs::path& file, int x, int z) const {
logger.info() << "converting inventories region " << x << "_" << z;
wfile->getRegions().processInventories(x, z, [=](Inventory* inventory) {
inventory->convert(report.get());
});
}
void WorldConverter::convertPlayer(const fs::path& file) const {
logger.info() << "converting player " << file.u8string();
auto map = files::read_json(file);
Player::convert(map.get(), report.get());
files::write_json(file, map.get());
}
void WorldConverter::convert(const ConvertTask& task) const {
if (!fs::is_regular_file(task.file)) return;
switch (task.type) {
case ConvertTaskType::UPGRADE_SIMPLE:
upgradeSimple(task.file, task.x, task.z);
break;
case ConvertTaskType::UPGRADE_VOXELS:
upgradeVoxels(task.file, task.x, task.z);
break;
case ConvertTaskType::VOXELS:
convertVoxels(task.file, task.x, task.z);
break;
case ConvertTaskType::INVENTORIES:
convertInventories(task.file, task.x, task.z);
break;
case ConvertTaskType::PLAYER:
convertPlayer(task.file);
break;
}
}
void WorldConverter::convertNext() {
if (tasks.empty()) {
throw std::runtime_error("no more regions to convert");
}
ConvertTask task = tasks.front();
tasks.pop();
tasksDone++;
convert(task);
}
void WorldConverter::setOnComplete(runnable callback) {
this->onComplete = std::move(callback);
}
void WorldConverter::update() {
convertNext();
if (onComplete && tasks.empty()) {
onComplete();
}
}
void WorldConverter::terminate() {
tasks = {};
}
bool WorldConverter::isActive() const {
return !tasks.empty();
}
void WorldConverter::write() {
logger.info() << "writing world";
wfile->write(nullptr, upgradeMode ? nullptr : content);
}
void WorldConverter::waitForEnd() {
while (isActive()) {
update();
}
}
uint WorldConverter::getWorkTotal() const {
return tasks.size() + tasksDone;
}
uint WorldConverter::getWorkDone() const {
return tasksDone;
}