#ifndef VOXEL_LOD_TERRAIN_UPDATE_DATA_H #define VOXEL_LOD_TERRAIN_UPDATE_DATA_H #include "../../constants/voxel_constants.h" #include "../../generators/voxel_generator.h" #include "../../storage/voxel_data_map.h" #include "../../streams/voxel_stream.h" #include "../../util/fixed_array.h" #include "../voxel_mesh_map.h" #include "lod_octree.h" #include #include namespace zylann { class AsyncDependencyTracker; namespace voxel { // Settings and states needed for the multi-threaded part of the update loop of VoxelLodTerrain. // See `VoxelLodTerrainUpdateTask` for more info. struct VoxelLodTerrainUpdateData { struct OctreeItem { LodOctree octree; }; struct TransitionUpdate { Vector3i block_position; uint8_t transition_mask; }; struct BlockLocation { Vector3i position; uint8_t lod; }; struct BlockToSave { std::shared_ptr voxels; Vector3i position; uint8_t lod; }; // These values don't change during the update task. struct Settings { // Area within which voxels can exist. // Note, these bounds might not be exactly represented. This volume is chunk-based, so the result will be // approximated to the closest chunk. Box3i bounds_in_voxels; unsigned int lod_count = 0; // Distance between a viewer and the end of LOD0 float lod_distance = 0.f; unsigned int view_distance_voxels = 512; bool full_load_mode = false; bool run_stream_in_editor = true; unsigned int mesh_block_size_po2 = 4; }; enum MeshState { MESH_NEVER_UPDATED = 0, // TODO Redundant with MESH_NEED_UPDATE? MESH_UP_TO_DATE, MESH_NEED_UPDATE, // The mesh is out of date but was not yet scheduled for update MESH_UPDATE_NOT_SENT, // The mesh is out of date and was scheduled for update, but no request have been sent // yet MESH_UPDATE_SENT // The mesh is out of date, and an update request was sent, pending response }; struct MeshBlockState { std::atomic state; uint8_t transition_mask; bool active; bool pending_transition_update; MeshBlockState() : state(MESH_NEVER_UPDATED), transition_mask(0), active(false), pending_transition_update(false) {} }; // Version of the mesh map designed to be mainly used for the threaded update task. // It contains states used to determine when to actually load/unload meshes. struct MeshMapState { // Values in this map are expected to have stable addresses. std::unordered_map map; // Locked for writing when blocks get inserted or removed from the map. // If you need to lock more than one Lod, always do so in increasing order, to avoid deadlocks. // IMPORTANT: // - The update task will add and remove blocks from this map. // - Threads outside the update task must never add or remove blocks to the map (even with locking), // unless the task is not running in parallel. RWLock map_lock; }; // Each LOD works in a set of coordinates spanning 2x more voxels the higher their index is struct Lod { // Keeping track of asynchronously loading blocks so we don't try to redundantly load them std::unordered_set loading_blocks; BinaryMutex loading_blocks_mutex; // These are relative to this LOD, in block coordinates Vector3i last_viewer_data_block_pos; int last_view_distance_data_blocks = 0; MeshMapState mesh_map_state; std::vector blocks_pending_update; Vector3i last_viewer_mesh_block_pos; int last_view_distance_mesh_blocks = 0; // Deferred outputs to main thread std::vector mesh_blocks_to_unload; std::vector mesh_blocks_to_update_transitions; std::vector mesh_blocks_to_activate; std::vector mesh_blocks_to_deactivate; inline bool has_loading_block(const Vector3i &pos) const { return loading_blocks.find(pos) != loading_blocks.end(); } }; struct AsyncEdit { IThreadedTask *task; Box3i box; std::shared_ptr task_tracker; }; struct RunningAsyncEdit { std::shared_ptr tracker; Box3i box; }; struct Stats { uint32_t blocked_lods = 0; uint32_t time_detect_required_blocks = 0; uint32_t time_io_requests = 0; uint32_t time_mesh_requests = 0; uint32_t time_total = 0; }; // Data modified by the update task struct State { // This terrain type is a sparse grid of octrees. // Indexed by a grid coordinate whose step is the size of the highest-LOD block. // Not using a pointer because Map storage is stable. // TODO Optimization: could be replaced with a grid data structure std::map lod_octrees; Box3i last_octree_region_box; Vector3i local_viewer_pos_previous_octree_update; bool had_blocked_octree_nodes_previous_update = false; bool force_update_octrees_next_update = false; FixedArray lods; // This is the entry point for notifying data changes, which will cause mesh updates. // Contains blocks that were edited and need their LOD counterparts to be updated. // Scheduling is only done at LOD0 because it is the only editable LOD. std::vector blocks_pending_lodding_lod0; BinaryMutex blocks_pending_lodding_lod0_mutex; std::vector pending_async_edits; BinaryMutex pending_async_edits_mutex; std::vector running_async_edits; // Areas where generated stuff has changed. Similar to an edit, but non-destructive. std::vector changed_generated_areas; BinaryMutex changed_generated_areas_mutex; Stats stats; }; // Set to true when the update task is finished std::atomic_bool task_is_complete; // Will be locked as long as the update task is running. BinaryMutex completion_mutex; Settings settings; State state; // After this call, no locking is necessary, as no other thread should be using the data. // However it can stall for longer, so prefer using it when doing structural changes, such as changing LOD count, // LOD distances, or the way the update logic runs. void wait_for_end_of_task() { MutexLock lock(completion_mutex); } }; } // namespace voxel } // namespace zylann #endif // VOXEL_LOD_TERRAIN_UPDATE_DATA_H