// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.Loader; using System.Runtime.Serialization.Formatters.Binary; using FlaxEngine; namespace FlaxEditor.Surface { /// /// Metadata container. /// [HideInEditor] public class SurfaceMeta { /// /// Metadata entry /// public struct Entry { /// /// The type identifier. /// public int TypeID; /// /// The data. /// public byte[] Data; } /// /// All meta entries /// public readonly List Entries = new List(); /// /// The attribute meta type identifier. /// public const int AttributeMetaTypeID = 12; /// /// Gets the attributes collection from the data. /// /// The graph metadata. /// The attributes collection. public static Attribute[] GetAttributes(byte[] data) { if (data != null && data.Length != 0) { using (var stream = new MemoryStream(data)) { try { // Ensure we are in the correct load context (https://github.com/dotnet/runtime/issues/42041) using var ctx = AssemblyLoadContext.EnterContextualReflection(typeof(Editor).Assembly); var formatter = new BinaryFormatter(); #pragma warning disable SYSLIB0011 return (Attribute[])formatter.Deserialize(stream); #pragma warning restore SYSLIB0011 } catch (Exception ex) { Editor.LogError("Failed to deserialize Visject attributes array."); Editor.LogWarning(ex); } } } return Utils.GetEmptyArray(); } /// /// Determines whether the specified attribute was defined for this member. /// /// The graph parameter. /// The attribute type. /// true if the specified member has attribute; otherwise, false. public static bool HasAttribute(GraphParameter parameter, Type attributeType) { return GetAttributes(parameter).Any(x => x.GetType() == attributeType); } /// /// Gets the attributes collection from the graph parameter. /// /// The graph parameter. /// The attributes collection. public static Attribute[] GetAttributes(GraphParameter parameter) { var data = parameter.GetMetaData(AttributeMetaTypeID); return GetAttributes(data); } /// /// Gets the attributes collection. /// /// The attributes collection. public Attribute[] GetAttributes() { for (int i = 0; i < Entries.Count; i++) { if (Entries[i].TypeID == AttributeMetaTypeID) return GetAttributes(Entries[i].Data); } return Utils.GetEmptyArray(); } /// /// Sets the attributes collection. /// /// The attributes to set. public void SetAttributes(Attribute[] attributes) { if (attributes == null || attributes.Length == 0) { RemoveEntry(AttributeMetaTypeID); } else { for (int i = 0; i < attributes.Length; i++) { if (attributes[i] == null) throw new NullReferenceException("One of the Visject attributes is null."); } using (var stream = new MemoryStream()) { // Ensure we are in the correct load context (https://github.com/dotnet/runtime/issues/42041) using var ctx = AssemblyLoadContext.EnterContextualReflection(typeof(Editor).Assembly); var formatter = new BinaryFormatter(); #pragma warning disable SYSLIB0011 formatter.Serialize(stream, attributes); #pragma warning restore SYSLIB0011 AddEntry(AttributeMetaTypeID, stream.ToArray()); } } } /// /// Load from the stream /// /// Stream public void Load(BinaryReader stream) { Entries.Clear(); Entries.TrimExcess(); // Version 1 { int entries = stream.ReadInt32(); Entries.Capacity = entries; for (int i = 0; i < entries; i++) { Entry e = new Entry { TypeID = stream.ReadInt32() }; stream.ReadInt64(); // don't use CreationTime uint dataSize = stream.ReadUInt32(); e.Data = new byte[dataSize]; stream.Read(e.Data, 0, (int)dataSize); Entries.Add(e); } } } /// /// Save to the stream /// /// Stream /// True if cannot save data public void Save(BinaryWriter stream) { stream.Write(Entries.Count); for (int i = 0; i < Entries.Count; i++) { Entry e = Entries[i]; stream.Write(e.TypeID); stream.Write((long)0); uint dataSize = e.Data != null ? (uint)e.Data.Length : 0; stream.Write(dataSize); if (dataSize > 0) { stream.Write(e.Data); } } } /// /// Releases meta data. /// public void Release() { Entries.Clear(); Entries.TrimExcess(); } /// /// Gets the entry. /// /// Entry type ID /// Entry public Entry GetEntry(int typeID) { for (int i = 0; i < Entries.Count; i++) { if (Entries[i].TypeID == typeID) { return Entries[i]; } } return new Entry(); } /// /// Adds new entry. /// /// Type ID /// Bytes to set public void AddEntry(int typeID, byte[] data) { var e = new Entry { TypeID = typeID, Data = data }; for (int i = 0; i < Entries.Count; i++) { if (Entries[i].TypeID == typeID) { Entries[i] = e; return; } } Entries.Add(e); } /// /// Removes the entry. /// /// Type ID public void RemoveEntry(int typeID) { for (int i = 0; i < Entries.Count; i++) { if (Entries[i].TypeID == typeID) { Entries.RemoveAt(i); break; } } } } }