Abstracted voxel metadata so internals no longer depends on Variant
This commit is contained in:
parent
8aec9cf777
commit
d7e0af161a
@ -1,6 +1,9 @@
|
|||||||
Voxel block format
|
Voxel block format
|
||||||
====================
|
====================
|
||||||
|
|
||||||
|
!!! warn
|
||||||
|
This document is about an old version of the format. You may check the most recent version.
|
||||||
|
|
||||||
Version: 3
|
Version: 3
|
||||||
|
|
||||||
This page describes the binary format used by default in this module to serialize voxel blocks to files, network or databases.
|
This page describes the binary format used by default in this module to serialize voxel blocks to files, network or databases.
|
||||||
|
130
doc/source/specs/block_format_v4.md
Normal file
130
doc/source/specs/block_format_v4.md
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
Voxel block format
|
||||||
|
====================
|
||||||
|
|
||||||
|
Version: 4
|
||||||
|
|
||||||
|
This page describes the binary format used by default in this module to serialize voxel blocks to files, network or databases.
|
||||||
|
|
||||||
|
### Changes from version 3
|
||||||
|
|
||||||
|
- Metadata uses a new format which no longer depends on Godot Engine.
|
||||||
|
|
||||||
|
|
||||||
|
Specification
|
||||||
|
----------------
|
||||||
|
|
||||||
|
### Endianess
|
||||||
|
|
||||||
|
By default, little-endian.
|
||||||
|
|
||||||
|
### Compressed container
|
||||||
|
|
||||||
|
A block is usually serialized within a compressed data container.
|
||||||
|
This is the format provided by the `VoxelBlockSerializer` utility class. If you don't use compression, the layout will correspond to `BlockData` described in the next listing, and won't have this wrapper.
|
||||||
|
See [Compressed container format](#compressed-container) for specification.
|
||||||
|
|
||||||
|
### Block format
|
||||||
|
|
||||||
|
It starts with version number `4` in one byte, then some info and the actual voxels. Optionally, it is followed by custom metadata.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
The size and formats are present to make the format standalone. When used within a chunked container like region files, it is recommended to check if they match the format expected for the volume as a whole.
|
||||||
|
|
||||||
|
```
|
||||||
|
BlockData
|
||||||
|
- version: uint8_t
|
||||||
|
- size_x: uint16_t
|
||||||
|
- size_y: uint16_t
|
||||||
|
- size_z: uint16_t
|
||||||
|
- channels[8]
|
||||||
|
- metadata*
|
||||||
|
- epilogue
|
||||||
|
```
|
||||||
|
|
||||||
|
### Channels
|
||||||
|
|
||||||
|
Block data starts with exactly 8 channels one after the other, each with the following structure:
|
||||||
|
|
||||||
|
```
|
||||||
|
Channel
|
||||||
|
- format: uint8_t (low nibble = compression, high nibble = depth)
|
||||||
|
- data
|
||||||
|
```
|
||||||
|
|
||||||
|
`format` contains both compression and bit depth, respectively known as `VoxelBuffer::Compression` and `VoxelBuffer::Depth` enums. The low nibble contains compression, and the high nibble contains depth. Depending on those values, `data` will be different.
|
||||||
|
|
||||||
|
Depth can be 0 (8-bit), 1 (16-bit), 2 (32-bit) or 3 (64-bit).
|
||||||
|
|
||||||
|
If compression is `COMPRESSION_NONE` (0), `data` will be an array of N*S bytes, where N is the number of voxels inside a block, multiplied by the number of bytes corresponding to the bit depth. For example, a block of size 16x16x16 and a channel of 32-bit depth will have `16*16*16*4` bytes to load from the file into this channel.
|
||||||
|
The 3D indexing of that data is in order `ZXY`.
|
||||||
|
|
||||||
|
If compression is `COMPRESSION_UNIFORM` (1), the data will be a single voxel value, which means all voxels in the block have that same value. Unused channels will always use this mode. The value spans the same number of bytes defined by the depth.
|
||||||
|
|
||||||
|
Other compression values are invalid.
|
||||||
|
|
||||||
|
#### SDF channel
|
||||||
|
|
||||||
|
The second channel (at index 1) is used for SDF data. If depth is 8 or 16 bits, it may contain fixed-point values encoded as `inorm8` or `inorm16`. This is numbers in the range [-1..1].
|
||||||
|
|
||||||
|
To obtain a `float` from an `int8`, use `max(i / 127, -1.f)`.
|
||||||
|
To obtain a `float` from an `int16`, use `max(i / 32767, -1.f)`.
|
||||||
|
|
||||||
|
For 32-bit depth, regular `float` are used.
|
||||||
|
For 64-bit depth, regular `double` are used.
|
||||||
|
|
||||||
|
### Metadata
|
||||||
|
|
||||||
|
After all channels information, block data can contain metadata information. Blocks that don't contain any will only have a fixed amount of bytes left (from the epilogue) before reaching the size of the total data to read. If there is more, the block contains metadata.
|
||||||
|
|
||||||
|
```
|
||||||
|
Metadata
|
||||||
|
- metadata_size: uint32_t
|
||||||
|
- block_metadata: MetadataItem
|
||||||
|
- voxel_metadata: VoxelMetadataItem[*]
|
||||||
|
|
||||||
|
VoxelMetadataItem
|
||||||
|
- x: uint16_t
|
||||||
|
- y: uint16_t
|
||||||
|
- z: uint16_t
|
||||||
|
- metadata: MetadataItem
|
||||||
|
```
|
||||||
|
|
||||||
|
It starts with one 32-bit unsigned integer representing the total size of all metadata there is to read. That data comes in two groups: one for the whole block, and a list that associates one per voxel (not all voxels have metadata).
|
||||||
|
|
||||||
|
Each metadata item uses the following format:
|
||||||
|
|
||||||
|
```
|
||||||
|
MetadataItem
|
||||||
|
- type: uint8_t
|
||||||
|
- data
|
||||||
|
```
|
||||||
|
|
||||||
|
It starts with a `type` header, followed by data depending on that type.
|
||||||
|
|
||||||
|
- If `type` is `0`, the item is empty and there is no `data` to read.
|
||||||
|
- If `type` is `1`, it is followed by 8 bytes (`uint64_t`).
|
||||||
|
- If `type` is `32`, it is followed by a Godot Engine `Variant`, encoded using the `encode_variant` function. This is only available when using Godot Engine.
|
||||||
|
- If `type` is greater than `32`, the following data is application-defined. The application usually knows which data corresponds to that type and defines how to serialize and deserialize it.
|
||||||
|
|
||||||
|
The meaning of metadata is application-defined. Two games using different metadata are not expected to be compatible.
|
||||||
|
|
||||||
|
|
||||||
|
### Epilogue
|
||||||
|
|
||||||
|
At the very end, block data finishes with a sequence of 4 bytes, which once read into a `uint32_t` integer must match the value `0x900df00d`. If that condition isn't fulfilled, the block must be assumed corrupted.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
On little-endian architectures (like desktop), binary editors will not show the epilogue as `0x900df00d`, but as `0x0df00d90` instead.
|
||||||
|
|
||||||
|
|
||||||
|
Current Issues
|
||||||
|
----------------
|
||||||
|
|
||||||
|
### Endianess
|
||||||
|
|
||||||
|
The format is intented to use little-endian, however the implementation of the engine does not fully guarantee this.
|
||||||
|
|
||||||
|
Godot's `encode_variant` doesn't seem to care about endianess across architectures, so it's possible it becomes a problem in the future and gets changed to a custom format.
|
||||||
|
The implementation of block channels with depth greater than 8-bit currently doesn't consider this either. This might be refined in a later iteration.
|
||||||
|
|
||||||
|
This will become important to address if voxel games require communication between mobile and desktop.
|
@ -1,5 +1,5 @@
|
|||||||
#ifndef EDITION_FUNCS_H
|
#ifndef VOXEL_EDITION_FUNCS_H
|
||||||
#define EDITION_FUNCS_H
|
#define VOXEL_EDITION_FUNCS_H
|
||||||
|
|
||||||
#include "../storage/funcs.h"
|
#include "../storage/funcs.h"
|
||||||
#include "../util/fixed_array.h"
|
#include "../util/fixed_array.h"
|
||||||
@ -110,4 +110,4 @@ inline void blend_texture_packed_u16(
|
|||||||
|
|
||||||
} // namespace zylann::voxel
|
} // namespace zylann::voxel
|
||||||
|
|
||||||
#endif // EDITION_FUNCS_H
|
#endif // VOXEL_EDITION_FUNCS_H
|
||||||
|
@ -64,12 +64,12 @@ void VoxelToolBuffer::_post_edit(const Box3i &box) {
|
|||||||
|
|
||||||
void VoxelToolBuffer::set_voxel_metadata(Vector3i pos, Variant meta) {
|
void VoxelToolBuffer::set_voxel_metadata(Vector3i pos, Variant meta) {
|
||||||
ERR_FAIL_COND(_buffer.is_null());
|
ERR_FAIL_COND(_buffer.is_null());
|
||||||
_buffer->get_buffer().set_voxel_metadata(pos, meta);
|
_buffer->set_voxel_metadata(pos, meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
Variant VoxelToolBuffer::get_voxel_metadata(Vector3i pos) const {
|
Variant VoxelToolBuffer::get_voxel_metadata(Vector3i pos) const {
|
||||||
ERR_FAIL_COND_V(_buffer.is_null(), Variant());
|
ERR_FAIL_COND_V(_buffer.is_null(), Variant());
|
||||||
return _buffer->get_buffer().get_voxel_metadata(pos);
|
return _buffer->get_voxel_metadata(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoxelToolBuffer::paste(
|
void VoxelToolBuffer::paste(
|
||||||
@ -116,7 +116,7 @@ void VoxelToolBuffer::paste(
|
|||||||
dst.set_voxel(v, x, y, z, channel_index);
|
dst.set_voxel(v, x, y, z, channel_index);
|
||||||
|
|
||||||
// Overwrite previous metadata
|
// Overwrite previous metadata
|
||||||
dst.set_voxel_metadata(Vector3i(x, y, z), Variant());
|
dst.erase_voxel_metadata(Vector3i(x, y, z));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
#include "../meshers/blocky/voxel_mesher_blocky.h"
|
#include "../meshers/blocky/voxel_mesher_blocky.h"
|
||||||
#include "../meshers/cubes/voxel_mesher_cubes.h"
|
#include "../meshers/cubes/voxel_mesher_cubes.h"
|
||||||
#include "../storage/voxel_buffer_gd.h"
|
#include "../storage/voxel_buffer_gd.h"
|
||||||
|
#include "../storage/voxel_metadata_variant.h"
|
||||||
#include "../terrain/fixed_lod/voxel_terrain.h"
|
#include "../terrain/fixed_lod/voxel_terrain.h"
|
||||||
#include "../util/godot/funcs.h"
|
#include "../util/godot/funcs.h"
|
||||||
#include "../util/voxel_raycast.h"
|
#include "../util/voxel_raycast.h"
|
||||||
@ -211,7 +212,9 @@ void VoxelToolTerrain::set_voxel_metadata(Vector3i pos, Variant meta) {
|
|||||||
VoxelDataBlock *block = map.get_block(map.voxel_to_block(pos));
|
VoxelDataBlock *block = map.get_block(map.voxel_to_block(pos));
|
||||||
ERR_FAIL_COND_MSG(block == nullptr, "Area not editable");
|
ERR_FAIL_COND_MSG(block == nullptr, "Area not editable");
|
||||||
RWLockWrite lock(block->get_voxels().get_lock());
|
RWLockWrite lock(block->get_voxels().get_lock());
|
||||||
block->get_voxels().set_voxel_metadata(map.to_local(pos), meta);
|
VoxelMetadata *meta_storage = block->get_voxels().get_or_create_voxel_metadata(map.to_local(pos));
|
||||||
|
ERR_FAIL_COND(meta_storage == nullptr);
|
||||||
|
gd::set_as_variant(*meta_storage, meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
Variant VoxelToolTerrain::get_voxel_metadata(Vector3i pos) const {
|
Variant VoxelToolTerrain::get_voxel_metadata(Vector3i pos) const {
|
||||||
@ -220,7 +223,11 @@ Variant VoxelToolTerrain::get_voxel_metadata(Vector3i pos) const {
|
|||||||
VoxelDataBlock *block = map.get_block(map.voxel_to_block(pos));
|
VoxelDataBlock *block = map.get_block(map.voxel_to_block(pos));
|
||||||
ERR_FAIL_COND_V_MSG(block == nullptr, Variant(), "Area not editable");
|
ERR_FAIL_COND_V_MSG(block == nullptr, Variant(), "Area not editable");
|
||||||
RWLockRead lock(block->get_voxels().get_lock());
|
RWLockRead lock(block->get_voxels().get_lock());
|
||||||
return block->get_voxels_const().get_voxel_metadata(map.to_local(pos));
|
const VoxelMetadata *meta = block->get_voxels_const().get_voxel_metadata(map.to_local(pos));
|
||||||
|
if (meta == nullptr) {
|
||||||
|
return Variant();
|
||||||
|
}
|
||||||
|
return gd::get_as_variant(*meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoxelToolTerrain::run_blocky_random_tick_static(VoxelDataMap &map, Box3i voxel_box, const VoxelBlockyLibrary &lib,
|
void VoxelToolTerrain::run_blocky_random_tick_static(VoxelDataMap &map, Box3i voxel_box, const VoxelBlockyLibrary &lib,
|
||||||
@ -389,9 +396,10 @@ void VoxelToolTerrain::for_each_voxel_metadata_in_area(AABB voxel_area, const Ca
|
|||||||
// TODO Worth it locking blocks for metadata?
|
// TODO Worth it locking blocks for metadata?
|
||||||
|
|
||||||
block->get_voxels().for_each_voxel_metadata_in_area(
|
block->get_voxels().for_each_voxel_metadata_in_area(
|
||||||
rel_voxel_box, [&callback, block_origin](Vector3i rel_pos, Variant meta) {
|
rel_voxel_box, [&callback, block_origin](Vector3i rel_pos, const VoxelMetadata &meta) {
|
||||||
|
Variant v = gd::get_as_variant(meta);
|
||||||
const Variant key = rel_pos + block_origin;
|
const Variant key = rel_pos + block_origin;
|
||||||
const Variant *args[2] = { &key, &meta };
|
const Variant *args[2] = { &key, &v };
|
||||||
Callable::CallError err;
|
Callable::CallError err;
|
||||||
Variant retval; // We don't care about the return value, Callable API requires it
|
Variant retval; // We don't care about the return value, Callable API requires it
|
||||||
callback.call(args, 2, retval, err);
|
callback.call(args, 2, retval, err);
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
#include "server/voxel_server_gd.h"
|
#include "server/voxel_server_gd.h"
|
||||||
#include "storage/voxel_buffer_gd.h"
|
#include "storage/voxel_buffer_gd.h"
|
||||||
#include "storage/voxel_memory_pool.h"
|
#include "storage/voxel_memory_pool.h"
|
||||||
|
#include "storage/voxel_metadata_variant.h"
|
||||||
#include "streams/region/voxel_stream_region_files.h"
|
#include "streams/region/voxel_stream_region_files.h"
|
||||||
#include "streams/sqlite/voxel_stream_sqlite.h"
|
#include "streams/sqlite/voxel_stream_sqlite.h"
|
||||||
#include "streams/vox_loader.h"
|
#include "streams/vox_loader.h"
|
||||||
@ -77,6 +78,8 @@ void register_voxel_types() {
|
|||||||
|
|
||||||
Engine::get_singleton()->add_singleton(Engine::Singleton("VoxelServer", gd::VoxelServer::get_singleton()));
|
Engine::get_singleton()->add_singleton(Engine::Singleton("VoxelServer", gd::VoxelServer::get_singleton()));
|
||||||
|
|
||||||
|
VoxelMetadataFactory::get_singleton().add_constructor_by_type<gd::VoxelMetadataVariant>(gd::METADATA_TYPE_VARIANT);
|
||||||
|
|
||||||
// TODO Can I prevent users from instancing it? is "register_virtual_class" correct for a class that's not abstract?
|
// TODO Can I prevent users from instancing it? is "register_virtual_class" correct for a class that's not abstract?
|
||||||
ClassDB::register_class<gd::VoxelServer>();
|
ClassDB::register_class<gd::VoxelServer>();
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#include "voxel_buffer_gd.h"
|
#include "voxel_buffer_gd.h"
|
||||||
#include "../edition/voxel_tool_buffer.h"
|
#include "../edition/voxel_tool_buffer.h"
|
||||||
#include "../util/memory.h"
|
#include "../util/memory.h"
|
||||||
|
#include "voxel_metadata_variant.h"
|
||||||
|
|
||||||
#include <core/io/image.h>
|
#include <core/io/image.h>
|
||||||
|
|
||||||
@ -105,19 +106,42 @@ VoxelBuffer::Depth VoxelBuffer::get_channel_depth(unsigned int channel_index) co
|
|||||||
return VoxelBuffer::Depth(_buffer->get_channel_depth(channel_index));
|
return VoxelBuffer::Depth(_buffer->get_channel_depth(channel_index));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Variant VoxelBuffer::get_block_metadata() const {
|
||||||
|
return get_as_variant(_buffer->get_block_metadata());
|
||||||
|
}
|
||||||
|
|
||||||
void VoxelBuffer::set_block_metadata(Variant meta) {
|
void VoxelBuffer::set_block_metadata(Variant meta) {
|
||||||
_buffer->set_block_metadata(meta);
|
set_as_variant(_buffer->get_block_metadata(), meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
Variant VoxelBuffer::get_voxel_metadata(Vector3i pos) const {
|
||||||
|
VoxelMetadata *meta = _buffer->get_voxel_metadata(pos);
|
||||||
|
if (meta == nullptr) {
|
||||||
|
return Variant();
|
||||||
|
}
|
||||||
|
return get_as_variant(*meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VoxelBuffer::set_voxel_metadata(Vector3i pos, Variant meta) {
|
||||||
|
if (meta.get_type() == Variant::NIL) {
|
||||||
|
_buffer->erase_voxel_metadata(pos);
|
||||||
|
} else {
|
||||||
|
VoxelMetadata *mv = _buffer->get_or_create_voxel_metadata(pos);
|
||||||
|
ZN_ASSERT_RETURN(mv != nullptr);
|
||||||
|
set_as_variant(*mv, meta);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoxelBuffer::for_each_voxel_metadata(const Callable &callback) const {
|
void VoxelBuffer::for_each_voxel_metadata(const Callable &callback) const {
|
||||||
ERR_FAIL_COND(callback.is_null());
|
ERR_FAIL_COND(callback.is_null());
|
||||||
//_buffer->for_each_voxel_metadata(callback);
|
//_buffer->for_each_voxel_metadata(callback);
|
||||||
|
|
||||||
const FlatMap<Vector3i, Variant> &metadata = _buffer->get_voxel_metadata();
|
const FlatMapMoveOnly<Vector3i, VoxelMetadata> &metadata = _buffer->get_voxel_metadata();
|
||||||
|
|
||||||
for (auto it = metadata.begin(); it != metadata.end(); ++it) {
|
for (auto it = metadata.begin(); it != metadata.end(); ++it) {
|
||||||
|
Variant v = get_as_variant(it->value);
|
||||||
const Variant key = it->key;
|
const Variant key = it->key;
|
||||||
const Variant *args[2] = { &key, &it->value };
|
const Variant *args[2] = { &key, &v };
|
||||||
Callable::CallError err;
|
Callable::CallError err;
|
||||||
Variant retval; // We don't care about the return value, Callable API requires it
|
Variant retval; // We don't care about the return value, Callable API requires it
|
||||||
callback.call(args, 2, retval, err);
|
callback.call(args, 2, retval, err);
|
||||||
@ -136,9 +160,10 @@ void VoxelBuffer::for_each_voxel_metadata_in_area(const Callable &callback, Vect
|
|||||||
|
|
||||||
const Box3i box = Box3i::from_min_max(min_pos, max_pos);
|
const Box3i box = Box3i::from_min_max(min_pos, max_pos);
|
||||||
|
|
||||||
_buffer->for_each_voxel_metadata_in_area(box, [&callback](Vector3i rel_pos, Variant meta) {
|
_buffer->for_each_voxel_metadata_in_area(box, [&callback](Vector3i rel_pos, const VoxelMetadata &meta) {
|
||||||
|
Variant v = get_as_variant(meta);
|
||||||
const Variant key = rel_pos;
|
const Variant key = rel_pos;
|
||||||
const Variant *args[2] = { &key, &meta };
|
const Variant *args[2] = { &key, &v };
|
||||||
Callable::CallError err;
|
Callable::CallError err;
|
||||||
Variant retval; // We don't care about the return value, Callable API requires it
|
Variant retval; // We don't care about the return value, Callable API requires it
|
||||||
callback.call(args, 2, retval, err);
|
callback.call(args, 2, retval, err);
|
||||||
|
@ -149,17 +149,12 @@ public:
|
|||||||
|
|
||||||
// Metadata
|
// Metadata
|
||||||
|
|
||||||
Variant get_block_metadata() const {
|
Variant get_block_metadata() const;
|
||||||
return _buffer->get_block_metadata();
|
|
||||||
}
|
|
||||||
void set_block_metadata(Variant meta);
|
void set_block_metadata(Variant meta);
|
||||||
|
|
||||||
Variant get_voxel_metadata(Vector3i pos) const {
|
Variant get_voxel_metadata(Vector3i pos) const;
|
||||||
return _buffer->get_voxel_metadata(pos);
|
void set_voxel_metadata(Vector3i pos, Variant meta);
|
||||||
}
|
|
||||||
void set_voxel_metadata(Vector3i pos, Variant meta) {
|
|
||||||
_buffer->set_voxel_metadata(pos, meta);
|
|
||||||
}
|
|
||||||
void for_each_voxel_metadata(const Callable &callback) const;
|
void for_each_voxel_metadata(const Callable &callback) const;
|
||||||
void for_each_voxel_metadata_in_area(const Callable &callback, Vector3i min_pos, Vector3i max_pos);
|
void for_each_voxel_metadata_in_area(const Callable &callback, Vector3i min_pos, Vector3i max_pos);
|
||||||
void copy_voxel_metadata_in_area(
|
void copy_voxel_metadata_in_area(
|
||||||
|
@ -597,13 +597,8 @@ void VoxelBufferInternal::move_to(VoxelBufferInternal &dst) {
|
|||||||
dst._channels = _channels;
|
dst._channels = _channels;
|
||||||
dst._size = _size;
|
dst._size = _size;
|
||||||
|
|
||||||
// TODO Optimization: Godot needs move semantics
|
dst._block_metadata = std::move(_block_metadata);
|
||||||
dst._block_metadata = _block_metadata;
|
dst._voxel_metadata = std::move(_voxel_metadata);
|
||||||
_block_metadata = BlockMetadata();
|
|
||||||
|
|
||||||
// TODO Optimization: Godot needs move semantics
|
|
||||||
dst._voxel_metadata = _voxel_metadata;
|
|
||||||
_voxel_metadata.clear();
|
|
||||||
|
|
||||||
for (unsigned int i = 0; i < _channels.size(); ++i) {
|
for (unsigned int i = 0; i < _channels.size(); ++i) {
|
||||||
Channel &channel = _channels[i];
|
Channel &channel = _channels[i];
|
||||||
@ -780,27 +775,33 @@ float VoxelBufferInternal::get_sdf_quantization_scale(Depth d) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoxelBufferInternal::set_block_metadata(Variant meta) {
|
const VoxelMetadata *VoxelBufferInternal::get_voxel_metadata(Vector3i pos) const {
|
||||||
_block_metadata.user_data = meta;
|
ZN_ASSERT_RETURN_V(is_position_valid(pos), nullptr);
|
||||||
|
return _voxel_metadata.find(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
Variant VoxelBufferInternal::get_voxel_metadata(Vector3i pos) const {
|
VoxelMetadata *VoxelBufferInternal::get_voxel_metadata(Vector3i pos) {
|
||||||
ZN_ASSERT_RETURN_V(is_position_valid(pos), Variant());
|
ZN_ASSERT_RETURN_V(is_position_valid(pos), nullptr);
|
||||||
Variant metadata;
|
return _voxel_metadata.find(pos);
|
||||||
_voxel_metadata.find(pos, metadata);
|
|
||||||
return metadata;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoxelBufferInternal::set_voxel_metadata(Vector3i pos, Variant meta) {
|
VoxelMetadata *VoxelBufferInternal::get_or_create_voxel_metadata(Vector3i pos) {
|
||||||
ZN_ASSERT_RETURN(is_position_valid(pos));
|
ZN_ASSERT_RETURN_V(is_position_valid(pos), nullptr);
|
||||||
if (meta.get_type() == Variant::NIL) {
|
VoxelMetadata *d = _voxel_metadata.find(pos);
|
||||||
_voxel_metadata.erase(pos);
|
if (d != nullptr) {
|
||||||
} else {
|
return d;
|
||||||
_voxel_metadata.insert_or_assign(pos, meta);
|
|
||||||
}
|
}
|
||||||
|
// TODO Optimize: we know the key should not exist
|
||||||
|
VoxelMetadata &meta = _voxel_metadata.insert_or_assign(pos, VoxelMetadata());
|
||||||
|
return &meta;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoxelBufferInternal::clear_and_set_voxel_metadata(Span<FlatMap<Vector3i, Variant>::Pair> pairs) {
|
void VoxelBufferInternal::erase_voxel_metadata(Vector3i pos) {
|
||||||
|
ZN_ASSERT_RETURN(is_position_valid(pos));
|
||||||
|
_voxel_metadata.erase(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VoxelBufferInternal::clear_and_set_voxel_metadata(Span<FlatMapMoveOnly<Vector3i, VoxelMetadata>::Pair> pairs) {
|
||||||
#ifdef DEBUG_ENABLED
|
#ifdef DEBUG_ENABLED
|
||||||
for (size_t i = 0; i < pairs.size(); ++i) {
|
for (size_t i = 0; i < pairs.size(); ++i) {
|
||||||
ZN_ASSERT_CONTINUE(is_position_valid(pairs[i].key));
|
ZN_ASSERT_CONTINUE(is_position_valid(pairs[i].key));
|
||||||
@ -853,7 +854,7 @@ void VoxelBufferInternal::clear_voxel_metadata() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VoxelBufferInternal::clear_voxel_metadata_in_area(Box3i box) {
|
void VoxelBufferInternal::clear_voxel_metadata_in_area(Box3i box) {
|
||||||
_voxel_metadata.remove_if([&box](const FlatMap<Vector3i, Variant>::Pair &p) { //
|
_voxel_metadata.remove_if([&box](const FlatMapMoveOnly<Vector3i, VoxelMetadata>::Pair &p) { //
|
||||||
return box.contains(p.key);
|
return box.contains(p.key);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -865,12 +866,14 @@ void VoxelBufferInternal::copy_voxel_metadata_in_area(
|
|||||||
const Box3i clipped_src_box = src_box.clipped(Box3i(src_box.pos - dst_origin, _size));
|
const Box3i clipped_src_box = src_box.clipped(Box3i(src_box.pos - dst_origin, _size));
|
||||||
const Vector3i clipped_dst_offset = dst_origin + clipped_src_box.pos - src_box.pos;
|
const Vector3i clipped_dst_offset = dst_origin + clipped_src_box.pos - src_box.pos;
|
||||||
|
|
||||||
for (FlatMap<Vector3i, Variant>::ConstIterator src_it = src_buffer._voxel_metadata.begin();
|
for (FlatMapMoveOnly<Vector3i, VoxelMetadata>::ConstIterator src_it = src_buffer._voxel_metadata.begin();
|
||||||
src_it != src_buffer._voxel_metadata.end(); ++src_it) {
|
src_it != src_buffer._voxel_metadata.end(); ++src_it) {
|
||||||
if (src_box.contains(src_it->key)) {
|
if (src_box.contains(src_it->key)) {
|
||||||
const Vector3i dst_pos = src_it->key + clipped_dst_offset;
|
const Vector3i dst_pos = src_it->key + clipped_dst_offset;
|
||||||
ZN_ASSERT(is_position_valid(dst_pos));
|
ZN_ASSERT(is_position_valid(dst_pos));
|
||||||
_voxel_metadata.insert_or_assign(dst_pos, src_it->value.duplicate());
|
|
||||||
|
VoxelMetadata &meta = _voxel_metadata.insert_or_assign(dst_pos, VoxelMetadata());
|
||||||
|
meta.copy_from(src_it->value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -878,12 +881,13 @@ void VoxelBufferInternal::copy_voxel_metadata_in_area(
|
|||||||
void VoxelBufferInternal::copy_voxel_metadata(const VoxelBufferInternal &src_buffer) {
|
void VoxelBufferInternal::copy_voxel_metadata(const VoxelBufferInternal &src_buffer) {
|
||||||
ZN_ASSERT_RETURN(src_buffer.get_size() == _size);
|
ZN_ASSERT_RETURN(src_buffer.get_size() == _size);
|
||||||
|
|
||||||
for (FlatMap<Vector3i, Variant>::ConstIterator src_it = src_buffer._voxel_metadata.begin();
|
for (FlatMapMoveOnly<Vector3i, VoxelMetadata>::ConstIterator src_it = src_buffer._voxel_metadata.begin();
|
||||||
src_it != src_buffer._voxel_metadata.end(); ++src_it) {
|
src_it != src_buffer._voxel_metadata.end(); ++src_it) {
|
||||||
_voxel_metadata.insert_or_assign(src_it->key, src_it->value.duplicate());
|
VoxelMetadata &meta = _voxel_metadata.insert_or_assign(src_it->key, VoxelMetadata());
|
||||||
|
meta.copy_from(src_it->value);
|
||||||
}
|
}
|
||||||
|
|
||||||
_block_metadata.user_data = src_buffer._block_metadata.user_data.duplicate();
|
_block_metadata.copy_from(src_buffer._block_metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace zylann::voxel
|
} // namespace zylann::voxel
|
||||||
|
@ -6,9 +6,9 @@
|
|||||||
#include "../util/flat_map.h"
|
#include "../util/flat_map.h"
|
||||||
#include "../util/math/box3i.h"
|
#include "../util/math/box3i.h"
|
||||||
#include "funcs.h"
|
#include "funcs.h"
|
||||||
|
#include "voxel_metadata.h"
|
||||||
|
|
||||||
#include <core/os/rw_lock.h>
|
#include <core/os/rw_lock.h>
|
||||||
#include <core/variant/variant.h>
|
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
|
||||||
namespace zylann::voxel {
|
namespace zylann::voxel {
|
||||||
@ -416,20 +416,26 @@ public:
|
|||||||
|
|
||||||
// Metadata
|
// Metadata
|
||||||
|
|
||||||
Variant get_block_metadata() const {
|
VoxelMetadata &get_block_metadata() {
|
||||||
return _block_metadata.user_data;
|
return _block_metadata;
|
||||||
|
}
|
||||||
|
const VoxelMetadata &get_block_metadata() const {
|
||||||
|
return _block_metadata;
|
||||||
}
|
}
|
||||||
void set_block_metadata(Variant meta);
|
|
||||||
|
|
||||||
Variant get_voxel_metadata(Vector3i pos) const;
|
const VoxelMetadata *get_voxel_metadata(Vector3i pos) const;
|
||||||
void set_voxel_metadata(Vector3i pos, Variant meta);
|
VoxelMetadata *get_voxel_metadata(Vector3i pos);
|
||||||
|
VoxelMetadata *get_or_create_voxel_metadata(Vector3i pos);
|
||||||
|
void erase_voxel_metadata(Vector3i pos);
|
||||||
|
|
||||||
void clear_and_set_voxel_metadata(Span<FlatMap<Vector3i, Variant>::Pair> pairs);
|
void clear_and_set_voxel_metadata(Span<FlatMapMoveOnly<Vector3i, VoxelMetadata>::Pair> pairs);
|
||||||
|
|
||||||
template <typename F>
|
template <typename F>
|
||||||
void for_each_voxel_metadata_in_area(Box3i box, F callback) const {
|
void for_each_voxel_metadata_in_area(Box3i box, F callback) const {
|
||||||
for (FlatMap<Vector3i, Variant>::ConstIterator it = _voxel_metadata.begin(); it != _voxel_metadata.end();
|
// TODO For `find`s and this kind of iteration, we may want to separate keys and values in FlatMap's internal
|
||||||
++it) {
|
// storage, to reduce cache misses
|
||||||
|
for (FlatMapMoveOnly<Vector3i, VoxelMetadata>::ConstIterator it = _voxel_metadata.begin();
|
||||||
|
it != _voxel_metadata.end(); ++it) {
|
||||||
if (box.contains(it->key)) {
|
if (box.contains(it->key)) {
|
||||||
callback(it->key, it->value);
|
callback(it->key, it->value);
|
||||||
}
|
}
|
||||||
@ -447,7 +453,7 @@ public:
|
|||||||
void copy_voxel_metadata_in_area(const VoxelBufferInternal &src_buffer, Box3i src_box, Vector3i dst_origin);
|
void copy_voxel_metadata_in_area(const VoxelBufferInternal &src_buffer, Box3i src_box, Vector3i dst_origin);
|
||||||
void copy_voxel_metadata(const VoxelBufferInternal &src_buffer);
|
void copy_voxel_metadata(const VoxelBufferInternal &src_buffer);
|
||||||
|
|
||||||
const FlatMap<Vector3i, Variant> &get_voxel_metadata() const {
|
const FlatMapMoveOnly<Vector3i, VoxelMetadata> &get_voxel_metadata() const {
|
||||||
return _voxel_metadata;
|
return _voxel_metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -480,15 +486,10 @@ private:
|
|||||||
// How many voxels are there in the three directions. All populated channels have the same size.
|
// How many voxels are there in the three directions. All populated channels have the same size.
|
||||||
Vector3i _size;
|
Vector3i _size;
|
||||||
|
|
||||||
struct BlockMetadata {
|
|
||||||
// User-defined data, not used by the engine
|
|
||||||
Variant user_data;
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO Could we separate metadata from VoxelBufferInternal?
|
// TODO Could we separate metadata from VoxelBufferInternal?
|
||||||
BlockMetadata _block_metadata;
|
VoxelMetadata _block_metadata;
|
||||||
// This metadata is expected to be sparse, with low amount of items.
|
// This metadata is expected to be sparse, with low amount of items.
|
||||||
FlatMap<Vector3i, Variant> _voxel_metadata;
|
FlatMapMoveOnly<Vector3i, VoxelMetadata> _voxel_metadata;
|
||||||
|
|
||||||
// TODO It may be preferable to actually move away from storing an RWLock in every buffer in the future.
|
// TODO It may be preferable to actually move away from storing an RWLock in every buffer in the future.
|
||||||
// We should be able to find a solution because very few of these locks are actually used at a given time.
|
// We should be able to find a solution because very few of these locks are actually used at a given time.
|
||||||
|
101
storage/voxel_metadata.cpp
Normal file
101
storage/voxel_metadata.cpp
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
#include "voxel_metadata.h"
|
||||||
|
#include "../util/errors.h"
|
||||||
|
#include "../util/string_funcs.h"
|
||||||
|
|
||||||
|
namespace zylann::voxel {
|
||||||
|
|
||||||
|
void VoxelMetadata::clear() {
|
||||||
|
if (_type >= TYPE_CUSTOM_BEGIN) {
|
||||||
|
ZN_DELETE(_data.custom_data);
|
||||||
|
_data.custom_data = nullptr;
|
||||||
|
}
|
||||||
|
_type = TYPE_EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VoxelMetadata::set_u64(const uint64_t &v) {
|
||||||
|
if (_type != TYPE_U64) {
|
||||||
|
clear();
|
||||||
|
_type = TYPE_U64;
|
||||||
|
}
|
||||||
|
_data.u64_data = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t VoxelMetadata::get_u64() const {
|
||||||
|
ZN_ASSERT(_type == TYPE_U64);
|
||||||
|
return _data.u64_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VoxelMetadata::set_custom(uint8_t type, ICustomVoxelMetadata *custom_data) {
|
||||||
|
ZN_ASSERT(type >= TYPE_CUSTOM_BEGIN);
|
||||||
|
clear();
|
||||||
|
_type = type;
|
||||||
|
_data.custom_data = custom_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
ICustomVoxelMetadata &VoxelMetadata::get_custom() {
|
||||||
|
ZN_ASSERT(_type >= TYPE_CUSTOM_BEGIN);
|
||||||
|
ZN_ASSERT(_data.custom_data != nullptr);
|
||||||
|
return *_data.custom_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ICustomVoxelMetadata &VoxelMetadata::get_custom() const {
|
||||||
|
ZN_ASSERT(_type >= TYPE_CUSTOM_BEGIN);
|
||||||
|
ZN_ASSERT(_data.custom_data != nullptr);
|
||||||
|
return *_data.custom_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VoxelMetadata::copy_from(const VoxelMetadata &src) {
|
||||||
|
clear();
|
||||||
|
if (src._type >= TYPE_CUSTOM_BEGIN) {
|
||||||
|
ZN_ASSERT(src._data.custom_data != nullptr);
|
||||||
|
_data.custom_data = src._data.custom_data->duplicate();
|
||||||
|
} else {
|
||||||
|
_data = src._data;
|
||||||
|
}
|
||||||
|
_type = src._type;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
VoxelMetadataFactory g_voxel_metadata_factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
VoxelMetadataFactory &VoxelMetadataFactory::get_singleton() {
|
||||||
|
return g_voxel_metadata_factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
VoxelMetadataFactory::VoxelMetadataFactory() {
|
||||||
|
fill(_constructors, (ConstructorFunc) nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VoxelMetadataFactory::add_constructor(uint8_t type, ConstructorFunc ctor) {
|
||||||
|
ZN_ASSERT(type >= VoxelMetadata::TYPE_CUSTOM_BEGIN);
|
||||||
|
const unsigned int i = type - VoxelMetadata::TYPE_CUSTOM_BEGIN;
|
||||||
|
ZN_ASSERT_MSG(_constructors[i] == nullptr, "Type already registered");
|
||||||
|
_constructors[i] = ctor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VoxelMetadataFactory::remove_constructor(uint8_t type) {
|
||||||
|
ZN_ASSERT(type >= VoxelMetadata::TYPE_CUSTOM_BEGIN);
|
||||||
|
const unsigned int i = type - VoxelMetadata::TYPE_CUSTOM_BEGIN;
|
||||||
|
ZN_ASSERT_MSG(_constructors[i] != nullptr, "Type not registered");
|
||||||
|
_constructors[i] = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ICustomVoxelMetadata *VoxelMetadataFactory::try_construct(uint8_t type) const {
|
||||||
|
ZN_ASSERT_RETURN_V_MSG(
|
||||||
|
type >= VoxelMetadata::TYPE_CUSTOM_BEGIN, nullptr, format("Invalid custom metadata type {}", type));
|
||||||
|
const unsigned int i = type - VoxelMetadata::TYPE_CUSTOM_BEGIN;
|
||||||
|
|
||||||
|
const ConstructorFunc ctor = _constructors[i];
|
||||||
|
ZN_ASSERT_RETURN_V_MSG(ctor != nullptr, nullptr, format("Custom metadata constructor not found for type {}", type));
|
||||||
|
|
||||||
|
ICustomVoxelMetadata *m = ctor();
|
||||||
|
ZN_ASSERT_RETURN_V_MSG(
|
||||||
|
m != nullptr, nullptr, format("Custom metadata constructor for type {} returned nullptr", type));
|
||||||
|
|
||||||
|
return ctor();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace zylann::voxel
|
131
storage/voxel_metadata.h
Normal file
131
storage/voxel_metadata.h
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
#ifndef VOXEL_METADATA_H
|
||||||
|
#define VOXEL_METADATA_H
|
||||||
|
|
||||||
|
#include "../util/memory.h"
|
||||||
|
//#include "../util/non_copyable.h"
|
||||||
|
#include "../util/span.h"
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace zylann::voxel {
|
||||||
|
|
||||||
|
// Voxel metadata is arbitrary, sparse data that can be attached to particular voxels.
|
||||||
|
// It is not intented at being an efficient or fast storage method, but rather a versatile one for special cases.
|
||||||
|
// For example, it can be used to store text, tags, inventory contents, or other complex states attached to a voxel.
|
||||||
|
// If you need to store smaller data much more frequently, you may rely on a data channel instead.
|
||||||
|
|
||||||
|
// Base interface for custom data types.
|
||||||
|
class ICustomVoxelMetadata {
|
||||||
|
public:
|
||||||
|
virtual ~ICustomVoxelMetadata() {}
|
||||||
|
|
||||||
|
// Gets how many bytes this metadata will occupy when serialized.
|
||||||
|
virtual size_t get_serialized_size() const = 0;
|
||||||
|
|
||||||
|
// Serializes this metadata into `dst`. The size of `dst` will be equal or greater than the size returned by
|
||||||
|
// `get_serialized_size()`. Returns how many bytes were written.
|
||||||
|
virtual size_t serialize(Span<uint8_t> dst) const = 0;
|
||||||
|
|
||||||
|
// Deserializes this metadata from the given bytes.
|
||||||
|
// Returns `true` on success, `false` otherwise. `out_read_size` must be assigned to the number of bytes read.
|
||||||
|
virtual bool deserialize(Span<const uint8_t> src, uint64_t &out_read_size) = 0;
|
||||||
|
|
||||||
|
virtual ICustomVoxelMetadata *duplicate() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Container for one metadata instance. It owns the data.
|
||||||
|
class VoxelMetadata {
|
||||||
|
public:
|
||||||
|
enum Type : uint8_t { //
|
||||||
|
TYPE_EMPTY = 0,
|
||||||
|
TYPE_U64 = 1,
|
||||||
|
// Reserved types.
|
||||||
|
|
||||||
|
TYPE_CUSTOM_BEGIN = 32
|
||||||
|
// Types equal or greater will implement `ICustomVoxelMetadata`.
|
||||||
|
};
|
||||||
|
|
||||||
|
static const unsigned int CUSTOM_TYPES_MAX_COUNT = 256 - TYPE_CUSTOM_BEGIN;
|
||||||
|
|
||||||
|
VoxelMetadata() {}
|
||||||
|
|
||||||
|
VoxelMetadata(VoxelMetadata &&other) {
|
||||||
|
_type = other._type;
|
||||||
|
_data = other._data;
|
||||||
|
other._type = TYPE_EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
~VoxelMetadata() {
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void operator=(VoxelMetadata &&other) {
|
||||||
|
clear();
|
||||||
|
_type = other._type;
|
||||||
|
_data = other._data;
|
||||||
|
other._type = TYPE_EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
inline Type get_type() const {
|
||||||
|
return Type(_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_u64(const uint64_t &v);
|
||||||
|
uint64_t get_u64() const;
|
||||||
|
|
||||||
|
void set_custom(uint8_t type, ICustomVoxelMetadata *custom_data);
|
||||||
|
ICustomVoxelMetadata &get_custom();
|
||||||
|
const ICustomVoxelMetadata &get_custom() const;
|
||||||
|
|
||||||
|
// Clears this metadata and makes it a duplicate of the given one.
|
||||||
|
void copy_from(const VoxelMetadata &src);
|
||||||
|
|
||||||
|
private:
|
||||||
|
union Data {
|
||||||
|
uint64_t u64_data;
|
||||||
|
ICustomVoxelMetadata *custom_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t _type = TYPE_EMPTY;
|
||||||
|
Data _data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Registry of custom metadata types, used to deserialize them from saved data.
|
||||||
|
class VoxelMetadataFactory {
|
||||||
|
public:
|
||||||
|
typedef ICustomVoxelMetadata *(*ConstructorFunc)();
|
||||||
|
|
||||||
|
static VoxelMetadataFactory &get_singleton();
|
||||||
|
|
||||||
|
VoxelMetadataFactory();
|
||||||
|
|
||||||
|
// Registers a custom metadata type.
|
||||||
|
// The `type` you choose should remain the same over time.
|
||||||
|
// It will be used in save files, so changing it could break old saves.
|
||||||
|
// `type` must be greater or equal to `VoxelMetadata::TYPE_CUSTOM_BEGIN`.
|
||||||
|
void add_constructor(uint8_t type, ConstructorFunc ctor);
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void add_constructor_by_type(uint8_t type) {
|
||||||
|
add_constructor(type, []() { //
|
||||||
|
// Doesn't compile if I directly return the newed instance
|
||||||
|
ICustomVoxelMetadata *c = ZN_NEW(T);
|
||||||
|
return c;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove_constructor(uint8_t type);
|
||||||
|
|
||||||
|
// Constructs a custom metadata type from the given type ID.
|
||||||
|
// The `type` must be greater or equal to `VoxelMetadata::TYPE_CUSTOM_BEGIN`.
|
||||||
|
// Returns `nullptr` if the type could not be constructed.
|
||||||
|
ICustomVoxelMetadata *try_construct(uint8_t type) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
FixedArray<ConstructorFunc, VoxelMetadata::CUSTOM_TYPES_MAX_COUNT> _constructors;
|
||||||
|
};
|
||||||
|
|
||||||
|
} //namespace zylann::voxel
|
||||||
|
|
||||||
|
#endif // VOXEL_METADATA_H
|
70
storage/voxel_metadata_variant.cpp
Normal file
70
storage/voxel_metadata_variant.cpp
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
#include "voxel_metadata_variant.h"
|
||||||
|
#include <core/io/marshalls.h>
|
||||||
|
|
||||||
|
namespace zylann::voxel::gd {
|
||||||
|
|
||||||
|
size_t VoxelMetadataVariant::get_serialized_size() const {
|
||||||
|
int len;
|
||||||
|
const Error err = encode_variant(data, nullptr, len, false);
|
||||||
|
ERR_FAIL_COND_V_MSG(err != OK, 0, "Error when trying to encode Variant metadata.");
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t VoxelMetadataVariant::serialize(Span<uint8_t> dst) const {
|
||||||
|
int written_length;
|
||||||
|
const Error err = encode_variant(data, dst.data(), written_length, false);
|
||||||
|
ERR_FAIL_COND_V(err != OK, 0);
|
||||||
|
return written_length;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VoxelMetadataVariant::deserialize(Span<const uint8_t> src, uint64_t &out_read_size) {
|
||||||
|
int read_length;
|
||||||
|
const Error err = decode_variant(data, src.data(), src.size(), &read_length, false);
|
||||||
|
ERR_FAIL_COND_V_MSG(err != OK, false, "Failed to deserialize block metadata");
|
||||||
|
out_read_size = read_length;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ICustomVoxelMetadata *VoxelMetadataVariant::duplicate() {
|
||||||
|
VoxelMetadataVariant *d = ZN_NEW(VoxelMetadataVariant);
|
||||||
|
d->data = data.duplicate();
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
Variant get_as_variant(const VoxelMetadata &meta) {
|
||||||
|
switch (meta.get_type()) {
|
||||||
|
case METADATA_TYPE_VARIANT: {
|
||||||
|
const VoxelMetadataVariant &mv = static_cast<const VoxelMetadataVariant &>(meta.get_custom());
|
||||||
|
return mv.data;
|
||||||
|
}
|
||||||
|
case VoxelMetadata::TYPE_EMPTY: {
|
||||||
|
return Variant();
|
||||||
|
}
|
||||||
|
case VoxelMetadata::TYPE_U64: {
|
||||||
|
return Variant(int64_t(meta.get_u64()));
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
ZN_PRINT_ERROR("Unknown VoxelMetadata type");
|
||||||
|
return Variant();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_as_variant(VoxelMetadata &meta, Variant v) {
|
||||||
|
if (v.get_type() == Variant::NIL) {
|
||||||
|
meta.clear();
|
||||||
|
} else {
|
||||||
|
if (meta.get_type() == METADATA_TYPE_VARIANT) {
|
||||||
|
VoxelMetadataVariant &mv = static_cast<VoxelMetadataVariant &>(meta.get_custom());
|
||||||
|
mv.data = v;
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
VoxelMetadataVariant *mv = ZN_NEW(VoxelMetadataVariant);
|
||||||
|
mv->data = v;
|
||||||
|
meta.set_custom(METADATA_TYPE_VARIANT, mv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace zylann::voxel::gd
|
31
storage/voxel_metadata_variant.h
Normal file
31
storage/voxel_metadata_variant.h
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#ifndef VOXEL_METADATA_VARIANT_H
|
||||||
|
#define VOXEL_METADATA_VARIANT_H
|
||||||
|
|
||||||
|
#include "voxel_metadata.h"
|
||||||
|
#include <core/variant/variant.h>
|
||||||
|
|
||||||
|
namespace zylann::voxel::gd {
|
||||||
|
|
||||||
|
// TODO Not sure if that should be a custom type. Custom types are supposed to be specific to a game?
|
||||||
|
enum GodotMetadataTypes { //
|
||||||
|
METADATA_TYPE_VARIANT = VoxelMetadata::TYPE_CUSTOM_BEGIN
|
||||||
|
};
|
||||||
|
|
||||||
|
// Custom metadata holding a Godot Variant (basically, anything recognized by Godot Engine).
|
||||||
|
// Serializability depends on the same rules as Godot's `encode_variant`: no invalid objects, no cycles.
|
||||||
|
class VoxelMetadataVariant : public ICustomVoxelMetadata {
|
||||||
|
public:
|
||||||
|
Variant data;
|
||||||
|
|
||||||
|
size_t get_serialized_size() const override;
|
||||||
|
size_t serialize(Span<uint8_t> dst) const override;
|
||||||
|
bool deserialize(Span<const uint8_t> src, uint64_t &out_read_size) override;
|
||||||
|
ICustomVoxelMetadata *duplicate() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
Variant get_as_variant(const VoxelMetadata &meta);
|
||||||
|
void set_as_variant(VoxelMetadata &meta, Variant v);
|
||||||
|
|
||||||
|
} // namespace zylann::voxel::gd
|
||||||
|
|
||||||
|
#endif // VOXEL_METADATA_VARIANT_H
|
@ -5,9 +5,14 @@
|
|||||||
#include "../util/math/vector3i.h"
|
#include "../util/math/vector3i.h"
|
||||||
#include "../util/profiling.h"
|
#include "../util/profiling.h"
|
||||||
#include "../util/serialization.h"
|
#include "../util/serialization.h"
|
||||||
|
#include "../util/string_funcs.h"
|
||||||
#include "compressed_data.h"
|
#include "compressed_data.h"
|
||||||
|
|
||||||
|
#ifdef ZN_GODOT
|
||||||
|
#include "../storage/voxel_metadata_variant.h"
|
||||||
#include <core/io/marshalls.h> // For `encode_variant`
|
#include <core/io/marshalls.h> // For `encode_variant`
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <core/io/stream_peer.h>
|
#include <core/io/stream_peer.h>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
|
||||||
@ -24,11 +29,32 @@ thread_local std::vector<uint8_t> tls_data;
|
|||||||
thread_local std::vector<uint8_t> tls_compressed_data;
|
thread_local std::vector<uint8_t> tls_compressed_data;
|
||||||
thread_local std::vector<uint8_t> tls_metadata_tmp;
|
thread_local std::vector<uint8_t> tls_metadata_tmp;
|
||||||
|
|
||||||
|
size_t get_metadata_size_in_bytes(const VoxelMetadata &meta) {
|
||||||
|
size_t size = 1; // Type
|
||||||
|
switch (meta.get_type()) {
|
||||||
|
case VoxelMetadata::TYPE_EMPTY:
|
||||||
|
break;
|
||||||
|
case VoxelMetadata::TYPE_U64:
|
||||||
|
size += sizeof(uint64_t);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (meta.get_type() >= VoxelMetadata::TYPE_CUSTOM_BEGIN) {
|
||||||
|
const ICustomVoxelMetadata &custom = meta.get_custom();
|
||||||
|
size += custom.get_serialized_size();
|
||||||
|
} else {
|
||||||
|
ZN_PRINT_ERROR("Unknown metadata type");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
size_t get_metadata_size_in_bytes(const VoxelBufferInternal &buffer) {
|
size_t get_metadata_size_in_bytes(const VoxelBufferInternal &buffer) {
|
||||||
size_t size = 0;
|
size_t size = 0;
|
||||||
|
|
||||||
const FlatMap<Vector3i, Variant> &voxel_metadata = buffer.get_voxel_metadata();
|
const FlatMapMoveOnly<Vector3i, VoxelMetadata> &voxel_metadata = buffer.get_voxel_metadata();
|
||||||
for (FlatMap<Vector3i, Variant>::ConstIterator it = voxel_metadata.begin(); it != voxel_metadata.end(); ++it) {
|
for (FlatMapMoveOnly<Vector3i, VoxelMetadata>::ConstIterator it = voxel_metadata.begin();
|
||||||
|
it != voxel_metadata.end(); ++it) {
|
||||||
const Vector3i pos = it->key;
|
const Vector3i pos = it->key;
|
||||||
|
|
||||||
ERR_FAIL_COND_V_MSG(pos.x < 0 || static_cast<uint32_t>(pos.x) >= VoxelBufferInternal::MAX_SIZE, 0,
|
ERR_FAIL_COND_V_MSG(pos.x < 0 || static_cast<uint32_t>(pos.x) >= VoxelBufferInternal::MAX_SIZE, 0,
|
||||||
@ -39,23 +65,17 @@ size_t get_metadata_size_in_bytes(const VoxelBufferInternal &buffer) {
|
|||||||
"Invalid voxel metadata Z position");
|
"Invalid voxel metadata Z position");
|
||||||
|
|
||||||
size += 3 * sizeof(uint16_t); // Positions are stored as 3 unsigned shorts
|
size += 3 * sizeof(uint16_t); // Positions are stored as 3 unsigned shorts
|
||||||
|
size += get_metadata_size_in_bytes(it->value);
|
||||||
int len;
|
|
||||||
const Error err = encode_variant(it->value, nullptr, len, false);
|
|
||||||
ERR_FAIL_COND_V_MSG(err != OK, 0, "Error when trying to encode voxel metadata.");
|
|
||||||
size += len;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no metadata is found at all, nothing is serialized, not even null.
|
// If no metadata is found at all, nothing is serialized, not even null.
|
||||||
// It spares 24 bytes (40 if real_t == double),
|
// It spares 24 bytes (40 if real_t == double),
|
||||||
// and is backward compatible with saves made before introduction of metadata.
|
// and is backward compatible with saves made before introduction of metadata.
|
||||||
|
|
||||||
if (size != 0 || buffer.get_block_metadata() != Variant()) {
|
const VoxelMetadata &block_meta = buffer.get_block_metadata();
|
||||||
int len;
|
|
||||||
// Get size first by invoking the function is "length mode"
|
if (size != 0 || block_meta.get_type() != VoxelMetadata::TYPE_EMPTY) {
|
||||||
const Error err = encode_variant(buffer.get_block_metadata(), nullptr, len, false);
|
size += get_metadata_size_in_bytes(block_meta);
|
||||||
ERR_FAIL_COND_V_MSG(err != OK, 0, "Error when trying to encode block metadata.");
|
|
||||||
size += len;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return size;
|
return size;
|
||||||
@ -74,43 +94,51 @@ inline T read(uint8_t *&src) {
|
|||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void serialize_metadata(const VoxelMetadata &meta, MemoryWriterExistingBuffer &mw) {
|
||||||
|
const uint8_t type = meta.get_type();
|
||||||
|
switch (type) {
|
||||||
|
case VoxelMetadata::TYPE_EMPTY:
|
||||||
|
mw.store_8(type);
|
||||||
|
break;
|
||||||
|
case VoxelMetadata::TYPE_U64:
|
||||||
|
mw.store_8(type);
|
||||||
|
mw.store_64(meta.get_u64());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (type >= VoxelMetadata::TYPE_CUSTOM_BEGIN) {
|
||||||
|
mw.store_8(type);
|
||||||
|
const size_t written_size = meta.get_custom().serialize(mw.data.data.sub(mw.data.pos));
|
||||||
|
ZN_ASSERT(mw.data.pos + written_size <= mw.data.data.size());
|
||||||
|
mw.data.pos += written_size;
|
||||||
|
} else {
|
||||||
|
ZN_PRINT_ERROR("Unknown metadata type");
|
||||||
|
mw.store_8(VoxelMetadata::TYPE_EMPTY);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The target buffer MUST have correct size. Recoverable errors must have been checked before.
|
// The target buffer MUST have correct size. Recoverable errors must have been checked before.
|
||||||
void serialize_metadata(Span<uint8_t> p_dst, const VoxelBufferInternal &buffer) {
|
void serialize_metadata(Span<uint8_t> p_dst, const VoxelBufferInternal &buffer) {
|
||||||
uint8_t *dst = p_dst.data();
|
ByteSpanWithPosition bs(p_dst, 0);
|
||||||
|
MemoryWriterExistingBuffer mw(bs, ENDIANESS_LITTLE_ENDIAN);
|
||||||
|
|
||||||
{
|
const VoxelMetadata &block_meta = buffer.get_block_metadata();
|
||||||
int written_length;
|
serialize_metadata(block_meta, mw);
|
||||||
encode_variant(buffer.get_block_metadata(), dst, written_length, false);
|
|
||||||
dst += written_length;
|
|
||||||
|
|
||||||
// I chose to cast this way to fix a GCC warning.
|
const FlatMapMoveOnly<Vector3i, VoxelMetadata> &voxel_metadata = buffer.get_voxel_metadata();
|
||||||
// If dst - p_dst is negative (which is wrong), it will wrap and cause a justified assertion failure
|
for (FlatMapMoveOnly<Vector3i, VoxelMetadata>::ConstIterator it = voxel_metadata.begin();
|
||||||
CRASH_COND_MSG(
|
it != voxel_metadata.end(); ++it) {
|
||||||
static_cast<size_t>(dst - p_dst.data()) > p_dst.size(), "Wrote block metadata out of expected bounds");
|
|
||||||
}
|
|
||||||
|
|
||||||
const FlatMap<Vector3i, Variant> &voxel_metadata = buffer.get_voxel_metadata();
|
|
||||||
for (FlatMap<Vector3i, Variant>::ConstIterator it = voxel_metadata.begin(); it != voxel_metadata.end(); ++it) {
|
|
||||||
// Serializing key as ushort because it's more than enough for a 3D dense array
|
// Serializing key as ushort because it's more than enough for a 3D dense array
|
||||||
static_assert(VoxelBufferInternal::MAX_SIZE <= std::numeric_limits<uint16_t>::max(),
|
static_assert(VoxelBufferInternal::MAX_SIZE <= std::numeric_limits<uint16_t>::max(),
|
||||||
"Maximum size exceeds serialization support");
|
"Maximum size exceeds serialization support");
|
||||||
const Vector3i pos = it->key;
|
const Vector3i pos = it->key;
|
||||||
write<uint16_t>(dst, pos.x);
|
mw.store_16(pos.x);
|
||||||
write<uint16_t>(dst, pos.y);
|
mw.store_16(pos.y);
|
||||||
write<uint16_t>(dst, pos.z);
|
mw.store_16(pos.z);
|
||||||
|
|
||||||
int written_length;
|
serialize_metadata(it->value, mw);
|
||||||
const Error err = encode_variant(it->value, dst, written_length, false);
|
|
||||||
CRASH_COND_MSG(err != OK, "Error when trying to encode voxel metadata.");
|
|
||||||
dst += written_length;
|
|
||||||
|
|
||||||
CRASH_COND_MSG(
|
|
||||||
static_cast<size_t>(dst - p_dst.data()) > p_dst.size(), "Wrote voxel metadata out of expected bounds");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CRASH_COND_MSG(static_cast<size_t>(dst - p_dst.data()) != p_dst.size(),
|
|
||||||
String("Written metadata doesn't match expected count (expected {0}, got {1})")
|
|
||||||
.format(varray(ZN_SIZE_T_TO_VARIANT(p_dst.size()), (int)(dst - p_dst.data()))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
@ -123,52 +151,73 @@ struct ClearOnExit {
|
|||||||
|
|
||||||
//#define CLEAR_ON_EXIT(container) ClearOnExit<decltype(container)> clear_on_exit_##__LINE__;
|
//#define CLEAR_ON_EXIT(container) ClearOnExit<decltype(container)> clear_on_exit_##__LINE__;
|
||||||
|
|
||||||
bool deserialize_metadata(uint8_t *p_src, VoxelBufferInternal &buffer, const size_t metadata_size) {
|
static bool deserialize_metadata(VoxelMetadata &meta, MemoryReader &mr) {
|
||||||
uint8_t *src = p_src;
|
const uint8_t type = mr.get_8();
|
||||||
size_t remaining_length = metadata_size;
|
switch (type) {
|
||||||
|
case VoxelMetadata::TYPE_EMPTY:
|
||||||
|
meta.clear();
|
||||||
|
return true;
|
||||||
|
|
||||||
{
|
case VoxelMetadata::TYPE_U64:
|
||||||
Variant block_metadata;
|
meta.set_u64(mr.get_64());
|
||||||
int read_length;
|
return true;
|
||||||
const Error err = decode_variant(block_metadata, src, remaining_length, &read_length, false);
|
|
||||||
ERR_FAIL_COND_V_MSG(err != OK, false, "Failed to deserialize block metadata");
|
default:
|
||||||
remaining_length -= read_length;
|
if (type >= VoxelMetadata::TYPE_CUSTOM_BEGIN) {
|
||||||
src += read_length;
|
ICustomVoxelMetadata *custom = VoxelMetadataFactory::get_singleton().try_construct(type);
|
||||||
CRASH_COND_MSG(remaining_length > metadata_size, "Block metadata size underflow");
|
ZN_ASSERT_RETURN_V_MSG(
|
||||||
buffer.set_block_metadata(block_metadata);
|
custom != nullptr, false, format("Could not deserialize custom metadata with type {}", type));
|
||||||
|
|
||||||
|
// Store in a temporary container so it auto-deletes in case of error
|
||||||
|
VoxelMetadata temp;
|
||||||
|
temp.set_custom(type, custom);
|
||||||
|
|
||||||
|
size_t read_size = 0;
|
||||||
|
ZN_ASSERT_RETURN_V(custom->deserialize(mr.data.sub(mr.pos), read_size), false);
|
||||||
|
ZN_ASSERT_RETURN_V(mr.pos + read_size <= mr.data.size(), false);
|
||||||
|
mr.pos += read_size;
|
||||||
|
|
||||||
|
meta = std::move(temp);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
ZN_PRINT_ERROR("Unknown metadata type");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
typedef FlatMap<Vector3i, Variant>::Pair Pair;
|
bool deserialize_metadata(Span<const uint8_t> p_src, VoxelBufferInternal &buffer) {
|
||||||
|
MemoryReader mr(p_src, ENDIANESS_LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
ZN_ASSERT_RETURN_V(deserialize_metadata(buffer.get_block_metadata(), mr), false);
|
||||||
|
|
||||||
|
typedef FlatMapMoveOnly<Vector3i, VoxelMetadata>::Pair Pair;
|
||||||
static thread_local std::vector<Pair> tls_pairs;
|
static thread_local std::vector<Pair> tls_pairs;
|
||||||
// Clear when exiting scope (including cases of error) so we don't store dangling Variants
|
// Clear when exiting scope (including cases of error) so we don't store dangling Variants
|
||||||
ClearOnExit<std::vector<Pair>> clear_tls_pairs{ tls_pairs };
|
ClearOnExit<std::vector<Pair>> clear_tls_pairs{ tls_pairs };
|
||||||
|
|
||||||
while (remaining_length > 0) {
|
while (mr.pos < mr.data.size()) {
|
||||||
Vector3i pos;
|
Vector3i pos;
|
||||||
pos.x = read<uint16_t>(src);
|
pos.x = mr.get_16();
|
||||||
pos.y = read<uint16_t>(src);
|
pos.y = mr.get_16();
|
||||||
pos.z = read<uint16_t>(src);
|
pos.z = mr.get_16();
|
||||||
remaining_length -= 3 * sizeof(uint16_t);
|
|
||||||
|
|
||||||
ERR_CONTINUE_MSG(!buffer.is_position_valid(pos),
|
ZN_ASSERT_CONTINUE_MSG(buffer.is_position_valid(pos),
|
||||||
String("Invalid voxel metadata position {0} for buffer of size {1}")
|
format("Invalid voxel metadata position {} for buffer of size {}", pos, buffer.get_size()));
|
||||||
.format(varray(pos, buffer.get_size())));
|
|
||||||
|
|
||||||
Variant metadata;
|
//VoxelMetadata &vmeta = buffer.get_or_create_voxel_metadata(pos);
|
||||||
int read_length;
|
tls_pairs.resize(tls_pairs.size() + 1);
|
||||||
const Error err = decode_variant(metadata, src, remaining_length, &read_length, false);
|
Pair &p = tls_pairs.back();
|
||||||
ERR_FAIL_COND_V_MSG(err != OK, false, "Failed to deserialize block metadata");
|
p.key = pos;
|
||||||
remaining_length -= read_length;
|
ZN_ASSERT_RETURN_V_MSG(
|
||||||
src += read_length;
|
deserialize_metadata(p.value, mr), false, format("Failed to deserialize voxel metadata {}", pos));
|
||||||
CRASH_COND_MSG(remaining_length > metadata_size, "Block metadata size underflow");
|
|
||||||
|
|
||||||
tls_pairs.push_back(Pair{ pos, metadata });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set all metadata at once, FlatMap is faster to initialize this way
|
// Set all metadata at once, FlatMap is faster to initialize this way
|
||||||
buffer.clear_and_set_voxel_metadata(to_span(tls_pairs));
|
buffer.clear_and_set_voxel_metadata(to_span(tls_pairs));
|
||||||
|
|
||||||
CRASH_COND_MSG(remaining_length != 0, "Did not read expected size");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -300,6 +349,108 @@ SerializeResult serialize(const VoxelBufferInternal &voxel_buffer) {
|
|||||||
return SerializeResult(dst_data, true);
|
return SerializeResult(dst_data, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace legacy {
|
||||||
|
|
||||||
|
bool migrate_v3_to_v4(Span<const uint8_t> p_data, std::vector<uint8_t> &dst) {
|
||||||
|
// In v3, metadata was always a Godot Variant. In v4, metadata uses an independent format.
|
||||||
|
|
||||||
|
#ifndef ZN_GODOT
|
||||||
|
ZN_PRINT_ERROR("Cannot migrate block from v3 to v4, Godot Engine is required");
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
|
||||||
|
// Constants used at the time of this version
|
||||||
|
const unsigned int channel_count = 8;
|
||||||
|
const unsigned int no_compression = 0;
|
||||||
|
const unsigned int uniform_compression = 1;
|
||||||
|
|
||||||
|
MemoryReader mr(p_data, ENDIANESS_LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
const uint8_t rv = mr.get_8(); // version
|
||||||
|
ZN_ASSERT(rv == 3);
|
||||||
|
|
||||||
|
const uint16_t size_x = mr.get_16(); // size_x
|
||||||
|
const uint16_t size_y = mr.get_16(); // size_y
|
||||||
|
const uint16_t size_z = mr.get_16(); // size_z
|
||||||
|
const unsigned int volume = size_x * size_y * size_z;
|
||||||
|
|
||||||
|
for (unsigned int channel_index = 0; channel_index < channel_count; ++channel_index) {
|
||||||
|
const uint8_t fmt = mr.get_8();
|
||||||
|
|
||||||
|
const uint8_t compression_value = fmt & 0xf;
|
||||||
|
const uint8_t depth_value = (fmt >> 4) & 0xf;
|
||||||
|
|
||||||
|
ZN_ASSERT_RETURN_V(compression_value < 2, false);
|
||||||
|
ZN_ASSERT_RETURN_V(depth_value < 4, false);
|
||||||
|
|
||||||
|
if (compression_value == no_compression) {
|
||||||
|
mr.pos += volume << depth_value;
|
||||||
|
|
||||||
|
} else if (compression_value == uniform_compression) {
|
||||||
|
mr.pos += size_t(1) << depth_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ZN_ASSERT(mr.pos <= mr.data.size());
|
||||||
|
|
||||||
|
// Copy everything up to beginning of metadata
|
||||||
|
dst.resize(mr.pos);
|
||||||
|
memcpy(dst.data(), p_data.data(), mr.pos);
|
||||||
|
// Set version
|
||||||
|
dst[0] = 4;
|
||||||
|
|
||||||
|
// Convert metadata
|
||||||
|
|
||||||
|
const size_t total_metadata_size = mr.data.size() - mr.pos;
|
||||||
|
|
||||||
|
if (total_metadata_size > 0) {
|
||||||
|
MemoryWriter mw(dst, ENDIANESS_LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
struct L {
|
||||||
|
static bool convert_metadata_item(MemoryReader &mr, MemoryWriter &mw) {
|
||||||
|
// Read Variant
|
||||||
|
Variant src_meta;
|
||||||
|
int read_length;
|
||||||
|
const Error err =
|
||||||
|
decode_variant(src_meta, &mr.data[mr.pos], mr.data.size() - mr.pos, &read_length, false);
|
||||||
|
ZN_ASSERT_RETURN_V_MSG(err == OK, false, "Failed to deserialize v3 Variant metadata");
|
||||||
|
mr.pos += read_length;
|
||||||
|
ZN_ASSERT(mr.pos <= mr.data.size());
|
||||||
|
|
||||||
|
// Write v4 equivalent
|
||||||
|
VoxelMetadata dst_meta;
|
||||||
|
gd::VoxelMetadataVariant *custom = ZN_NEW(gd::VoxelMetadataVariant);
|
||||||
|
custom->data = src_meta;
|
||||||
|
dst_meta.set_custom(gd::METADATA_TYPE_VARIANT, custom);
|
||||||
|
mw.store_8(dst_meta.get_type());
|
||||||
|
const size_t ss = custom->get_serialized_size();
|
||||||
|
const size_t prev_size = mw.data.size();
|
||||||
|
mw.data.resize(mw.data.size() + ss);
|
||||||
|
const size_t written_size = custom->serialize(Span<uint8_t>(mw.data.data() + prev_size, ss));
|
||||||
|
mw.data.resize(prev_size + written_size);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ZN_ASSERT_RETURN_V(L::convert_metadata_item(mr, mw), false);
|
||||||
|
|
||||||
|
while (mr.pos < mr.data.size()) {
|
||||||
|
const uint16_t pos_x = mr.get_16();
|
||||||
|
const uint16_t pos_y = mr.get_16();
|
||||||
|
const uint16_t pos_z = mr.get_16();
|
||||||
|
|
||||||
|
mw.store_16(pos_x);
|
||||||
|
mw.store_16(pos_y);
|
||||||
|
mw.store_16(pos_z);
|
||||||
|
|
||||||
|
ZN_ASSERT_RETURN_V(L::convert_metadata_item(mr, mw), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool migrate_v2_to_v3(Span<const uint8_t> p_data, std::vector<uint8_t> &dst) {
|
bool migrate_v2_to_v3(Span<const uint8_t> p_data, std::vector<uint8_t> &dst) {
|
||||||
// In v2, SDF data was using a legacy arbitrary formula to encode fixed-point numbers.
|
// In v2, SDF data was using a legacy arbitrary formula to encode fixed-point numbers.
|
||||||
// In v3, it now uses inorm8 and inorm16.
|
// In v3, it now uses inorm8 and inorm16.
|
||||||
@ -317,7 +468,7 @@ bool migrate_v2_to_v3(Span<const uint8_t> p_data, std::vector<uint8_t> &dst) {
|
|||||||
MemoryReader mr(p_data, ENDIANESS_LITTLE_ENDIAN);
|
MemoryReader mr(p_data, ENDIANESS_LITTLE_ENDIAN);
|
||||||
|
|
||||||
const uint8_t rv = mr.get_8(); // version
|
const uint8_t rv = mr.get_8(); // version
|
||||||
CRASH_COND(rv != 2);
|
ZN_ASSERT(rv == 2);
|
||||||
|
|
||||||
dst[0] = 3;
|
dst[0] = 3;
|
||||||
|
|
||||||
@ -344,12 +495,12 @@ bool migrate_v2_to_v3(Span<const uint8_t> p_data, std::vector<uint8_t> &dst) {
|
|||||||
switch (depth_value) {
|
switch (depth_value) {
|
||||||
case 0:
|
case 0:
|
||||||
for (unsigned int i = 0; i < volume; ++i) {
|
for (unsigned int i = 0; i < volume; ++i) {
|
||||||
mw.store_8(snorm_to_s8(legacy::u8_to_snorm(mr.get_8())));
|
mw.store_8(snorm_to_s8(voxel::legacy::u8_to_snorm(mr.get_8())));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
for (unsigned int i = 0; i < volume; ++i) {
|
for (unsigned int i = 0; i < volume; ++i) {
|
||||||
mw.store_16(snorm_to_s16(legacy::u16_to_snorm(mr.get_16())));
|
mw.store_16(snorm_to_s16(voxel::legacy::u16_to_snorm(mr.get_16())));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
@ -361,10 +512,10 @@ bool migrate_v2_to_v3(Span<const uint8_t> p_data, std::vector<uint8_t> &dst) {
|
|||||||
} else if (compression_value == uniform_compression) {
|
} else if (compression_value == uniform_compression) {
|
||||||
switch (depth_value) {
|
switch (depth_value) {
|
||||||
case 0:
|
case 0:
|
||||||
mw.store_8(snorm_to_s8(legacy::u8_to_snorm(mr.get_8())));
|
mw.store_8(snorm_to_s8(voxel::legacy::u8_to_snorm(mr.get_8())));
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
mw.store_16(snorm_to_s16(legacy::u16_to_snorm(mr.get_16())));
|
mw.store_16(snorm_to_s16(voxel::legacy::u16_to_snorm(mr.get_16())));
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
case 3:
|
case 3:
|
||||||
@ -387,6 +538,8 @@ bool migrate_v2_to_v3(Span<const uint8_t> p_data, std::vector<uint8_t> &dst) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace legacy
|
||||||
|
|
||||||
bool deserialize(Span<const uint8_t> p_data, VoxelBufferInternal &out_voxel_buffer) {
|
bool deserialize(Span<const uint8_t> p_data, VoxelBufferInternal &out_voxel_buffer) {
|
||||||
ZN_PROFILE_SCOPE();
|
ZN_PROFILE_SCOPE();
|
||||||
|
|
||||||
@ -400,13 +553,21 @@ bool deserialize(Span<const uint8_t> p_data, VoxelBufferInternal &out_voxel_buff
|
|||||||
|
|
||||||
const uint8_t format_version = f.get_8();
|
const uint8_t format_version = f.get_8();
|
||||||
|
|
||||||
if (format_version == 2) {
|
switch (format_version) {
|
||||||
std::vector<uint8_t> migrated_data;
|
case 2: {
|
||||||
ERR_FAIL_COND_V(!migrate_v2_to_v3(p_data, migrated_data), false);
|
std::vector<uint8_t> migrated_data;
|
||||||
return deserialize(to_span_const(migrated_data), out_voxel_buffer);
|
ERR_FAIL_COND_V(!legacy::migrate_v2_to_v3(p_data, migrated_data), false);
|
||||||
|
return deserialize(to_span(migrated_data), out_voxel_buffer);
|
||||||
|
} break;
|
||||||
|
|
||||||
} else {
|
case 3: {
|
||||||
ERR_FAIL_COND_V(format_version != BLOCK_FORMAT_VERSION, false);
|
std::vector<uint8_t> migrated_data;
|
||||||
|
ERR_FAIL_COND_V(!legacy::migrate_v3_to_v4(p_data, migrated_data), false);
|
||||||
|
return deserialize(to_span(migrated_data), out_voxel_buffer);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
ERR_FAIL_COND_V(format_version != BLOCK_FORMAT_VERSION, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const unsigned int size_x = f.get_16();
|
const unsigned int size_x = f.get_16();
|
||||||
@ -475,7 +636,7 @@ bool deserialize(Span<const uint8_t> p_data, VoxelBufferInternal &out_voxel_buff
|
|||||||
ERR_FAIL_COND_V(f.get_position() + metadata_size > p_data.size(), false);
|
ERR_FAIL_COND_V(f.get_position() + metadata_size > p_data.size(), false);
|
||||||
metadata_tmp.resize(metadata_size);
|
metadata_tmp.resize(metadata_size);
|
||||||
f.get_buffer(to_span(metadata_tmp));
|
f.get_buffer(to_span(metadata_tmp));
|
||||||
deserialize_metadata(metadata_tmp.data(), out_voxel_buffer, metadata_tmp.size());
|
deserialize_metadata(to_span(metadata_tmp), out_voxel_buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Failure at this indicates file corruption
|
// Failure at this indicates file corruption
|
||||||
|
209
tests/tests.cpp
209
tests/tests.cpp
@ -5,6 +5,7 @@
|
|||||||
#include "../meshers/blocky/voxel_blocky_library.h"
|
#include "../meshers/blocky/voxel_blocky_library.h"
|
||||||
#include "../storage/voxel_buffer_gd.h"
|
#include "../storage/voxel_buffer_gd.h"
|
||||||
#include "../storage/voxel_data_map.h"
|
#include "../storage/voxel_data_map.h"
|
||||||
|
#include "../storage/voxel_metadata_variant.h"
|
||||||
#include "../streams/instance_data.h"
|
#include "../streams/instance_data.h"
|
||||||
#include "../streams/region/region_file.h"
|
#include "../streams/region/region_file.h"
|
||||||
#include "../streams/region/voxel_stream_region_files.h"
|
#include "../streams/region/voxel_stream_region_files.h"
|
||||||
@ -1485,10 +1486,11 @@ void test_flat_map() {
|
|||||||
ZYLANN_TEST_ASSERT_V(sorted_pairs.size() == map.size(), false);
|
ZYLANN_TEST_ASSERT_V(sorted_pairs.size() == map.size(), false);
|
||||||
for (size_t i = 0; i < sorted_pairs.size(); ++i) {
|
for (size_t i = 0; i < sorted_pairs.size(); ++i) {
|
||||||
const Pair expected_pair = sorted_pairs[i];
|
const Pair expected_pair = sorted_pairs[i];
|
||||||
Value value;
|
|
||||||
ZYLANN_TEST_ASSERT_V(map.has(expected_pair.key), false);
|
ZYLANN_TEST_ASSERT_V(map.has(expected_pair.key), false);
|
||||||
ZYLANN_TEST_ASSERT_V(map.find(expected_pair.key, value), false);
|
ZYLANN_TEST_ASSERT_V(map.find(expected_pair.key) != nullptr, false);
|
||||||
ZYLANN_TEST_ASSERT_V(value == expected_pair.value, false);
|
const Value *value = map.find(expected_pair.key);
|
||||||
|
ZYLANN_TEST_ASSERT_V(value != nullptr, false);
|
||||||
|
ZYLANN_TEST_ASSERT_V(*value == expected_pair.value, false);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -1829,6 +1831,205 @@ void test_expression_parser() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CustomMetadataTest : public ICustomVoxelMetadata {
|
||||||
|
public:
|
||||||
|
static const uint8_t ID = VoxelMetadata::TYPE_CUSTOM_BEGIN + 10;
|
||||||
|
|
||||||
|
uint8_t a;
|
||||||
|
uint8_t b;
|
||||||
|
uint8_t c;
|
||||||
|
|
||||||
|
size_t get_serialized_size() const override {
|
||||||
|
// Note, `sizeof(CustomMetadataTest)` gives 16 here. Probably because of vtable
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t serialize(Span<uint8_t> dst) const override {
|
||||||
|
dst[0] = a;
|
||||||
|
dst[1] = b;
|
||||||
|
dst[2] = c;
|
||||||
|
return get_serialized_size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool deserialize(Span<const uint8_t> src, uint64_t &out_read_size) override {
|
||||||
|
a = src[0];
|
||||||
|
b = src[1];
|
||||||
|
c = src[2];
|
||||||
|
out_read_size = get_serialized_size();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ICustomVoxelMetadata *duplicate() {
|
||||||
|
CustomMetadataTest *d = ZN_NEW(CustomMetadataTest);
|
||||||
|
*d = *this;
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const CustomMetadataTest &other) const {
|
||||||
|
return a == other.a && b == other.b && c == other.c;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void test_voxel_buffer_metadata() {
|
||||||
|
// Basic get and set
|
||||||
|
{
|
||||||
|
VoxelBufferInternal vb;
|
||||||
|
vb.create(10, 10, 10);
|
||||||
|
|
||||||
|
VoxelMetadata *meta = vb.get_or_create_voxel_metadata(Vector3i(1, 2, 3));
|
||||||
|
ZYLANN_TEST_ASSERT(meta != nullptr);
|
||||||
|
meta->set_u64(1234567890);
|
||||||
|
|
||||||
|
const VoxelMetadata *meta2 = vb.get_voxel_metadata(Vector3i(1, 2, 3));
|
||||||
|
ZYLANN_TEST_ASSERT(meta2 != nullptr);
|
||||||
|
ZYLANN_TEST_ASSERT(meta2->get_type() == meta->get_type());
|
||||||
|
ZYLANN_TEST_ASSERT(meta2->get_u64() == meta->get_u64());
|
||||||
|
}
|
||||||
|
// Serialization
|
||||||
|
{
|
||||||
|
VoxelBufferInternal vb;
|
||||||
|
vb.create(10, 10, 10);
|
||||||
|
|
||||||
|
{
|
||||||
|
VoxelMetadata *meta0 = vb.get_or_create_voxel_metadata(Vector3i(1, 2, 3));
|
||||||
|
ZYLANN_TEST_ASSERT(meta0 != nullptr);
|
||||||
|
meta0->set_u64(1234567890);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
VoxelMetadata *meta1 = vb.get_or_create_voxel_metadata(Vector3i(4, 5, 6));
|
||||||
|
ZYLANN_TEST_ASSERT(meta1 != nullptr);
|
||||||
|
meta1->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RemoveTypeOnExit {
|
||||||
|
~RemoveTypeOnExit() {
|
||||||
|
VoxelMetadataFactory::get_singleton().remove_constructor(CustomMetadataTest::ID);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
RemoveTypeOnExit rmtype;
|
||||||
|
VoxelMetadataFactory::get_singleton().add_constructor_by_type<CustomMetadataTest>(CustomMetadataTest::ID);
|
||||||
|
{
|
||||||
|
VoxelMetadata *meta2 = vb.get_or_create_voxel_metadata(Vector3i(7, 8, 9));
|
||||||
|
ZYLANN_TEST_ASSERT(meta2 != nullptr);
|
||||||
|
CustomMetadataTest *custom = ZN_NEW(CustomMetadataTest);
|
||||||
|
custom->a = 10;
|
||||||
|
custom->b = 20;
|
||||||
|
custom->c = 30;
|
||||||
|
meta2->set_custom(CustomMetadataTest::ID, custom);
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockSerializer::SerializeResult sresult = BlockSerializer::serialize(vb);
|
||||||
|
ZYLANN_TEST_ASSERT(sresult.success);
|
||||||
|
std::vector<uint8_t> bytes = sresult.data;
|
||||||
|
|
||||||
|
VoxelBufferInternal rvb;
|
||||||
|
ZYLANN_TEST_ASSERT(BlockSerializer::deserialize(to_span(bytes), rvb));
|
||||||
|
|
||||||
|
const FlatMapMoveOnly<Vector3i, VoxelMetadata> &vb_meta_map = vb.get_voxel_metadata();
|
||||||
|
const FlatMapMoveOnly<Vector3i, VoxelMetadata> &rvb_meta_map = rvb.get_voxel_metadata();
|
||||||
|
|
||||||
|
ZYLANN_TEST_ASSERT(vb_meta_map.size() == rvb_meta_map.size());
|
||||||
|
|
||||||
|
for (auto it = vb_meta_map.begin(); it != vb_meta_map.end(); ++it) {
|
||||||
|
const VoxelMetadata &meta = it->value;
|
||||||
|
const VoxelMetadata *rmeta = rvb_meta_map.find(it->key);
|
||||||
|
|
||||||
|
ZYLANN_TEST_ASSERT(rmeta != nullptr);
|
||||||
|
ZYLANN_TEST_ASSERT(rmeta->get_type() == meta.get_type());
|
||||||
|
|
||||||
|
switch (meta.get_type()) {
|
||||||
|
case VoxelMetadata::TYPE_EMPTY:
|
||||||
|
break;
|
||||||
|
case VoxelMetadata::TYPE_U64:
|
||||||
|
ZYLANN_TEST_ASSERT(meta.get_u64() == rmeta->get_u64());
|
||||||
|
break;
|
||||||
|
case CustomMetadataTest::ID: {
|
||||||
|
const CustomMetadataTest &custom = static_cast<const CustomMetadataTest &>(meta.get_custom());
|
||||||
|
const CustomMetadataTest &rcustom = static_cast<const CustomMetadataTest &>(rmeta->get_custom());
|
||||||
|
ZYLANN_TEST_ASSERT(custom == rcustom);
|
||||||
|
} break;
|
||||||
|
default:
|
||||||
|
ZYLANN_TEST_ASSERT(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_voxel_buffer_metadata_gd() {
|
||||||
|
// Basic get and set (Godot)
|
||||||
|
{
|
||||||
|
Ref<gd::VoxelBuffer> vb;
|
||||||
|
vb.instantiate();
|
||||||
|
vb->create(10, 10, 10);
|
||||||
|
|
||||||
|
Array meta;
|
||||||
|
meta.push_back("Hello");
|
||||||
|
meta.push_back("World");
|
||||||
|
meta.push_back(42);
|
||||||
|
|
||||||
|
vb->set_voxel_metadata(Vector3i(1, 2, 3), meta);
|
||||||
|
|
||||||
|
Array read_meta = vb->get_voxel_metadata(Vector3i(1, 2, 3));
|
||||||
|
ZYLANN_TEST_ASSERT(read_meta.size() == meta.size());
|
||||||
|
ZYLANN_TEST_ASSERT(read_meta == meta);
|
||||||
|
}
|
||||||
|
// Serialization (Godot)
|
||||||
|
{
|
||||||
|
Ref<gd::VoxelBuffer> vb;
|
||||||
|
vb.instantiate();
|
||||||
|
vb->create(10, 10, 10);
|
||||||
|
|
||||||
|
{
|
||||||
|
Array meta0;
|
||||||
|
meta0.push_back("Hello");
|
||||||
|
meta0.push_back("World");
|
||||||
|
meta0.push_back(42);
|
||||||
|
vb->set_voxel_metadata(Vector3i(1, 2, 3), meta0);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Dictionary meta1;
|
||||||
|
meta1["One"] = 1;
|
||||||
|
meta1["Two"] = 2.5;
|
||||||
|
meta1["Three"] = Basis();
|
||||||
|
vb->set_voxel_metadata(Vector3i(4, 5, 6), meta1);
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockSerializer::SerializeResult sresult = BlockSerializer::serialize(vb->get_buffer());
|
||||||
|
ZYLANN_TEST_ASSERT(sresult.success);
|
||||||
|
std::vector<uint8_t> bytes = sresult.data;
|
||||||
|
|
||||||
|
Ref<gd::VoxelBuffer> vb2;
|
||||||
|
vb2.instantiate();
|
||||||
|
|
||||||
|
ZYLANN_TEST_ASSERT(BlockSerializer::deserialize(to_span(bytes), vb2->get_buffer()));
|
||||||
|
|
||||||
|
ZYLANN_TEST_ASSERT(vb2->get_buffer().equals(vb->get_buffer()));
|
||||||
|
|
||||||
|
// `equals` does not compare metadata at the moment, mainly because it's not trivial and there is no use case
|
||||||
|
// for it apart from this test, so do it manually
|
||||||
|
|
||||||
|
const FlatMapMoveOnly<Vector3i, VoxelMetadata> &vb_meta_map = vb->get_buffer().get_voxel_metadata();
|
||||||
|
const FlatMapMoveOnly<Vector3i, VoxelMetadata> &vb2_meta_map = vb2->get_buffer().get_voxel_metadata();
|
||||||
|
|
||||||
|
ZYLANN_TEST_ASSERT(vb_meta_map.size() == vb2_meta_map.size());
|
||||||
|
|
||||||
|
for (auto it = vb_meta_map.begin(); it != vb_meta_map.end(); ++it) {
|
||||||
|
const VoxelMetadata &meta = it->value;
|
||||||
|
ZYLANN_TEST_ASSERT(meta.get_type() == gd::METADATA_TYPE_VARIANT);
|
||||||
|
|
||||||
|
const VoxelMetadata *meta2 = vb2_meta_map.find(it->key);
|
||||||
|
ZYLANN_TEST_ASSERT(meta2 != nullptr);
|
||||||
|
ZYLANN_TEST_ASSERT(meta2->get_type() == meta.get_type());
|
||||||
|
|
||||||
|
const gd::VoxelMetadataVariant &metav = static_cast<const gd::VoxelMetadataVariant &>(meta.get_custom());
|
||||||
|
const gd::VoxelMetadataVariant &meta2v = static_cast<const gd::VoxelMetadataVariant &>(meta2->get_custom());
|
||||||
|
ZYLANN_TEST_ASSERT(metav.data == meta2v.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
#define VOXEL_TEST(fname) \
|
#define VOXEL_TEST(fname) \
|
||||||
@ -1866,6 +2067,8 @@ void run_voxel_tests() {
|
|||||||
VOXEL_TEST(test_run_blocky_random_tick);
|
VOXEL_TEST(test_run_blocky_random_tick);
|
||||||
VOXEL_TEST(test_flat_map);
|
VOXEL_TEST(test_flat_map);
|
||||||
VOXEL_TEST(test_expression_parser);
|
VOXEL_TEST(test_expression_parser);
|
||||||
|
VOXEL_TEST(test_voxel_buffer_metadata);
|
||||||
|
VOXEL_TEST(test_voxel_buffer_metadata_gd);
|
||||||
|
|
||||||
print_line("------------ Voxel tests end -------------");
|
print_line("------------ Voxel tests end -------------");
|
||||||
}
|
}
|
||||||
|
221
util/flat_map.h
221
util/flat_map.h
@ -1,5 +1,5 @@
|
|||||||
#ifndef FLAT_MAP_H
|
#ifndef ZN_FLAT_MAP_H
|
||||||
#define FLAT_MAP_H
|
#define ZN_FLAT_MAP_H
|
||||||
|
|
||||||
#include "span.h"
|
#include "span.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@ -42,6 +42,7 @@ public:
|
|||||||
bool insert(K key, T value) {
|
bool insert(K key, T value) {
|
||||||
typename std::vector<Pair>::const_iterator it = std::lower_bound(_items.begin(), _items.end(), key);
|
typename std::vector<Pair>::const_iterator it = std::lower_bound(_items.begin(), _items.end(), key);
|
||||||
if (it != _items.end() && it->key == key) {
|
if (it != _items.end() && it->key == key) {
|
||||||
|
// Item already exists
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
_items.insert(it, Pair{ key, value });
|
_items.insert(it, Pair{ key, value });
|
||||||
@ -49,13 +50,16 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the key already exists, the item will replace the previous value.
|
// If the key already exists, the item will replace the previous value.
|
||||||
void insert_or_assign(K key, T value) {
|
T &insert_or_assign(K key, T value) {
|
||||||
typename std::vector<Pair>::iterator it = std::lower_bound(_items.begin(), _items.end(), key);
|
typename std::vector<Pair>::iterator it = std::lower_bound(_items.begin(), _items.end(), key);
|
||||||
if (it != _items.end() && it->key == key) {
|
if (it != _items.end() && it->key == key) {
|
||||||
|
// Item already exists, assign it
|
||||||
it->value = value;
|
it->value = value;
|
||||||
} else {
|
} else {
|
||||||
_items.insert(it, Pair{ key, value });
|
// Item doesnt exist, insert it
|
||||||
|
it = _items.insert(it, Pair{ key, value });
|
||||||
}
|
}
|
||||||
|
return it->value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize from a collection if items.
|
// Initialize from a collection if items.
|
||||||
@ -69,13 +73,20 @@ public:
|
|||||||
std::sort(_items.begin(), _items.end());
|
std::sort(_items.begin(), _items.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool find(K key, T &out_value) const {
|
const T *find(K key) const {
|
||||||
typename std::vector<Pair>::const_iterator it = std::lower_bound(_items.begin(), _items.end(), key);
|
typename std::vector<Pair>::const_iterator it = std::lower_bound(_items.begin(), _items.end(), key);
|
||||||
if (it != _items.end() && it->key == key) {
|
if (it != _items.end() && it->key == key) {
|
||||||
out_value = it->value;
|
return &it->value;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
T *find(K key) {
|
||||||
|
typename std::vector<Pair>::const_iterator it = std::lower_bound(_items.begin(), _items.end(), key);
|
||||||
|
if (it != _items.end() && it->key == key) {
|
||||||
|
return &it->value;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool has(K key) const {
|
bool has(K key) const {
|
||||||
@ -188,6 +199,198 @@ private:
|
|||||||
std::vector<Pair> _items;
|
std::vector<Pair> _items;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// template <typename T>
|
||||||
|
// void insert_default(std::vector<T> &vec, size_t pi) {
|
||||||
|
// ZN_ASSERT(pi <= vec.size());
|
||||||
|
// const size_t prev_size = vec.size();
|
||||||
|
// vec.resize(vec.size() + 1);
|
||||||
|
// for (size_t i = pi; i < prev_size; ++i) {
|
||||||
|
// vec[i + 1] = std::move(vec[i]);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Specialization of FlatMap where `T` is not copyable, only movable
|
||||||
|
template <typename K, typename T, typename KComp = FlatMapDefaultComparator<K>>
|
||||||
|
class FlatMapMoveOnly {
|
||||||
|
public:
|
||||||
|
struct Pair {
|
||||||
|
K key;
|
||||||
|
T value;
|
||||||
|
|
||||||
|
// For std::sort
|
||||||
|
inline bool operator<(const Pair &other) const {
|
||||||
|
return KComp::less_than(key, other.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For std::lower_bound
|
||||||
|
inline bool operator<(const K &other_key) const {
|
||||||
|
return KComp::less_than(key, other_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
Pair() {}
|
||||||
|
|
||||||
|
Pair(const K &p_key, T &&p_value) {
|
||||||
|
key = p_key;
|
||||||
|
value = std::move(p_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Pair(Pair &&other) {
|
||||||
|
key = other.key;
|
||||||
|
value = std::move(other.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator=(Pair &&other) {
|
||||||
|
key = other.key;
|
||||||
|
value = std::move(other.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// If the key already exists, the item is not inserted and returns false.
|
||||||
|
// If insertion was successful, returns true.
|
||||||
|
bool insert(K key, T &&value) {
|
||||||
|
typename std::vector<Pair>::const_iterator it = std::lower_bound(_items.begin(), _items.end(), key);
|
||||||
|
if (it != _items.end() && it->key == key) {
|
||||||
|
// Item already exists
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_items.insert(it, std::move(Pair(key, std::move(value))));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the key already exists, the item will replace the previous value.
|
||||||
|
T &insert_or_assign(K key, T &&value) {
|
||||||
|
typename std::vector<Pair>::iterator it = std::lower_bound(_items.begin(), _items.end(), key);
|
||||||
|
if (it != _items.end() && it->key == key) {
|
||||||
|
// Item already exists, assign it
|
||||||
|
it->value = std::move(value);
|
||||||
|
} else {
|
||||||
|
// Item doesnt exist, insert it
|
||||||
|
it = _items.insert(it, std::move(Pair(key, std::move(value))));
|
||||||
|
}
|
||||||
|
return it->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize from a collection if items.
|
||||||
|
// Faster than doing individual insertion of each item.
|
||||||
|
void clear_and_insert(Span<Pair> pairs) {
|
||||||
|
clear();
|
||||||
|
_items.resize(pairs.size());
|
||||||
|
for (size_t i = 0; i < pairs.size(); ++i) {
|
||||||
|
_items[i] = std::move(pairs[i]);
|
||||||
|
}
|
||||||
|
std::sort(_items.begin(), _items.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
const T *find(K key) const {
|
||||||
|
typename std::vector<Pair>::const_iterator it = std::lower_bound(_items.begin(), _items.end(), key);
|
||||||
|
if (it != _items.end() && it->key == key) {
|
||||||
|
return &it->value;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
T *find(K key) {
|
||||||
|
typename std::vector<Pair>::iterator it = std::lower_bound(_items.begin(), _items.end(), key);
|
||||||
|
if (it != _items.end() && it->key == key) {
|
||||||
|
return &it->value;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool has(K key) const {
|
||||||
|
// Using std::binary_search is very annoying.
|
||||||
|
// First, we don't want to pass a Pair because it would require constructing a T.
|
||||||
|
// Using just the key as the "value" to search doesn't compile because Pair only has comparison with K as second
|
||||||
|
// argument.
|
||||||
|
// Specifying a comparison lambda is also not viable because both arguments need to be convertible to K.
|
||||||
|
// Making it work would require passing a struct with two operators() with arguments in both orders...
|
||||||
|
//return std::binary_search(_items.cbegin(), _items.cend(), key);
|
||||||
|
|
||||||
|
typename std::vector<Pair>::const_iterator it = std::lower_bound(_items.begin(), _items.end(), key);
|
||||||
|
return it != _items.end() && it->key == key;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool erase(K key) {
|
||||||
|
typename std::vector<Pair>::iterator it = std::lower_bound(_items.begin(), _items.end(), key);
|
||||||
|
if (it != _items.end() && it->key == key) {
|
||||||
|
_items.erase(it);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t size() const {
|
||||||
|
return _items.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
_items.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// `bool predicate(FlatMap<K, T>::Pair)`
|
||||||
|
template <typename F>
|
||||||
|
inline void remove_if(F predicate) {
|
||||||
|
_items.erase(std::remove_if(_items.begin(), _items.end(), predicate));
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator=(const FlatMap<K, T> &other) {
|
||||||
|
_items = other._items;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const FlatMap<K, T> &other) const {
|
||||||
|
return _items == other._items;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConstIterator {
|
||||||
|
public:
|
||||||
|
ConstIterator(const Pair *p) : _current(p) {}
|
||||||
|
|
||||||
|
inline const Pair &operator*() {
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
ZN_ASSERT(_current != nullptr);
|
||||||
|
#endif
|
||||||
|
return *_current;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const Pair *operator->() {
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
ZN_ASSERT(_current != nullptr);
|
||||||
|
#endif
|
||||||
|
return _current;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ConstIterator &operator++() {
|
||||||
|
++_current;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool operator==(const ConstIterator other) const {
|
||||||
|
return _current == other._current;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool operator!=(const ConstIterator other) const {
|
||||||
|
return _current != other._current;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const Pair *_current;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline ConstIterator begin() const {
|
||||||
|
return ConstIterator(_items.empty() ? nullptr : &_items[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ConstIterator end() const {
|
||||||
|
return ConstIterator(_items.empty() ? nullptr : (&_items[0] + _items.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Sorted by key
|
||||||
|
std::vector<Pair> _items;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace zylann
|
} // namespace zylann
|
||||||
|
|
||||||
#endif // FLAT_MAP_H
|
#endif // ZN_FLAT_MAP_H
|
||||||
|
Loading…
x
Reference in New Issue
Block a user