Files
FlaxEngine/Source/Editor/Surface/VisjectSurfaceContext.Serialization.cs

966 lines
37 KiB
C#

// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using FlaxEditor.Scripting;
using FlaxEditor.Surface.Elements;
using FlaxEditor.Utilities;
using FlaxEngine;
using FlaxEngine.Utilities;
using Utils = FlaxEditor.Utilities.Utils;
namespace FlaxEditor.Surface
{
/// <summary>
/// Types of surface actions.
/// </summary>
public enum SurfaceNodeActions
{
/// <summary>
/// Node has been created by surface load.
/// </summary>
Load,
/// <summary>
/// Node has been created/deleted by user action.
/// </summary>
User,
/// <summary>
/// Node has been created/deleted via undo.
/// </summary>
Undo,
/// <summary>
/// Node has been pasted.
/// </summary>
Paste,
}
/// <summary>
/// The missing node. Cached the node group, type and stored values information.
/// </summary>
/// <seealso cref="FlaxEditor.Surface.SurfaceNode" />
internal class MissingNode : SurfaceNode
{
/// <inheritdoc />
public MissingNode(uint id, VisjectSurfaceContext context, ushort originalGroupId, ushort originalNodeId)
: base(id, context, new NodeArchetype
{
TypeID = originalNodeId,
Title = "Missing Node :(",
Description = ":(",
Flags = NodeFlags.AllGraphs,
Size = new Float2(200, 70),
Elements = new NodeElementArchetype[0],
DefaultValues = new object[32],
}, new GroupArchetype
{
GroupID = originalGroupId,
Name = string.Empty,
Color = Color.Black,
Archetypes = new NodeArchetype[0]
})
{
}
}
/// <summary>
/// The dummy custom node used to help custom surface nodes management (loading and layout preserving on missing type).
/// </summary>
internal class DummyCustomNode : SurfaceNode
{
/// <inheritdoc />
public DummyCustomNode(uint id, VisjectSurfaceContext context)
: base(id, context, new NodeArchetype(), new GroupArchetype())
{
}
}
public partial class VisjectSurfaceContext
{
// Note: surface serialization is port from c++ code base (also a legacy)
// Refactor this in future together with c++ backend
private struct ConnectionHint
{
public uint NodeA;
public byte BoxA;
public uint NodeB;
public byte BoxB;
}
private readonly ThreadLocal<List<Box>> _cachedBoxes = new ThreadLocal<List<Box>>(() => new List<Box>());
private readonly ThreadLocal<List<ConnectionHint>> _cachedConnections = new ThreadLocal<List<ConnectionHint>>(() => new List<ConnectionHint>());
/// <summary>
/// Loads the surface from bytes. Clears the surface before and uses context source data as a surface bytes source.
/// </summary>
/// <remarks>
/// Assume this method does not throw exceptions but uses return value as a error code.
/// </remarks>
/// <returns>True if failed, otherwise false.</returns>
public bool Load()
{
if (_surface != null)
_surface._isUpdatingBoxTypes++;
try
{
// Prepare
Clear();
Loading?.Invoke(this);
// Load bytes
var bytes = Context.SurfaceData;
if (bytes == null)
throw new Exception("Failed to load surface data.");
// Load graph (empty bytes data means empty graph for simplicity when using subgraphs)
if (bytes.Length > 0)
{
using (var stream = new MemoryStream(bytes))
using (var reader = new BinaryReader(stream))
{
LoadGraph(reader);
}
}
// Load surface meta
var meta = _meta.GetEntry(10);
if (meta.Data != null)
{
Utils.ByteArrayToStructure(meta.Data, out CachedSurfaceMeta);
}
else
{
// Reset view
CachedSurfaceMeta.ViewCenterPosition = Float2.Zero;
CachedSurfaceMeta.Scale = 1.0f;
}
// [Deprecated on 04.07.2019] Load surface comments
var commentsData = _meta.GetEntry(666);
if (commentsData.Data != null)
{
using (var stream = new MemoryStream(commentsData.Data))
using (var reader = new BinaryReader(stream))
{
var commentsCount = reader.ReadInt32();
for (int i = 0; i < commentsCount; i++)
{
var title = reader.ReadStr(71);
var color = new Color(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
var bounds = new Rectangle(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
var comment = SpawnComment(ref bounds, title, color);
if (comment == null)
throw new InvalidOperationException("Failed to create comment.");
OnControlLoaded(comment, SurfaceNodeActions.Load);
}
}
}
// Post load
for (int i = 0; i < RootControl.Children.Count; i++)
{
if (RootControl.Children[i] is SurfaceControl control)
control.OnSurfaceLoaded(SurfaceNodeActions.Load);
}
RootControl.UnlockChildrenRecursive();
// Update boxes types for nodes that dependant box types based on incoming connections
{
bool keepUpdating = false;
int updateLimit = 100;
do
{
for (int i = 0; i < RootControl.Children.Count; i++)
{
if (RootControl.Children[i] is SurfaceNode node && !node.HasDependentBoxesSetup)
{
node.UpdateBoxesTypes();
keepUpdating = true;
}
}
} while (keepUpdating && updateLimit-- > 0);
}
Loaded?.Invoke(this);
// Clear modification flag
_isModified = false;
}
catch (Exception ex)
{
// Error
Editor.LogWarning("Loading Visject Surface data failed.");
Editor.LogWarning(ex);
return true;
}
finally
{
if (_surface != null)
_surface._isUpdatingBoxTypes--;
}
return false;
}
/// <summary>
/// Saves the surface to bytes. Performs also modified child surfaces saving before.
/// </summary>
/// <remarks>
/// Assume this method does not throw exceptions but uses return value as a error code.
/// </remarks>
/// <returns>True if failed, otherwise false.</returns>
public bool Save()
{
// Save all children modified before saving the current surface
for (int i = 0; i < Children.Count; i++)
{
if (Children[i].IsModified && Children[i].Save())
return true;
}
Saving?.Invoke(this);
// Save surface meta
_meta.AddEntry(10, Utils.StructureToByteArray(ref CachedSurfaceMeta));
// Save all nodes meta
VisjectSurface.Meta11 meta11;
for (int i = 0; i < Nodes.Count; i++)
{
var node = Nodes[i];
meta11.Position = node.Location;
meta11.Selected = false; // don't save selection to prevent stupid binary diffs on asset
// TODO: reuse byte[] array for all nodes to reduce dynamic memory allocations
node.Meta.AddEntry(11, Utils.StructureToByteArray(ref meta11));
}
// Save graph
try
{
// Save graph
using (var stream = new MemoryStream())
using (var writer = new BinaryWriter(stream))
{
// Save graph to bytes
SaveGraph(writer);
var bytes = stream.ToArray();
// Send data to the container
Context.SurfaceData = bytes;
Saved?.Invoke(this);
// Clear modification flag
_isModified = false;
}
}
catch (Exception ex)
{
// Error
Editor.LogWarning("Saving Visject Surface data failed.");
Editor.LogWarning(ex);
return true;
}
return false;
}
// [Deprecated on 31.07.2020, expires on 31.07.2022]
internal enum GraphParamType_Deprecated
{
Bool = 0,
Integer = 1,
Float = 2,
Vector2 = 3,
Vector3 = 4,
Vector4 = 5,
Color = 6,
Texture = 7,
NormalMap = 8,
String = 9,
Box = 10,
Rotation = 11,
Transform = 12,
Asset = 13,
Actor = 14,
Rectangle = 15,
CubeTexture = 16,
SceneTexture = 17,
GPUTexture = 18,
Matrix = 19,
GPUTextureArray = 20,
GPUTextureVolume = 21,
GPUTextureCube = 22,
ChannelMask = 23,
};
// [Deprecated on 31.07.2020, expires on 31.07.2022]
internal enum GraphConnectionType_Deprecated : uint
{
Invalid = 0,
Impulse = 1 << 0,
Bool = 1 << 1,
Integer = 1 << 2,
Float = 1 << 3,
Vector2 = 1 << 4,
Vector3 = 1 << 5,
Vector4 = 1 << 6,
String = 1 << 7,
Object = 1 << 8,
Rotation = 1 << 9,
Transform = 1 << 10,
Box = 1 << 11,
ImpulseSecondary = 1 << 12,
UnsignedInteger = 1 << 13,
Scalar = Bool | Integer | Float | UnsignedInteger,
Vector = Vector2 | Vector3 | Vector4,
Variable = Scalar | Vector | String | Object | Rotation | Transform | Box,
All = Variable | Impulse,
};
internal static Type GetGraphParameterValueType(GraphParamType_Deprecated type)
{
// [Deprecated on 31.07.2020, expires on 31.07.2022]
switch (type)
{
case GraphParamType_Deprecated.Bool: return typeof(bool);
case GraphParamType_Deprecated.Integer: return typeof(int);
case GraphParamType_Deprecated.Float: return typeof(float);
case GraphParamType_Deprecated.Vector2: return typeof(Float2);
case GraphParamType_Deprecated.Vector3: return typeof(Float3);
case GraphParamType_Deprecated.Vector4: return typeof(Float4);
case GraphParamType_Deprecated.Color: return typeof(Color);
case GraphParamType_Deprecated.Texture: return typeof(Texture);
case GraphParamType_Deprecated.NormalMap: return typeof(Texture);
case GraphParamType_Deprecated.String: return typeof(string);
case GraphParamType_Deprecated.Box: return typeof(BoundingBox);
case GraphParamType_Deprecated.Rotation: return typeof(Quaternion);
case GraphParamType_Deprecated.Transform: return typeof(Transform);
case GraphParamType_Deprecated.Asset: return typeof(Asset);
case GraphParamType_Deprecated.Actor: return typeof(Actor);
case GraphParamType_Deprecated.Rectangle: return typeof(Rectangle);
case GraphParamType_Deprecated.CubeTexture: return typeof(CubeTexture);
case GraphParamType_Deprecated.SceneTexture: return typeof(MaterialSceneTextures);
case GraphParamType_Deprecated.GPUTexture: return typeof(GPUTexture);
case GraphParamType_Deprecated.Matrix: return typeof(Matrix);
case GraphParamType_Deprecated.GPUTextureArray: return typeof(GPUTexture);
case GraphParamType_Deprecated.GPUTextureVolume: return typeof(GPUTexture);
case GraphParamType_Deprecated.GPUTextureCube: return typeof(GPUTexture);
case GraphParamType_Deprecated.ChannelMask: return typeof(ChannelMask);
default: throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
}
internal static Type GetGraphConnectionType(GraphConnectionType_Deprecated connectionType)
{
// [Deprecated on 31.07.2020, expires on 31.07.2022]
switch (connectionType)
{
case GraphConnectionType_Deprecated.Impulse: return typeof(void);
case GraphConnectionType_Deprecated.Bool: return typeof(bool);
case GraphConnectionType_Deprecated.Integer: return typeof(int);
case GraphConnectionType_Deprecated.Float: return typeof(float);
case GraphConnectionType_Deprecated.Vector2: return typeof(Float2);
case GraphConnectionType_Deprecated.Vector3: return typeof(Float3);
case GraphConnectionType_Deprecated.Vector4: return typeof(Float4);
case GraphConnectionType_Deprecated.String: return typeof(string);
case GraphConnectionType_Deprecated.Object: return typeof(FlaxEngine.Object);
case GraphConnectionType_Deprecated.Rotation: return typeof(Quaternion);
case GraphConnectionType_Deprecated.Transform: return typeof(Transform);
case GraphConnectionType_Deprecated.Box: return typeof(BoundingBox);
case GraphConnectionType_Deprecated.ImpulseSecondary: return typeof(void);
case GraphConnectionType_Deprecated.UnsignedInteger: return typeof(uint);
default: return null;
}
}
private void SaveGraph(BinaryWriter stream)
{
// Magic Code
stream.Write(1963542358);
// Version
stream.Write(7000);
// Nodes count
stream.Write(Nodes.Count);
// Parameters count
stream.Write(Parameters.Count);
// For each node
for (int i = 0; i < Nodes.Count; i++)
{
var node = Nodes[i];
stream.Write(node.ID);
stream.Write(node.Type);
}
// For each param
for (int i = 0; i < Parameters.Count; i++)
{
var param = Parameters[i];
stream.WriteVariantType(param.Type);
stream.WriteGuid(ref param.ID);
stream.WriteStr(param.Name, 97);
stream.Write((byte)(param.IsPublic ? 1 : 0));
stream.WriteVariant(param.Value);
param.Meta.Save(stream);
}
// For each node
var boxes = _cachedBoxes.Value;
boxes.Clear();
for (int i = 0; i < Nodes.Count; i++)
{
var node = Nodes[i];
// Values
if (node.Values != null)
{
stream.Write(node.Values.Length);
for (int j = 0; j < node.Values.Length; j++)
stream.WriteVariant(node.Values[j]);
}
else
{
stream.Write(0);
}
// Boxes
node.GetBoxes(boxes);
stream.Write((ushort)boxes.Count);
for (int j = 0; j < boxes.Count; j++)
{
var box = boxes[j];
stream.Write((byte)box.ID);
stream.WriteVariantType(box.DefaultType);
stream.Write((ushort)box.Connections.Count);
for (int k = 0; k < box.Connections.Count; k++)
{
var targetBox = box.Connections[k];
if (targetBox == null)
throw new Exception("Missing target box.");
stream.Write(targetBox.ParentNode.ID);
stream.Write((byte)targetBox.ID);
}
}
// Meta
node.Meta.Save(stream);
}
boxes.Clear();
// Visject Meta
_meta.Save(stream);
// Ending char
stream.Write((byte)'\t');
}
private void LoadGraph(BinaryReader stream)
{
// IMPORTANT! This must match C++ Graph format
var nodeArchetypes = _surface?.NodeArchetypes ?? NodeFactory.DefaultGroups;
var customNodes = _surface?.GetCustomNodes();
// Magic Code
int tmp = stream.ReadInt32();
if (tmp != 1963542358)
{
// Error
throw new Exception("Invalid Graph format version");
}
// Version
var version = stream.ReadUInt32();
var tmpHints = _cachedConnections.Value;
var guidBytes = new byte[16];
if (version < 7000)
{
// Time saved (not used anymore to prevent binary diffs after saving unmodified surface)
stream.ReadInt64();
// Nodes count
int nodesCount = stream.ReadInt32();
if (Nodes.Capacity < nodesCount)
Nodes.Capacity = nodesCount;
tmpHints.Clear();
tmpHints.Capacity = Mathf.Max(tmpHints.Capacity, nodesCount * 4);
// Parameters count
int parametersCount = stream.ReadInt32();
if (Parameters.Capacity < parametersCount)
Parameters.Capacity = parametersCount;
// For each node
for (int i = 0; i < nodesCount; i++)
{
// ID
uint id = stream.ReadUInt32();
// Type
ushort typeId = stream.ReadUInt16();
ushort groupId = stream.ReadUInt16();
// Create node
SurfaceNode node;
if (groupId == Archetypes.Custom.GroupID)
node = new DummyCustomNode(id, this);
else
node = NodeFactory.CreateNode(nodeArchetypes, id, this, groupId, typeId);
if (node == null)
node = new MissingNode(id, this, groupId, typeId);
Nodes.Add(node);
}
// For each param
for (int i = 0; i < parametersCount; i++)
{
// Create param
var param = new SurfaceParameter();
Parameters.Add(param);
// Properties
param.Type = new ScriptType(GetGraphParameterValueType((GraphParamType_Deprecated)stream.ReadByte()));
stream.Read(guidBytes, 0, 16);
param.ID = new Guid(guidBytes);
param.Name = stream.ReadStr(97);
param.IsPublic = stream.ReadByte() != 0;
bool isStatic = stream.ReadByte() != 0;
bool isUIVisible = stream.ReadByte() != 0;
bool isUIEditable = stream.ReadByte() != 0;
// References [Deprecated]
int refsCount = stream.ReadInt32();
for (int j = 0; j < refsCount; j++)
{
uint refID = stream.ReadUInt32();
}
// Value
stream.ReadCommonValue(ref param.Value);
// Meta
param.Meta.Load(stream);
}
// For each node
for (int i = 0; i < nodesCount; i++)
{
var node = Nodes[i];
int valuesCnt = stream.ReadInt32();
int firstValueReadIdx = 0;
// Special case for missing nodes
if (node is DummyCustomNode customNode)
{
node = null;
// Values check
if (valuesCnt < 2)
throw new Exception("Missing custom nodes data.");
// Node typename check
object typeNameValue = null;
stream.ReadCommonValue(ref typeNameValue);
firstValueReadIdx = 1;
string typeName = typeNameValue as string ?? string.Empty;
// Find custom node archetype that matches this node type (it must be unique)
if (customNodes?.Archetypes != null && typeName.Length != 0)
{
NodeArchetype arch = null;
foreach (var nodeArchetype in customNodes.Archetypes)
{
if (string.Equals(Archetypes.Custom.GetNodeTypeName(nodeArchetype), typeName, StringComparison.OrdinalIgnoreCase))
{
arch = nodeArchetype;
break;
}
}
if (arch != null)
node = NodeFactory.CreateNode(customNode.ID, this, customNodes, arch);
}
// Fallback to the
if (node == null)
{
Editor.LogWarning(string.Format("Cannot find custom node archetype for {0}", typeName));
node = new MissingNode(customNode.ID, this, Archetypes.Custom.GroupID, customNode.Archetype.TypeID);
}
Nodes[i] = node;
// Store node typename in values container
node.Values[0] = typeName;
}
if (node is MissingNode)
{
// Read all values
Array.Resize(ref node.Values, valuesCnt);
for (int j = firstValueReadIdx; j < valuesCnt; j++)
{
// ReSharper disable once PossibleNullReferenceException
stream.ReadCommonValue(ref node.Values[j]);
}
firstValueReadIdx = valuesCnt = node.Values.Length;
}
// Values
int nodeValuesCnt = node.Values?.Length ?? 0;
if (valuesCnt == nodeValuesCnt)
{
for (int j = firstValueReadIdx; j < valuesCnt; j++)
{
// ReSharper disable once PossibleNullReferenceException
stream.ReadCommonValue(ref node.Values[j]);
}
}
else if ((node.Archetype.Flags & NodeFlags.VariableValuesSize) != 0)
{
node.Values = new object[valuesCnt];
for (int j = firstValueReadIdx; j < valuesCnt; j++)
{
// ReSharper disable once PossibleNullReferenceException
stream.ReadCommonValue(ref node.Values[j]);
}
}
else
{
Editor.LogWarning(string.Format("Invalid node values. Loaded: {0}, expected: {1}. Type: {2}, {3}", valuesCnt, nodeValuesCnt, node.Archetype.Title, node.Archetype.TypeID));
object dummy = null;
for (int j = firstValueReadIdx; j < valuesCnt; j++)
{
stream.ReadCommonValue(ref dummy);
if (j < nodeValuesCnt &&
dummy != null &&
node.Values[j] != null &&
node.Values[j].GetType() == dummy.GetType())
{
node.Values[j] = dummy;
}
}
}
// Boxes
ushort boxesCount = stream.ReadUInt16();
for (int j = 0; j < boxesCount; j++)
{
var id = stream.ReadByte();
stream.ReadUInt32(); // Skip type
ushort connectionsCnt = stream.ReadUInt16();
ConnectionHint hint;
hint.NodeB = node.ID;
hint.BoxB = id;
for (int k = 0; k < connectionsCnt; k++)
{
uint targetNodeID = stream.ReadUInt32();
byte targetBoxID = stream.ReadByte();
hint.NodeA = targetNodeID;
hint.BoxA = targetBoxID;
tmpHints.Add(hint);
}
}
// Meta
node.Meta.Load(stream);
OnControlLoaded(node, SurfaceNodeActions.Load);
}
}
else if (version == 7000)
{
// Nodes count
int nodesCount = stream.ReadInt32();
if (Nodes.Capacity < nodesCount)
Nodes.Capacity = nodesCount;
tmpHints.Clear();
tmpHints.Capacity = Mathf.Max(tmpHints.Capacity, nodesCount * 4);
// Parameters count
int parametersCount = stream.ReadInt32();
if (Parameters.Capacity < parametersCount)
Parameters.Capacity = parametersCount;
// For each node
for (int i = 0; i < nodesCount; i++)
{
uint id = stream.ReadUInt32();
ushort typeId = stream.ReadUInt16();
ushort groupId = stream.ReadUInt16();
// Create node
SurfaceNode node;
if (groupId == Archetypes.Custom.GroupID)
node = new DummyCustomNode(id, this);
else
node = NodeFactory.CreateNode(nodeArchetypes, id, this, groupId, typeId);
if (node == null)
node = new MissingNode(id, this, groupId, typeId);
Nodes.Add(node);
}
// For each param
for (int i = 0; i < parametersCount; i++)
{
// Create param
var param = new SurfaceParameter();
Parameters.Add(param);
param.Type = stream.ReadVariantScriptType();
stream.Read(guidBytes, 0, 16);
param.ID = new Guid(guidBytes);
param.Name = stream.ReadStr(97);
param.IsPublic = stream.ReadByte() != 0;
// Value
param.Value = stream.ReadVariant();
// Meta
param.Meta.Load(stream);
}
// For each node
for (int i = 0; i < nodesCount; i++)
{
var node = Nodes[i];
int valuesCnt = stream.ReadInt32();
int firstValueReadIdx = 0;
// Special case for missing nodes
if (node is DummyCustomNode customNode)
{
node = null;
// Values check
if (valuesCnt < 2)
throw new Exception("Missing custom nodes data.");
// Node typename check
object typeNameValue = stream.ReadVariant();
firstValueReadIdx = 1;
string typeName = typeNameValue as string ?? string.Empty;
// Find custom node archetype that matches this node type (it must be unique)
if (customNodes?.Archetypes != null && typeName.Length != 0)
{
NodeArchetype arch = null;
foreach (var nodeArchetype in customNodes.Archetypes)
{
if (string.Equals(Archetypes.Custom.GetNodeTypeName(nodeArchetype), typeName, StringComparison.OrdinalIgnoreCase))
{
arch = nodeArchetype;
break;
}
}
if (arch != null)
node = NodeFactory.CreateNode(customNode.ID, this, customNodes, arch);
}
// Fallback to the
if (node == null)
{
Editor.LogWarning(string.Format("Cannot find custom node archetype for {0}", typeName));
node = new MissingNode(customNode.ID, this, Archetypes.Custom.GroupID, customNode.Archetype.TypeID);
}
Nodes[i] = node;
// Store node typename in values container
node.Values[0] = typeName;
}
if (node is MissingNode)
{
// Read all values
Array.Resize(ref node.Values, valuesCnt);
for (int j = firstValueReadIdx; j < valuesCnt; j++)
node.Values[j] = stream.ReadVariant();
firstValueReadIdx = valuesCnt = node.Values.Length;
}
// Values
int nodeValuesCnt = node.Values?.Length ?? 0;
if (valuesCnt == nodeValuesCnt)
{
for (int j = firstValueReadIdx; j < valuesCnt; j++)
node.Values[j] = stream.ReadVariant();
}
else if ((node.Archetype.Flags & NodeFlags.VariableValuesSize) != 0)
{
node.Values = new object[valuesCnt];
for (int j = firstValueReadIdx; j < valuesCnt; j++)
node.Values[j] = stream.ReadVariant();
}
else
{
Editor.LogWarning(string.Format("Invalid node values. Loaded: {0}, expected: {1}. Type: {2}, {3}", valuesCnt, nodeValuesCnt, node.Archetype.Title, node.Archetype.TypeID));
object dummy;
for (int j = firstValueReadIdx; j < valuesCnt; j++)
{
dummy = stream.ReadVariant();
if (j < nodeValuesCnt &&
dummy != null &&
node.Values[j] != null &&
node.Values[j].GetType() == dummy.GetType())
{
node.Values[j] = dummy;
}
}
}
// Boxes
ushort boxesCount = stream.ReadUInt16();
for (int j = 0; j < boxesCount; j++)
{
var id = stream.ReadByte();
stream.ReadVariantType(); // Skip type
var connectionsCnt = stream.ReadUInt16();
ConnectionHint hint;
hint.NodeB = node.ID;
hint.BoxB = id;
for (int k = 0; k < connectionsCnt; k++)
{
uint targetNodeID = stream.ReadUInt32();
byte targetBoxID = stream.ReadByte();
hint.NodeA = targetNodeID;
hint.BoxA = targetBoxID;
tmpHints.Add(hint);
}
}
// Meta
node.Meta.Load(stream);
OnControlLoaded(node, SurfaceNodeActions.Load);
}
}
else
{
throw new Exception($"Unsupported graph version {version}.");
}
// Visject Meta
_meta.Load(stream);
// Setup connections
for (int i = 0; i < tmpHints.Count; i++)
{
var c = tmpHints[i];
var nodeA = FindNode(c.NodeA);
var nodeB = FindNode(c.NodeB);
if (nodeA == null || nodeB == null)
{
// Error
Editor.LogWarning("Invalid connected node id.");
continue;
}
var boxA = nodeA.GetBox(c.BoxA);
var boxB = nodeB.GetBox(c.BoxB);
if (boxA != null && boxB != null)
{
boxA.Connections.Add(boxB);
}
}
// Ending char
byte end = stream.ReadByte();
if (end != '\t')
throw new Exception("Invalid data.");
}
/// <summary>
/// Called when control gets added to the surface as spawn operation (eg. add new comment or add new node).
/// </summary>
/// <param name="control">The control.</param>
/// <param name="action">The action node.</param>
public virtual void OnControlSpawned(SurfaceControl control, SurfaceNodeActions action)
{
control.OnSpawned(action);
ControlSpawned?.Invoke(control);
if (Surface != null && control is SurfaceNode node)
Surface.OnNodeSpawned(node);
}
/// <summary>
/// Called when control gets removed from the surface as delete/cut operation (eg. remove comment or cut node).
/// </summary>
/// <param name="control">The control.</param>
/// <param name="action">The action node.</param>
public virtual void OnControlDeleted(SurfaceControl control, SurfaceNodeActions action)
{
ControlDeleted?.Invoke(control);
control.OnDeleted(action);
if (control is SurfaceNode node)
Surface.OnNodeDeleted(node);
}
/// <summary>
/// Called when control gets loaded and should be added to the surface. Handles surface nodes initialization.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="action">The action node.</param>
public virtual void OnControlLoaded(SurfaceControl control, SurfaceNodeActions action)
{
if (control is SurfaceNode node)
{
// Initialize node
OnNodeLoaded(node, action);
}
// Link control
control.OnLoaded(action);
control.Parent = RootControl;
}
/// <summary>
/// Called when node gets loaded and should be added to the surface. Creates node elements from the archetype.
/// </summary>
/// <param name="node">The node.</param>
/// <param name="action">The action node.</param>
public virtual void OnNodeLoaded(SurfaceNode node, SurfaceNodeActions action)
{
// Create child elements of the node based on it's archetype
int elementsCount = node.Archetype.Elements?.Length ?? 0;
for (int i = 0; i < elementsCount; i++)
{
// ReSharper disable once PossibleNullReferenceException
node.AddElement(node.Archetype.Elements[i]);
}
// Load metadata
var meta = node.Meta.GetEntry(11);
if (meta.Data != null)
{
var meta11 = Utils.ByteArrayToStructure<VisjectSurface.Meta11>(meta.Data);
node.Location = meta11.Position;
//node.IsSelected = meta11.Selected;
}
}
}
}