From 563eecebda40e7f46334a332807a3d52699df2f4 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 30 Jul 2021 12:37:36 +0200 Subject: [PATCH] Add support for Pack/Unpack Structure nodes and Enum constant in Anim Graph --- Source/Editor/Editor.cs | 1 + Source/Editor/SceneGraph/SceneGraphNode.cs | 4 +- Source/Editor/Surface/AnimGraphSurface.cs | 216 ++++++++++++++++++ Source/Editor/Surface/Archetypes/Constants.cs | 2 +- Source/Editor/Surface/Archetypes/Packing.cs | 5 +- Source/Engine/Content/Assets/VisualScript.cpp | 185 +-------------- Source/Engine/Content/Assets/VisualScript.h | 1 + Source/Engine/Visject/VisjectGraph.cpp | 181 ++++++++++++++- Source/Engine/Visject/VisjectGraph.h | 2 +- 9 files changed, 409 insertions(+), 188 deletions(-) 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);