2019-08-17 11:43:08 -07:00
|
|
|
#include "voxel_stream_block_files.h"
|
2021-01-16 05:34:45 -08:00
|
|
|
#include "../server/voxel_server.h"
|
2021-02-01 14:25:25 -08:00
|
|
|
|
2019-05-27 16:40:09 -07:00
|
|
|
#include <core/os/dir_access.h>
|
|
|
|
#include <core/os/file_access.h>
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
const uint8_t FORMAT_VERSION = 1;
|
|
|
|
const char *FORMAT_META_MAGIC = "VXBM";
|
|
|
|
const char *FORMAT_BLOCK_MAGIC = "VXB_";
|
|
|
|
const char *META_FILE_NAME = "meta.vxbm";
|
|
|
|
const char *BLOCK_FILE_EXTENSION = ".vxb";
|
|
|
|
} // namespace
|
|
|
|
|
2021-02-01 14:25:25 -08:00
|
|
|
thread_local VoxelBlockSerializerInternal VoxelStreamBlockFiles::_block_serializer;
|
|
|
|
|
2019-08-17 11:43:08 -07:00
|
|
|
VoxelStreamBlockFiles::VoxelStreamBlockFiles() {
|
2019-05-27 16:40:09 -07:00
|
|
|
// Defaults
|
2019-08-23 17:44:27 -07:00
|
|
|
_meta.block_size_po2 = 4;
|
2019-05-27 16:40:09 -07:00
|
|
|
_meta.lod_count = 1;
|
|
|
|
_meta.version = FORMAT_VERSION;
|
2021-09-25 20:14:50 -07:00
|
|
|
_meta.channel_depths.fill(VoxelBufferInternal::DEFAULT_CHANNEL_DEPTH);
|
2019-05-27 16:40:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO Have configurable block size
|
|
|
|
|
2021-01-17 09:18:05 -08:00
|
|
|
VoxelStream::Result VoxelStreamBlockFiles::emerge_block(
|
2021-09-25 20:14:50 -07:00
|
|
|
VoxelBufferInternal &out_buffer, Vector3i origin_in_voxels, int lod) {
|
|
|
|
//
|
2019-05-27 16:40:09 -07:00
|
|
|
if (_directory_path.empty()) {
|
2021-01-17 09:18:05 -08:00
|
|
|
return RESULT_BLOCK_NOT_FOUND;
|
2019-05-27 16:40:09 -07:00
|
|
|
}
|
|
|
|
|
2019-08-23 17:44:27 -07:00
|
|
|
if (!_meta_loaded) {
|
2020-02-09 09:07:53 -08:00
|
|
|
if (load_meta() != VOXEL_FILE_OK) {
|
2021-01-17 09:18:05 -08:00
|
|
|
return RESULT_ERROR;
|
2019-05-27 16:40:09 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-23 17:44:27 -07:00
|
|
|
CRASH_COND(!_meta_loaded);
|
|
|
|
|
|
|
|
const Vector3i block_size(1 << _meta.block_size_po2);
|
|
|
|
|
2021-01-17 09:18:05 -08:00
|
|
|
ERR_FAIL_COND_V(lod >= _meta.lod_count, RESULT_ERROR);
|
2021-09-25 20:14:50 -07:00
|
|
|
ERR_FAIL_COND_V(block_size != out_buffer.get_size(), RESULT_ERROR);
|
2019-05-27 16:40:09 -07:00
|
|
|
|
2019-06-01 17:59:39 -07:00
|
|
|
Vector3i block_pos = get_block_position(origin_in_voxels) >> lod;
|
2019-05-27 16:40:09 -07:00
|
|
|
String file_path = get_block_file_path(block_pos, lod);
|
|
|
|
|
|
|
|
FileAccess *f = nullptr;
|
2021-01-16 05:34:45 -08:00
|
|
|
VoxelFileLockerRead file_rlock(file_path);
|
2019-05-27 16:40:09 -07:00
|
|
|
{
|
|
|
|
Error err;
|
2021-02-01 14:25:25 -08:00
|
|
|
f = FileAccess::open(file_path, FileAccess::READ, &err);
|
2019-06-01 17:59:39 -07:00
|
|
|
// Had to add ERR_FILE_CANT_OPEN because that's what Godot actually returns when the file doesn't exist...
|
|
|
|
if (f == nullptr && (err == ERR_FILE_NOT_FOUND || err == ERR_FILE_CANT_OPEN)) {
|
2021-01-17 09:18:05 -08:00
|
|
|
return RESULT_BLOCK_NOT_FOUND;
|
2019-05-27 16:40:09 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-17 09:18:05 -08:00
|
|
|
ERR_FAIL_COND_V(f == nullptr, RESULT_ERROR);
|
2019-05-27 16:40:09 -07:00
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
uint8_t version;
|
2020-02-09 09:07:53 -08:00
|
|
|
VoxelFileResult err = check_magic_and_version(f, FORMAT_VERSION, FORMAT_BLOCK_MAGIC, version);
|
2021-01-16 05:34:45 -08:00
|
|
|
if (err != VOXEL_FILE_OK) {
|
|
|
|
memdelete(f);
|
|
|
|
ERR_PRINT(String("Invalid file header: ") + ::to_string(err));
|
2021-01-17 09:18:05 -08:00
|
|
|
return RESULT_ERROR;
|
2021-01-16 05:34:45 -08:00
|
|
|
}
|
2020-02-09 09:07:53 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Configure depths, as they currently are only specified in the meta file.
|
|
|
|
// Files are expected to contain such depths, and use those in the buffer to know how much data to read.
|
|
|
|
for (unsigned int channel_index = 0; channel_index < _meta.channel_depths.size(); ++channel_index) {
|
2021-09-25 20:14:50 -07:00
|
|
|
out_buffer.set_channel_depth(channel_index, _meta.channel_depths[channel_index]);
|
2019-05-27 16:40:09 -07:00
|
|
|
}
|
|
|
|
|
2019-08-17 10:15:16 -07:00
|
|
|
uint32_t size_to_read = f->get_32();
|
2021-09-25 20:14:50 -07:00
|
|
|
if (!_block_serializer.decompress_and_deserialize(f, size_to_read, out_buffer)) {
|
2021-01-16 05:34:45 -08:00
|
|
|
ERR_PRINT("Failed to decompress and deserialize");
|
|
|
|
}
|
2019-05-27 16:40:09 -07:00
|
|
|
}
|
2019-08-17 10:15:16 -07:00
|
|
|
|
|
|
|
f->close();
|
|
|
|
memdelete(f);
|
2021-01-17 09:18:05 -08:00
|
|
|
|
|
|
|
return RESULT_BLOCK_FOUND;
|
2019-05-27 16:40:09 -07:00
|
|
|
}
|
|
|
|
|
2021-09-25 20:14:50 -07:00
|
|
|
void VoxelStreamBlockFiles::immerge_block(VoxelBufferInternal &buffer, Vector3i origin_in_voxels, int lod) {
|
2019-05-27 16:40:09 -07:00
|
|
|
ERR_FAIL_COND(_directory_path.empty());
|
|
|
|
|
2020-02-09 10:37:44 -08:00
|
|
|
if (!_meta_loaded) {
|
|
|
|
// If it's not loaded, always try to load meta file first if it exists already,
|
|
|
|
// because we could want to save blocks without reading any
|
|
|
|
VoxelFileResult res = load_meta();
|
|
|
|
if (res != VOXEL_FILE_OK && res != VOXEL_FILE_CANT_OPEN) {
|
|
|
|
// The file is present but there is a problem with it
|
|
|
|
String meta_path = _directory_path.plus_file(META_FILE_NAME);
|
|
|
|
ERR_PRINT(String("Could not read {0}: {1}").format(varray(meta_path, ::to_string(res))));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-23 17:44:27 -07:00
|
|
|
if (!_meta_saved) {
|
2020-02-09 10:37:44 -08:00
|
|
|
// First time we save the meta file, initialize it from the first block format
|
|
|
|
for (unsigned int i = 0; i < _meta.channel_depths.size(); ++i) {
|
2021-09-25 20:14:50 -07:00
|
|
|
_meta.channel_depths[i] = buffer.get_channel_depth(i);
|
2020-02-09 10:37:44 -08:00
|
|
|
}
|
2020-02-09 09:07:53 -08:00
|
|
|
VoxelFileResult res = save_meta();
|
|
|
|
ERR_FAIL_COND(res != VOXEL_FILE_OK);
|
|
|
|
}
|
|
|
|
|
2020-02-09 10:37:44 -08:00
|
|
|
// Check format
|
|
|
|
const Vector3i block_size = Vector3i(1 << _meta.block_size_po2);
|
2021-09-25 20:14:50 -07:00
|
|
|
ERR_FAIL_COND(buffer.get_size() != block_size);
|
2020-02-09 09:07:53 -08:00
|
|
|
for (unsigned int channel_index = 0; channel_index < _meta.channel_depths.size(); ++channel_index) {
|
2021-09-25 20:14:50 -07:00
|
|
|
ERR_FAIL_COND(buffer.get_channel_depth(channel_index) != _meta.channel_depths[channel_index]);
|
2019-05-27 16:40:09 -07:00
|
|
|
}
|
|
|
|
|
2019-06-01 17:59:39 -07:00
|
|
|
Vector3i block_pos = get_block_position(origin_in_voxels) >> lod;
|
2019-05-27 16:40:09 -07:00
|
|
|
String file_path = get_block_file_path(block_pos, lod);
|
|
|
|
|
|
|
|
{
|
2021-01-16 05:34:45 -08:00
|
|
|
const Error err = check_directory_created(file_path.get_base_dir());
|
2019-05-27 16:40:09 -07:00
|
|
|
ERR_FAIL_COND(err != OK);
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
FileAccess *f = nullptr;
|
2021-01-16 05:34:45 -08:00
|
|
|
VoxelFileLockerWrite file_wlock(file_path);
|
2019-05-27 16:40:09 -07:00
|
|
|
{
|
|
|
|
Error err;
|
|
|
|
// Create file if not exists, always truncate
|
2021-02-01 14:25:25 -08:00
|
|
|
f = FileAccess::open(file_path, FileAccess::WRITE, &err);
|
2019-05-27 16:40:09 -07:00
|
|
|
}
|
|
|
|
ERR_FAIL_COND(f == nullptr);
|
|
|
|
|
|
|
|
f->store_buffer((uint8_t *)FORMAT_BLOCK_MAGIC, 4);
|
|
|
|
f->store_8(FORMAT_VERSION);
|
|
|
|
|
2021-09-25 20:14:50 -07:00
|
|
|
VoxelBlockSerializerInternal::SerializeResult res = _block_serializer.serialize_and_compress(buffer);
|
2021-01-29 17:36:37 -08:00
|
|
|
if (!res.success) {
|
|
|
|
memdelete(f);
|
|
|
|
ERR_PRINT("Failed to save block");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
f->store_32(res.data.size());
|
|
|
|
f->store_buffer(res.data.data(), res.data.size());
|
2019-08-17 10:15:16 -07:00
|
|
|
|
|
|
|
f->close();
|
|
|
|
memdelete(f);
|
2019-05-27 16:40:09 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-17 17:44:33 -07:00
|
|
|
int VoxelStreamBlockFiles::get_used_channels_mask() const {
|
|
|
|
// Assuming all, since that stream can store anything.
|
2021-09-25 20:14:50 -07:00
|
|
|
return VoxelBufferInternal::ALL_CHANNELS_MASK;
|
2021-09-17 17:44:33 -07:00
|
|
|
}
|
|
|
|
|
2019-08-17 11:43:08 -07:00
|
|
|
String VoxelStreamBlockFiles::get_directory() const {
|
2019-05-27 16:40:09 -07:00
|
|
|
return _directory_path;
|
|
|
|
}
|
|
|
|
|
2019-08-17 11:43:08 -07:00
|
|
|
void VoxelStreamBlockFiles::set_directory(String dirpath) {
|
2019-05-27 16:40:09 -07:00
|
|
|
if (_directory_path != dirpath) {
|
|
|
|
_directory_path = dirpath;
|
2019-08-23 17:44:27 -07:00
|
|
|
_meta_loaded = false;
|
2019-05-27 16:40:09 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-23 17:44:27 -07:00
|
|
|
int VoxelStreamBlockFiles::get_block_size_po2() const {
|
|
|
|
return _meta.block_size_po2;
|
|
|
|
}
|
|
|
|
|
2020-02-09 09:07:53 -08:00
|
|
|
VoxelFileResult VoxelStreamBlockFiles::save_meta() {
|
2019-05-27 16:40:09 -07:00
|
|
|
CRASH_COND(_directory_path.empty());
|
|
|
|
|
|
|
|
// Make sure the directory exists
|
|
|
|
{
|
|
|
|
Error err = check_directory_created(_directory_path);
|
|
|
|
if (err != OK) {
|
|
|
|
ERR_PRINT("Could not save meta");
|
2020-02-09 09:07:53 -08:00
|
|
|
return VOXEL_FILE_CANT_OPEN;
|
2019-05-27 16:40:09 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
String meta_path = _directory_path.plus_file(META_FILE_NAME);
|
|
|
|
|
|
|
|
{
|
|
|
|
Error err;
|
2021-01-16 05:34:45 -08:00
|
|
|
VoxelFileLockerWrite file_wlock(meta_path);
|
2021-02-01 14:25:25 -08:00
|
|
|
FileAccess *f = FileAccess::open(meta_path, FileAccess::WRITE, &err);
|
2020-02-09 09:07:53 -08:00
|
|
|
ERR_FAIL_COND_V(f == nullptr, VOXEL_FILE_CANT_OPEN);
|
2019-05-27 16:40:09 -07:00
|
|
|
|
|
|
|
f->store_buffer((uint8_t *)FORMAT_META_MAGIC, 4);
|
|
|
|
f->store_8(FORMAT_VERSION);
|
|
|
|
|
|
|
|
f->store_8(_meta.lod_count);
|
2019-08-23 17:44:27 -07:00
|
|
|
f->store_8(_meta.block_size_po2);
|
2019-05-27 16:40:09 -07:00
|
|
|
|
2020-01-25 14:54:49 -08:00
|
|
|
for (unsigned int i = 0; i < _meta.channel_depths.size(); ++i) {
|
|
|
|
f->store_8(_meta.channel_depths[i]);
|
|
|
|
}
|
|
|
|
|
2019-05-27 16:40:09 -07:00
|
|
|
memdelete(f);
|
|
|
|
}
|
|
|
|
|
2019-08-23 17:44:27 -07:00
|
|
|
_meta_loaded = true;
|
|
|
|
_meta_saved = true;
|
2020-02-09 09:07:53 -08:00
|
|
|
return VOXEL_FILE_OK;
|
2019-05-27 16:40:09 -07:00
|
|
|
}
|
|
|
|
|
2021-01-16 05:34:45 -08:00
|
|
|
VoxelFileResult VoxelStreamBlockFiles::load_or_create_meta() {
|
|
|
|
VoxelFileResult res = load_meta();
|
|
|
|
if (res == VOXEL_FILE_DOES_NOT_EXIST) {
|
|
|
|
VoxelFileResult save_result = save_meta();
|
|
|
|
ERR_FAIL_COND_V(save_result != VOXEL_FILE_OK, save_result);
|
|
|
|
return VOXEL_FILE_OK;
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2020-02-09 09:07:53 -08:00
|
|
|
VoxelFileResult VoxelStreamBlockFiles::load_meta() {
|
2019-05-27 16:40:09 -07:00
|
|
|
CRASH_COND(_directory_path.empty());
|
|
|
|
|
|
|
|
String meta_path = _directory_path.plus_file(META_FILE_NAME);
|
|
|
|
|
|
|
|
Meta meta;
|
|
|
|
{
|
2020-02-09 09:07:53 -08:00
|
|
|
Error open_result;
|
2021-01-16 05:34:45 -08:00
|
|
|
VoxelFileLockerRead file_rlock(meta_path);
|
2021-02-01 14:25:25 -08:00
|
|
|
FileAccessRef f = FileAccess::open(meta_path, FileAccess::READ, &open_result);
|
2019-06-01 17:59:39 -07:00
|
|
|
// Had to add ERR_FILE_CANT_OPEN because that's what Godot actually returns when the file doesn't exist...
|
2020-02-09 09:07:53 -08:00
|
|
|
if (!_meta_saved && (open_result == ERR_FILE_NOT_FOUND || open_result == ERR_FILE_CANT_OPEN)) {
|
2019-06-01 17:59:39 -07:00
|
|
|
// This is a new terrain, save the meta we have and consider it current
|
2021-01-16 05:34:45 -08:00
|
|
|
return VOXEL_FILE_DOES_NOT_EXIST;
|
2019-06-01 17:59:39 -07:00
|
|
|
}
|
2020-02-09 09:07:53 -08:00
|
|
|
ERR_FAIL_COND_V(!f, VOXEL_FILE_CANT_OPEN);
|
2019-05-27 16:40:09 -07:00
|
|
|
|
2020-02-09 09:07:53 -08:00
|
|
|
VoxelFileResult check_result = check_magic_and_version(f.f, FORMAT_VERSION, FORMAT_META_MAGIC, meta.version);
|
|
|
|
if (check_result != VOXEL_FILE_OK) {
|
|
|
|
return check_result;
|
2019-05-27 16:40:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
meta.lod_count = f->get_8();
|
2019-08-23 17:44:27 -07:00
|
|
|
meta.block_size_po2 = f->get_8();
|
|
|
|
|
2020-01-25 14:54:49 -08:00
|
|
|
for (unsigned int i = 0; i < meta.channel_depths.size(); ++i) {
|
|
|
|
uint8_t depth = f->get_8();
|
2021-09-25 20:14:50 -07:00
|
|
|
ERR_FAIL_COND_V(depth >= VoxelBufferInternal::DEPTH_COUNT, VOXEL_FILE_INVALID_DATA);
|
|
|
|
meta.channel_depths[i] = (VoxelBufferInternal::Depth)depth;
|
2020-01-25 14:54:49 -08:00
|
|
|
}
|
|
|
|
|
2020-02-09 09:07:53 -08:00
|
|
|
ERR_FAIL_COND_V(meta.lod_count < 1 || meta.lod_count > 32, VOXEL_FILE_INVALID_DATA);
|
|
|
|
ERR_FAIL_COND_V(meta.block_size_po2 < 1 || meta.block_size_po2 > 8, VOXEL_FILE_INVALID_DATA);
|
2019-05-27 16:40:09 -07:00
|
|
|
}
|
|
|
|
|
2019-08-23 17:44:27 -07:00
|
|
|
_meta_loaded = true;
|
2019-05-27 16:40:09 -07:00
|
|
|
_meta = meta;
|
2020-02-09 09:07:53 -08:00
|
|
|
return VOXEL_FILE_OK;
|
2019-05-27 16:40:09 -07:00
|
|
|
}
|
|
|
|
|
2019-08-17 11:43:08 -07:00
|
|
|
String VoxelStreamBlockFiles::get_block_file_path(const Vector3i &block_pos, unsigned int lod) const {
|
2019-05-27 16:40:09 -07:00
|
|
|
// TODO This is probably extremely inefficient, also given the nature of Godot strings
|
|
|
|
|
|
|
|
// Save under a folder, because there could be other kinds of data to store in this terrain
|
|
|
|
String path = "blocks/lod";
|
|
|
|
path += String::num_uint64(lod);
|
|
|
|
path += '/';
|
|
|
|
for (unsigned int i = 0; i < 3; ++i) {
|
|
|
|
if (block_pos[i] >= 0) {
|
|
|
|
path += '+';
|
|
|
|
}
|
2019-06-01 17:59:39 -07:00
|
|
|
path += String::num_int64(block_pos[i]);
|
2019-05-27 16:40:09 -07:00
|
|
|
}
|
|
|
|
path += BLOCK_FILE_EXTENSION;
|
2019-06-01 17:59:39 -07:00
|
|
|
return _directory_path.plus_file(path);
|
2019-05-27 16:40:09 -07:00
|
|
|
}
|
|
|
|
|
2019-08-17 11:43:08 -07:00
|
|
|
Vector3i VoxelStreamBlockFiles::get_block_position(const Vector3i &origin_in_voxels) const {
|
2019-08-23 17:44:27 -07:00
|
|
|
return origin_in_voxels >> _meta.block_size_po2;
|
2019-05-27 16:40:09 -07:00
|
|
|
}
|
|
|
|
|
2019-08-17 11:43:08 -07:00
|
|
|
void VoxelStreamBlockFiles::_bind_methods() {
|
|
|
|
ClassDB::bind_method(D_METHOD("set_directory", "directory"), &VoxelStreamBlockFiles::set_directory);
|
|
|
|
ClassDB::bind_method(D_METHOD("get_directory"), &VoxelStreamBlockFiles::get_directory);
|
2019-05-27 16:40:09 -07:00
|
|
|
|
2019-08-17 12:22:57 -07:00
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::STRING, "directory", PROPERTY_HINT_DIR), "set_directory", "get_directory");
|
2019-05-27 16:40:09 -07:00
|
|
|
}
|