// Copyright (c) Wojciech Figat. All rights reserved. using System; using System.Threading; using System.Threading.Tasks; namespace FlaxEngine.Utilities { /// /// Helper utility for caching model meshes data. /// [HideInEditor] public class MeshDataCache { /// /// The single mesh data container. /// public struct MeshData { /// /// The index buffer. /// public uint[] IndexBuffer; /// /// The vertex buffer. /// [Deprecated in v1.10] /// [Obsolete("Use new VertexAccessor.")] public Mesh.Vertex[] VertexBuffer; /// /// The vertex buffer accessor (with all available vertex buffers loaded in). /// public MeshAccessor VertexAccessor; } private ModelBase _model; private MeshData[][] _meshDatas; private bool _inProgress; private bool _cancel; /// /// Gets the mesh datas (null if during downloading). /// public MeshData[][] MeshDatas => _inProgress ? null : _meshDatas; /// /// Occurs when mesh data gets downloaded (called on async thread). /// public event Action Finished; /// /// Requests the mesh data. /// /// The model to get its data. /// True if has valid data to access, otherwise false if it's during downloading. public bool RequestMeshData(ModelBase model) { if (model == null) throw new ArgumentNullException(); if (_model != model) { // Mode changes so release previous cache Dispose(); } if (_inProgress) { // Still downloading return false; } if (_meshDatas != null) { // Has valid data return true; } // Start downloading _model = model; _inProgress = true; _cancel = false; Task.Run(new Action(DownloadMeshData)); return false; } /// /// Releases cache. /// public void Dispose() { WaitForMeshDataRequestEnd(); _meshDatas = null; _inProgress = false; } /// /// Waits for mesh data request to end. Does nothing if already has valid data or no valid request pending. /// public void WaitForMeshDataRequestEnd() { if (_inProgress) { _cancel = true; for (int i = 0; i < 500 && _inProgress; i++) Thread.Sleep(10); } } private void DownloadMeshData() { var success = false; try { if (!_model) return; if (_model.WaitForLoaded()) throw new Exception("WaitForLoaded failed"); var lodsCount = _model.LODsCount; _meshDatas = new MeshData[lodsCount][]; Span vertexBufferTypes = stackalloc MeshBufferType[3] { MeshBufferType.Vertex0, MeshBufferType.Vertex1, MeshBufferType.Vertex2 }; for (int lodIndex = 0; lodIndex < lodsCount && !_cancel; lodIndex++) { _model.GetMeshes(out var meshes, lodIndex); _meshDatas[lodIndex] = new MeshData[meshes.Length]; for (int meshIndex = 0; meshIndex < meshes.Length && !_cancel; meshIndex++) { var mesh = meshes[meshIndex]; var meshData = new MeshData { IndexBuffer = mesh.DownloadIndexBuffer(), #pragma warning disable 0618 VertexBuffer = mesh is Mesh m ? m.DownloadVertexBuffer() : null, #pragma warning restore 0618 VertexAccessor = new MeshAccessor(), }; if (meshData.VertexAccessor.LoadMesh(mesh, false, vertexBufferTypes)) throw new Exception("MeshAccessor.LoadMesh failed"); _meshDatas[lodIndex][meshIndex] = meshData; } } success = true; } catch (Exception ex) { Debug.Logger.LogHandler.LogWrite(LogType.Warning, "Failed to get mesh data."); Debug.Logger.LogHandler.LogWrite(LogType.Warning, _model?.ToString() ?? string.Empty); Debug.Logger.LogHandler.LogException(ex, null); } finally { _inProgress = false; if (success) { Finished?.Invoke(); } } } } }