diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs
index b5ae35cd0..9e0375c31 100644
--- a/Source/Editor/Editor.cs
+++ b/Source/Editor/Editor.cs
@@ -518,6 +518,7 @@ namespace FlaxEditor
// Cleanup
Undo.Dispose();
Surface.VisualScriptSurface.NodesCache.Clear();
+ Surface.AnimGraphSurface.NodesCache.Clear();
Instance = null;
// Invoke new instance if need to open a project
diff --git a/Source/Editor/SceneGraph/SceneGraphNode.cs b/Source/Editor/SceneGraph/SceneGraphNode.cs
index bc8fe84aa..6446cf662 100644
--- a/Source/Editor/SceneGraph/SceneGraphNode.cs
+++ b/Source/Editor/SceneGraph/SceneGraphNode.cs
@@ -165,12 +165,13 @@ namespace FlaxEditor.SceneGraph
///
/// The scene graph raycasting data container.
///
+ [HideInEditor]
public struct RayCastData
{
///
/// The raycasting optional flags.
///
- [Flags]
+ [Flags, HideInEditor]
public enum FlagTypes
{
///
@@ -334,6 +335,7 @@ namespace FlaxEditor.SceneGraph
///
/// The scene graph node state container. Used for Editor undo actions (eg. restoring deleted node).
///
+ [HideInEditor]
public struct StateData
{
///
diff --git a/Source/Editor/Surface/AnimGraphSurface.cs b/Source/Editor/Surface/AnimGraphSurface.cs
index abffc4432..96a8471cc 100644
--- a/Source/Editor/Surface/AnimGraphSurface.cs
+++ b/Source/Editor/Surface/AnimGraphSurface.cs
@@ -2,10 +2,14 @@
using System;
using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
using FlaxEditor.Content;
using FlaxEditor.Scripting;
using FlaxEditor.Surface.ContextMenu;
+using FlaxEditor.Surface.Elements;
using FlaxEngine;
+using FlaxEngine.GUI;
using Animation = FlaxEditor.Surface.Archetypes.Animation;
namespace FlaxEditor.Surface
@@ -72,6 +76,198 @@ namespace FlaxEditor.Surface
}
};
+ internal static class NodesCache
+ {
+ private static readonly object _locker = new object();
+ private static int _version;
+ private static Task _task;
+ private static VisjectCM _taskContextMenu;
+ private static Dictionary, GroupArchetype> _cache;
+
+ public static void Wait()
+ {
+ _task?.Wait();
+ }
+
+ public static void Clear()
+ {
+ Wait();
+
+ if (_cache != null && _cache.Count != 0)
+ {
+ OnCodeEditingTypesCleared();
+ }
+ }
+
+ public static void Get(VisjectCM contextMenu)
+ {
+ Wait();
+
+ lock (_locker)
+ {
+ if (_cache == null)
+ _cache = new Dictionary, GroupArchetype>();
+ contextMenu.LockChildrenRecursive();
+
+ // Check if has cached groups
+ if (_cache.Count != 0)
+ {
+ // Check if context menu doesn't have the recent cached groups
+ if (!contextMenu.Groups.Any(g => g.Archetype.Tag is int asInt && asInt == _version))
+ {
+ var groups = contextMenu.Groups.Where(g => g.Archetype.Tag is int).ToArray();
+ foreach (var g in groups)
+ contextMenu.RemoveGroup(g);
+ foreach (var g in _cache.Values)
+ contextMenu.AddGroup(g);
+ }
+ }
+ else
+ {
+ // Remove any old groups from context menu
+ var groups = contextMenu.Groups.Where(g => g.Archetype.Tag is int).ToArray();
+ foreach (var g in groups)
+ contextMenu.RemoveGroup(g);
+
+ // Register for scripting types reload
+ Editor.Instance.CodeEditing.TypesCleared += OnCodeEditingTypesCleared;
+
+ // Run caching on an async
+ _task = Task.Run(OnActiveContextMenuShowAsync);
+ _taskContextMenu = contextMenu;
+ }
+
+ contextMenu.UnlockChildrenRecursive();
+ }
+ }
+
+ private static void OnActiveContextMenuShowAsync()
+ {
+ Profiler.BeginEvent("Setup Anim Graph Context Menu (async)");
+
+ foreach (var scriptType in Editor.Instance.CodeEditing.All.Get())
+ {
+ if (!SurfaceUtils.IsValidVisualScriptType(scriptType))
+ continue;
+
+ // Skip Newtonsoft.Json stuff
+ var scriptTypeTypeName = scriptType.TypeName;
+ if (scriptTypeTypeName.StartsWith("Newtonsoft.Json."))
+ continue;
+ var scriptTypeName = scriptType.Name;
+
+ // Enum
+ if (scriptType.IsEnum)
+ {
+ // Create node archetype
+ var node = (NodeArchetype)Archetypes.Constants.Nodes[10].Clone();
+ node.DefaultValues[0] = Activator.CreateInstance(scriptType.Type);
+ node.Flags &= ~NodeFlags.NoSpawnViaGUI;
+ node.Title = scriptTypeName;
+ node.Description = scriptTypeTypeName;
+ var attributes = scriptType.GetAttributes(false);
+ var tooltipAttribute = (TooltipAttribute)attributes.FirstOrDefault(x => x is TooltipAttribute);
+ if (tooltipAttribute != null)
+ node.Description += "\n" + tooltipAttribute.Text;
+
+ // Create group archetype
+ var groupKey = new KeyValuePair(scriptTypeName, 2);
+ if (!_cache.TryGetValue(groupKey, out var group))
+ {
+ group = new GroupArchetype
+ {
+ GroupID = groupKey.Value,
+ Name = groupKey.Key,
+ Color = new Color(243, 156, 18),
+ Tag = _version,
+ Archetypes = new List(),
+ };
+ _cache.Add(groupKey, group);
+ }
+
+ // Add node to the group
+ ((IList)group.Archetypes).Add(node);
+ continue;
+ }
+
+ // Structure
+ if (scriptType.IsValueType)
+ {
+ if (scriptType.IsVoid)
+ continue;
+
+ // Create group archetype
+ var groupKey = new KeyValuePair(scriptTypeName, 4);
+ if (!_cache.TryGetValue(groupKey, out var group))
+ {
+ group = new GroupArchetype
+ {
+ GroupID = groupKey.Value,
+ Name = groupKey.Key,
+ Color = new Color(155, 89, 182),
+ Tag = _version,
+ Archetypes = new List(),
+ };
+ _cache.Add(groupKey, group);
+ }
+
+ var attributes = scriptType.GetAttributes(false);
+ var tooltipAttribute = (TooltipAttribute)attributes.FirstOrDefault(x => x is TooltipAttribute);
+
+ // Create Pack node archetype
+ var node = (NodeArchetype)Archetypes.Packing.Nodes[6].Clone();
+ node.DefaultValues[0] = scriptTypeTypeName;
+ node.Flags &= ~NodeFlags.NoSpawnViaGUI;
+ node.Title = "Pack " + scriptTypeName;
+ node.Description = scriptTypeTypeName;
+ if (tooltipAttribute != null)
+ node.Description += "\n" + tooltipAttribute.Text;
+ ((IList)group.Archetypes).Add(node);
+
+ // Create Unpack node archetype
+ node = (NodeArchetype)Archetypes.Packing.Nodes[13].Clone();
+ node.DefaultValues[0] = scriptTypeTypeName;
+ node.Flags &= ~NodeFlags.NoSpawnViaGUI;
+ node.Title = "Unpack " + scriptTypeName;
+ node.Description = scriptTypeTypeName;
+ if (tooltipAttribute != null)
+ node.Description += "\n" + tooltipAttribute.Text;
+ ((IList)group.Archetypes).Add(node);
+ }
+ }
+
+ // Add group to context menu (on a main thread)
+ FlaxEngine.Scripting.InvokeOnUpdate(() =>
+ {
+ lock (_locker)
+ {
+ _taskContextMenu.AddGroups(_cache.Values);
+ _taskContextMenu = null;
+ }
+ });
+
+ Profiler.EndEvent();
+
+ lock (_locker)
+ {
+ _task = null;
+ }
+ }
+
+ private static void OnCodeEditingTypesCleared()
+ {
+ Wait();
+
+ lock (_locker)
+ {
+ _cache.Clear();
+ _version++;
+ }
+
+ Editor.Instance.CodeEditing.TypesCleared -= OnCodeEditingTypesCleared;
+ }
+ }
+
///
/// The state machine editing context menu.
///
@@ -152,6 +348,23 @@ namespace FlaxEditor.Surface
SetPrimaryMenu(menu);
}
+ ///
+ protected override void OnShowPrimaryMenu(VisjectCM activeCM, Vector2 location, Box startBox)
+ {
+ Profiler.BeginEvent("Setup Anim Graph Context Menu");
+ NodesCache.Get(activeCM);
+ Profiler.EndEvent();
+
+ base.OnShowPrimaryMenu(activeCM, location, startBox);
+
+ activeCM.VisibleChanged += OnActiveContextMenuVisibleChanged;
+ }
+
+ private void OnActiveContextMenuVisibleChanged(Control activeCM)
+ {
+ NodesCache.Wait();
+ }
+
///
public override string GetTypeName(ScriptType type)
{
@@ -230,6 +443,8 @@ namespace FlaxEditor.Surface
///
public override void OnDestroy()
{
+ if (IsDisposing)
+ return;
if (_cmStateMachineMenu != null)
{
_cmStateMachineMenu.Dispose();
@@ -245,6 +460,7 @@ namespace FlaxEditor.Surface
_isRegisteredForScriptsReload = false;
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
}
+ NodesCache.Wait();
base.OnDestroy();
}
diff --git a/Source/Editor/Surface/Archetypes/Constants.cs b/Source/Editor/Surface/Archetypes/Constants.cs
index 0b8defee0..0a5083c89 100644
--- a/Source/Editor/Surface/Archetypes/Constants.cs
+++ b/Source/Editor/Surface/Archetypes/Constants.cs
@@ -354,7 +354,7 @@ namespace FlaxEditor.Surface.Archetypes
Title = "Enum",
Create = (id, context, arch, groupArch) => new EnumNode(id, context, arch, groupArch),
Description = "Enum constant value.",
- Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI,
+ Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph | NodeFlags.NoSpawnViaGUI,
Size = new Vector2(180, 20),
DefaultValues = new object[]
{
diff --git a/Source/Editor/Surface/Archetypes/Packing.cs b/Source/Editor/Surface/Archetypes/Packing.cs
index 0e9fa4ff0..83d321577 100644
--- a/Source/Editor/Surface/Archetypes/Packing.cs
+++ b/Source/Editor/Surface/Archetypes/Packing.cs
@@ -7,7 +7,6 @@ using System.Reflection;
using FlaxEditor.Scripting;
using FlaxEditor.Surface.Elements;
using FlaxEngine;
-using FlaxEngine.GUI;
namespace FlaxEditor.Surface.Archetypes
{
@@ -355,7 +354,7 @@ namespace FlaxEditor.Surface.Archetypes
Title = "Pack Structure",
Create = (id, context, arch, groupArch) => new PackStructureNode(id, context, arch, groupArch),
Description = "Makes the structure data to from the components.",
- Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI,
+ Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph | NodeFlags.NoSpawnViaGUI,
Size = new Vector2(180, 20),
DefaultValues = new object[]
{
@@ -465,7 +464,7 @@ namespace FlaxEditor.Surface.Archetypes
Title = "Unpack Structure",
Create = (id, context, arch, groupArch) => new UnpackStructureNode(id, context, arch, groupArch),
Description = "Breaks the structure data to allow extracting components from it.",
- Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI,
+ Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph | NodeFlags.NoSpawnViaGUI,
Size = new Vector2(180, 20),
DefaultValues = new object[]
{
diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp
index 59c84741f..6ce748ed4 100644
--- a/Source/Engine/Content/Assets/VisualScript.cpp
+++ b/Source/Engine/Content/Assets/VisualScript.cpp
@@ -119,6 +119,12 @@ void VisualScriptExecutor::Invoke(const Guid& scriptId, int32 nodeId, int32 boxI
stack.Stack = frame.PreviousFrame;
}
+void VisualScriptExecutor::OnError(Node* node, Box* box, const StringView& message)
+{
+ VisjectExecutor::OnError(node, box, message);
+ PrintStack(LogType::Error);
+}
+
VisjectExecutor::Value VisualScriptExecutor::eatBox(Node* caller, Box* box)
{
// Check if graph is looped or is too deep
@@ -172,12 +178,6 @@ void VisualScriptExecutor::ProcessGroupConstants(Box* box, Node* node, Value& va
{
switch (node->TypeID)
{
- // Enum
- case 11:
- {
- value = node->Values[0];
- break;
- }
default:
VisjectExecutor::ProcessGroupConstants(box, node, value);
break;
@@ -188,179 +188,6 @@ void VisualScriptExecutor::ProcessGroupPacking(Box* box, Node* node, Value& valu
{
switch (node->TypeID)
{
- // Pack Structure
- case 26:
- {
- // Find type
- const StringView typeName(node->Values[0]);
- const StringAsANSI<100> typeNameAnsi(typeName.Get(), typeName.Length());
- const StringAnsiView typeNameAnsiView(typeNameAnsi.Get(), typeName.Length());
- const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(typeNameAnsiView);
- if (!typeHandle)
- {
- const auto mclass = Scripting::FindClass(typeNameAnsiView);
- if (mclass)
- {
- // Fallback to C#-only types
- bool failed = false;
- auto instance = mclass->CreateInstance();
- value = instance;
- auto& layoutCache = node->Values[1];
- CHECK(layoutCache.Type.Type == VariantType::Blob);
- MemoryReadStream stream((byte*)layoutCache.AsBlob.Data, layoutCache.AsBlob.Length);
- const byte version = stream.ReadByte();
- if (version == 1)
- {
- int32 fieldsCount;
- stream.ReadInt32(&fieldsCount);
- for (int32 boxId = 1; boxId < node->Boxes.Count(); boxId++)
- {
- box = &node->Boxes[boxId];
- String fieldName;
- stream.ReadString(&fieldName, 11);
- VariantType fieldType;
- stream.ReadVariantType(&fieldType);
- if (box && box->HasConnection())
- {
- StringAsANSI<40> fieldNameAnsi(*fieldName, fieldName.Length());
- auto field = mclass->GetField(fieldNameAnsi.Get());
- if (field)
- {
- Variant fieldValue = eatBox(node, box->FirstConnection());
- field->SetValue(instance, MUtils::VariantToManagedArgPtr(fieldValue, field->GetType(), failed));
- }
- }
- }
- }
- }
- else if (typeName.HasChars())
- {
- LOG(Error, "Missing type '{0}'", typeName);
- PrintStack(LogType::Error);
- }
- return;
- }
- const ScriptingType& type = typeHandle.GetType();
-
- // Allocate structure data and initialize it with native constructor
- value.SetType(VariantType(VariantType::Structure, typeNameAnsiView));
-
- // Setup structure fields
- auto& layoutCache = node->Values[1];
- CHECK(layoutCache.Type.Type == VariantType::Blob);
- MemoryReadStream stream((byte*)layoutCache.AsBlob.Data, layoutCache.AsBlob.Length);
- const byte version = stream.ReadByte();
- if (version == 1)
- {
- int32 fieldsCount;
- stream.ReadInt32(&fieldsCount);
- for (int32 boxId = 1; boxId < node->Boxes.Count(); boxId++)
- {
- box = &node->Boxes[boxId];
- String fieldName;
- stream.ReadString(&fieldName, 11);
- VariantType fieldType;
- stream.ReadVariantType(&fieldType);
- if (box && box->HasConnection())
- {
- const Variant fieldValue = eatBox(node, box->FirstConnection());
- type.Struct.SetField(value.AsBlob.Data, fieldName, fieldValue);
- }
- }
- }
- break;
- }
- // Unpack Structure
- case 36:
- {
- // Get value with structure data
- const Variant structureValue = eatBox(node, node->GetBox(0)->FirstConnection());
- if (!node->GetBox(0)->HasConnection())
- return;
-
- // Find type
- const StringView typeName(node->Values[0]);
- const StringAsANSI<100> typeNameAnsi(typeName.Get(), typeName.Length());
- const StringAnsiView typeNameAnsiView(typeNameAnsi.Get(), typeName.Length());
- const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(typeNameAnsiView);
- if (!typeHandle)
- {
- const auto mclass = Scripting::FindClass(typeNameAnsiView);
- if (mclass)
- {
- // Fallback to C#-only types
- auto instance = (MonoObject*)structureValue;
- CHECK(instance);
- if (structureValue.Type.Type != VariantType::ManagedObject || mono_object_get_class(instance) != mclass->GetNative())
- {
- LOG(Error, "Cannot unpack value of type {0} to structure of type {1}", String(MUtils::GetClassFullname(instance)), typeName);
- PrintStack(LogType::Error);
- return;
- }
- auto& layoutCache = node->Values[1];
- CHECK(layoutCache.Type.Type == VariantType::Blob);
- MemoryReadStream stream((byte*)layoutCache.AsBlob.Data, layoutCache.AsBlob.Length);
- const byte version = stream.ReadByte();
- if (version == 1)
- {
- int32 fieldsCount;
- stream.ReadInt32(&fieldsCount);
- for (int32 boxId = 1; boxId < node->Boxes.Count(); boxId++)
- {
- String fieldName;
- stream.ReadString(&fieldName, 11);
- VariantType fieldType;
- stream.ReadVariantType(&fieldType);
- if (box->ID == boxId)
- {
- StringAsANSI<40> fieldNameAnsi(*fieldName, fieldName.Length());
- auto field = mclass->GetField(fieldNameAnsi.Get());
- if (field)
- value = MUtils::UnboxVariant(field->GetValueBoxed(instance));
- break;
- }
- }
- }
- }
- else if (typeName.HasChars())
- {
- LOG(Error, "Missing type '{0}'", typeName);
- PrintStack(LogType::Error);
- }
- return;
- }
- const ScriptingType& type = typeHandle.GetType();
- if (structureValue.Type.Type != VariantType::Structure || StringUtils::Compare(typeNameAnsi.Get(), structureValue.Type.TypeName) != 0)
- {
- LOG(Error, "Cannot unpack value of type {0} to structure of type {1}", structureValue.Type, typeName);
- PrintStack(LogType::Error);
- return;
- }
-
- // Read structure field
- auto& layoutCache = node->Values[1];
- CHECK(layoutCache.Type.Type == VariantType::Blob);
- MemoryReadStream stream((byte*)layoutCache.AsBlob.Data, layoutCache.AsBlob.Length);
- const byte version = stream.ReadByte();
- if (version == 1)
- {
- int32 fieldsCount;
- stream.ReadInt32(&fieldsCount);
- for (int32 boxId = 1; boxId < node->Boxes.Count(); boxId++)
- {
- String fieldName;
- stream.ReadString(&fieldName, 11);
- VariantType fieldType;
- stream.ReadVariantType(&fieldType);
- if (box->ID == boxId)
- {
- type.Struct.GetField(structureValue.AsBlob.Data, fieldName, value);
- break;
- }
- }
- }
- break;
- }
default:
VisjectExecutor::ProcessGroupPacking(box, node, value);
break;
diff --git a/Source/Engine/Content/Assets/VisualScript.h b/Source/Engine/Content/Assets/VisualScript.h
index e903a0b6a..c14a5bac0 100644
--- a/Source/Engine/Content/Assets/VisualScript.h
+++ b/Source/Engine/Content/Assets/VisualScript.h
@@ -39,6 +39,7 @@ public:
void Invoke(const Guid& scriptId, int32 nodeId, int32 boxId, const Guid& instanceId, Variant& result) const;
private:
+ void OnError(Node* node, Box* box, const StringView& message) override;
Value eatBox(Node* caller, Box* box) override;
Graph* GetCurrentGraph() const override;
diff --git a/Source/Engine/Visject/VisjectGraph.cpp b/Source/Engine/Visject/VisjectGraph.cpp
index 4030c2520..23d1359ba 100644
--- a/Source/Engine/Visject/VisjectGraph.cpp
+++ b/Source/Engine/Visject/VisjectGraph.cpp
@@ -8,7 +8,11 @@
#include "Engine/Engine/GameplayGlobals.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Level/Actor.h"
+#include "Engine/Scripting/ManagedCLR/MType.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
+#include "Engine/Scripting/ManagedCLR/MField.h"
+#include "Engine/Scripting/ManagedCLR/MUtils.h"
+#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Utilities/StringConverter.h"
#define RAND Random::Rand()
@@ -37,6 +41,7 @@ VisjectExecutor::~VisjectExecutor()
void VisjectExecutor::OnError(Node* node, Box* box, const StringView& message)
{
Error(node, box, message);
+ LOG_STR(Error, message);
}
void VisjectExecutor::ProcessGroupConstants(Box* box, Node* node, Value& value)
@@ -99,15 +104,16 @@ void VisjectExecutor::ProcessGroupConstants(Box* box, Node* node, Value& value)
break;
}
case 9:
- {
value = node->Values[0];
break;
- }
// PI
case 10:
value = PI;
break;
-
+ // Enum
+ case 11:
+ value = node->Values[0];
+ break;
default:
break;
}
@@ -506,6 +512,175 @@ void VisjectExecutor::ProcessGroupPacking(Box* box, Node* node, Value& value)
break;
}
break;
+ }
+ // Pack Structure
+ case 26:
+ {
+ // Find type
+ const StringView typeName(node->Values[0]);
+ const StringAsANSI<100> typeNameAnsi(typeName.Get(), typeName.Length());
+ const StringAnsiView typeNameAnsiView(typeNameAnsi.Get(), typeName.Length());
+ const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(typeNameAnsiView);
+ if (!typeHandle)
+ {
+ const auto mclass = Scripting::FindClass(typeNameAnsiView);
+ if (mclass)
+ {
+ // Fallback to C#-only types
+ bool failed = false;
+ auto instance = mclass->CreateInstance();
+ value = instance;
+ auto& layoutCache = node->Values[1];
+ CHECK(layoutCache.Type.Type == VariantType::Blob);
+ MemoryReadStream stream((byte*)layoutCache.AsBlob.Data, layoutCache.AsBlob.Length);
+ const byte version = stream.ReadByte();
+ if (version == 1)
+ {
+ int32 fieldsCount;
+ stream.ReadInt32(&fieldsCount);
+ for (int32 boxId = 1; boxId < node->Boxes.Count(); boxId++)
+ {
+ box = &node->Boxes[boxId];
+ String fieldName;
+ stream.ReadString(&fieldName, 11);
+ VariantType fieldType;
+ stream.ReadVariantType(&fieldType);
+ if (box && box->HasConnection())
+ {
+ StringAsANSI<40> fieldNameAnsi(*fieldName, fieldName.Length());
+ auto field = mclass->GetField(fieldNameAnsi.Get());
+ if (field)
+ {
+ Variant fieldValue = eatBox(node, box->FirstConnection());
+ field->SetValue(instance, MUtils::VariantToManagedArgPtr(fieldValue, field->GetType(), failed));
+ }
+ }
+ }
+ }
+ }
+ else if (typeName.HasChars())
+ {
+ OnError(node, box, String::Format(TEXT("Missing type '{0}'"), typeName));
+ }
+ return;
+ }
+ const ScriptingType& type = typeHandle.GetType();
+
+ // Allocate structure data and initialize it with native constructor
+ value.SetType(VariantType(VariantType::Structure, typeNameAnsiView));
+
+ // Setup structure fields
+ auto& layoutCache = node->Values[1];
+ CHECK(layoutCache.Type.Type == VariantType::Blob);
+ MemoryReadStream stream((byte*)layoutCache.AsBlob.Data, layoutCache.AsBlob.Length);
+ const byte version = stream.ReadByte();
+ if (version == 1)
+ {
+ int32 fieldsCount;
+ stream.ReadInt32(&fieldsCount);
+ for (int32 boxId = 1; boxId < node->Boxes.Count(); boxId++)
+ {
+ box = &node->Boxes[boxId];
+ String fieldName;
+ stream.ReadString(&fieldName, 11);
+ VariantType fieldType;
+ stream.ReadVariantType(&fieldType);
+ if (box && box->HasConnection())
+ {
+ const Variant fieldValue = eatBox(node, box->FirstConnection());
+ type.Struct.SetField(value.AsBlob.Data, fieldName, fieldValue);
+ }
+ }
+ }
+ break;
+ }
+ // Unpack Structure
+ case 36:
+ {
+ // Get value with structure data
+ const Variant structureValue = eatBox(node, node->GetBox(0)->FirstConnection());
+ if (!node->GetBox(0)->HasConnection())
+ return;
+
+ // Find type
+ const StringView typeName(node->Values[0]);
+ const StringAsANSI<100> typeNameAnsi(typeName.Get(), typeName.Length());
+ const StringAnsiView typeNameAnsiView(typeNameAnsi.Get(), typeName.Length());
+ const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(typeNameAnsiView);
+ if (!typeHandle)
+ {
+ const auto mclass = Scripting::FindClass(typeNameAnsiView);
+ if (mclass)
+ {
+ // Fallback to C#-only types
+ auto instance = (MonoObject*)structureValue;
+ CHECK(instance);
+ if (structureValue.Type.Type != VariantType::ManagedObject || mono_object_get_class(instance) != mclass->GetNative())
+ {
+ OnError(node, box, String::Format(TEXT("Cannot unpack value of type {0} to structure of type {1}"), String(MUtils::GetClassFullname(instance)), typeName));
+ return;
+ }
+ auto& layoutCache = node->Values[1];
+ CHECK(layoutCache.Type.Type == VariantType::Blob);
+ MemoryReadStream stream((byte*)layoutCache.AsBlob.Data, layoutCache.AsBlob.Length);
+ const byte version = stream.ReadByte();
+ if (version == 1)
+ {
+ int32 fieldsCount;
+ stream.ReadInt32(&fieldsCount);
+ for (int32 boxId = 1; boxId < node->Boxes.Count(); boxId++)
+ {
+ String fieldName;
+ stream.ReadString(&fieldName, 11);
+ VariantType fieldType;
+ stream.ReadVariantType(&fieldType);
+ if (box->ID == boxId)
+ {
+ StringAsANSI<40> fieldNameAnsi(*fieldName, fieldName.Length());
+ auto field = mclass->GetField(fieldNameAnsi.Get());
+ if (field)
+ value = MUtils::UnboxVariant(field->GetValueBoxed(instance));
+ break;
+ }
+ }
+ }
+ }
+ else if (typeName.HasChars())
+ {
+ OnError(node, box, String::Format(TEXT("Missing type '{0}'"), typeName));
+ }
+ return;
+ }
+ const ScriptingType& type = typeHandle.GetType();
+ if (structureValue.Type.Type != VariantType::Structure || StringUtils::Compare(typeNameAnsi.Get(), structureValue.Type.TypeName) != 0)
+ {
+ OnError(node, box, String::Format(TEXT("Cannot unpack value of type {0} to structure of type {1}"), structureValue.Type, typeName));
+ return;
+ }
+
+ // Read structure field
+ auto& layoutCache = node->Values[1];
+ CHECK(layoutCache.Type.Type == VariantType::Blob);
+ MemoryReadStream stream((byte*)layoutCache.AsBlob.Data, layoutCache.AsBlob.Length);
+ const byte version = stream.ReadByte();
+ if (version == 1)
+ {
+ int32 fieldsCount;
+ stream.ReadInt32(&fieldsCount);
+ for (int32 boxId = 1; boxId < node->Boxes.Count(); boxId++)
+ {
+ String fieldName;
+ stream.ReadString(&fieldName, 11);
+ VariantType fieldType;
+ stream.ReadVariantType(&fieldType);
+ if (box->ID == boxId)
+ {
+ type.Struct.GetField(structureValue.AsBlob.Data, fieldName, value);
+ break;
+ }
+ }
+ }
+ break;
}
// Mask X, Y, Z, W
case 40:
diff --git a/Source/Engine/Visject/VisjectGraph.h b/Source/Engine/Visject/VisjectGraph.h
index d38234c6d..4ffd97db7 100644
--- a/Source/Engine/Visject/VisjectGraph.h
+++ b/Source/Engine/Visject/VisjectGraph.h
@@ -246,7 +246,7 @@ public:
public:
- void OnError(Node* node, Box* box, const StringView& message);
+ virtual void OnError(Node* node, Box* box, const StringView& message);
void ProcessGroupConstants(Box* box, Node* node, Value& value);
void ProcessGroupMath(Box* box, Node* node, Value& value);