move world-related sources from files/ to world/files
This commit is contained in:
@@ -0,0 +1,327 @@
|
||||
#include "WorldConverter.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
#include "content/ContentReport.hpp"
|
||||
#include "compatibility.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 "voxels/Block.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 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, layerid});
|
||||
}
|
||||
}
|
||||
|
||||
void WorldConverter::createUpgradeTasks() {
|
||||
const auto& regions = wfile->getRegions();
|
||||
for (auto& issue : report->getIssues()) {
|
||||
if (issue.issueType != ContentIssueType::REGION_FORMAT_UPDATE) {
|
||||
continue;
|
||||
}
|
||||
addRegionsTasks(issue.regionLayer, ConvertTaskType::UPGRADE_REGION);
|
||||
}
|
||||
}
|
||||
|
||||
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::BLOCK_DATA_LAYOUTS_UPDATE:
|
||||
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()});
|
||||
}
|
||||
|
||||
void WorldConverter::createBlockFieldsConvertTasks() {
|
||||
// blocks data conversion requires correct block indices
|
||||
// so it must be done AFTER voxels conversion
|
||||
const auto& regions = wfile->getRegions();
|
||||
for (auto& issue : report->getIssues()) {
|
||||
switch (issue.issueType) {
|
||||
case ContentIssueType::BLOCK_DATA_LAYOUTS_UPDATE:
|
||||
addRegionsTasks(
|
||||
REGION_LAYER_BLOCKS_DATA,
|
||||
ConvertTaskType::CONVERT_BLOCKS_DATA
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WorldConverter::WorldConverter(
|
||||
const std::shared_ptr<WorldFiles>& worldFiles,
|
||||
const Content* content,
|
||||
std::shared_ptr<ContentReport> reportPtr,
|
||||
ConvertMode mode
|
||||
)
|
||||
: wfile(worldFiles),
|
||||
report(std::move(reportPtr)),
|
||||
content(content),
|
||||
mode(mode)
|
||||
{
|
||||
switch (mode) {
|
||||
case ConvertMode::UPGRADE:
|
||||
createUpgradeTasks();
|
||||
break;
|
||||
case ConvertMode::REINDEX:
|
||||
createConvertTasks();
|
||||
break;
|
||||
case ConvertMode::BLOCK_FIELDS:
|
||||
createBlockFieldsConvertTasks();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
ConvertMode mode,
|
||||
bool multithreading
|
||||
) {
|
||||
auto converter = std::make_shared<WorldConverter>(
|
||||
worldFiles, content, report, mode);
|
||||
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()) {
|
||||
ConvertTask task = std::move(converterTasks.front());
|
||||
converterTasks.pop();
|
||||
pool->enqueueJob(std::move(task));
|
||||
|
||||
}
|
||||
pool->setOnComplete([=]() {
|
||||
converter->write();
|
||||
onDone();
|
||||
});
|
||||
return pool;
|
||||
}
|
||||
|
||||
void WorldConverter::upgradeRegion(
|
||||
const fs::path& file, int x, int z, RegionLayerIndex layer
|
||||
) const {
|
||||
auto path = wfile->getRegions().getRegionFilePath(layer, x, z);
|
||||
auto bytes = files::read_bytes_buffer(path);
|
||||
auto buffer = compatibility::convert_region_2to3(bytes, layer);
|
||||
files::write_bytes(path, buffer.data(), buffer.size());
|
||||
}
|
||||
|
||||
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,
|
||||
[=](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, report.get());
|
||||
files::write_json(file, map);
|
||||
}
|
||||
|
||||
void WorldConverter::convertBlocksData(int x, int z, const ContentReport& report) const {
|
||||
logger.info() << "converting blocks data";
|
||||
wfile->getRegions().processBlocksData(x, z,
|
||||
[=](BlocksMetadata* heap, std::unique_ptr<ubyte[]> voxelsData) {
|
||||
Chunk chunk(0, 0);
|
||||
chunk.decode(voxelsData.get());
|
||||
|
||||
const auto& indices = content->getIndices()->blocks;
|
||||
|
||||
BlocksMetadata newHeap;
|
||||
for (const auto& entry : *heap) {
|
||||
size_t index = entry.index;
|
||||
const auto& def = indices.require(chunk.voxels[index].id);
|
||||
const auto& newStruct = *def.dataStruct;
|
||||
const auto& found = report.blocksDataLayouts.find(def.name);
|
||||
if (found == report.blocksDataLayouts.end()) {
|
||||
logger.error() << "no previous fields layout found for block"
|
||||
<< def.name << " - discard";
|
||||
continue;
|
||||
}
|
||||
const auto& prevStruct = found->second;
|
||||
uint8_t* dst = newHeap.allocate(index, newStruct.size());
|
||||
newStruct.convert(prevStruct, entry.data(), dst, true);
|
||||
}
|
||||
*heap = std::move(newHeap);
|
||||
});
|
||||
}
|
||||
|
||||
void WorldConverter::convert(const ConvertTask& task) const {
|
||||
if (!fs::is_regular_file(task.file)) return;
|
||||
|
||||
switch (task.type) {
|
||||
case ConvertTaskType::UPGRADE_REGION:
|
||||
upgradeRegion(task.file, task.x, task.z, task.layer);
|
||||
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;
|
||||
case ConvertTaskType::CONVERT_BLOCKS_DATA:
|
||||
convertBlocksData(task.x, task.z, *report);
|
||||
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() << "applying changes";
|
||||
|
||||
auto patch = dv::object();
|
||||
switch (mode) {
|
||||
case ConvertMode::UPGRADE:
|
||||
patch["region-version"] = REGION_FORMAT_VERSION;
|
||||
break;
|
||||
case ConvertMode::REINDEX:
|
||||
WorldFiles::createContentIndicesCache(content->getIndices(), patch);
|
||||
break;
|
||||
case ConvertMode::BLOCK_FIELDS:
|
||||
WorldFiles::createBlockFieldsIndices(content->getIndices(), patch);
|
||||
break;
|
||||
}
|
||||
wfile->patchIndicesFile(patch);
|
||||
wfile->write(nullptr, nullptr);
|
||||
}
|
||||
|
||||
void WorldConverter::waitForEnd() {
|
||||
while (isActive()) {
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
uint WorldConverter::getWorkTotal() const {
|
||||
return tasks.size() + tasksDone;
|
||||
}
|
||||
|
||||
uint WorldConverter::getWorkDone() const {
|
||||
return tasksDone;
|
||||
}
|
||||
Reference in New Issue
Block a user