Add support for Pack/Unpack Structure nodes and Enum constant in Anim Graph

This commit is contained in:
Wojtek Figat
2021-07-30 12:37:36 +02:00
parent c2de55f005
commit 563eecebda
9 changed files with 409 additions and 188 deletions

View File

@@ -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

View File

@@ -165,12 +165,13 @@ namespace FlaxEditor.SceneGraph
/// <summary>
/// The scene graph raycasting data container.
/// </summary>
[HideInEditor]
public struct RayCastData
{
/// <summary>
/// The raycasting optional flags.
/// </summary>
[Flags]
[Flags, HideInEditor]
public enum FlagTypes
{
/// <summary>
@@ -334,6 +335,7 @@ namespace FlaxEditor.SceneGraph
/// <summary>
/// The scene graph node state container. Used for Editor undo actions (eg. restoring deleted node).
/// </summary>
[HideInEditor]
public struct StateData
{
/// <summary>

View File

@@ -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<KeyValuePair<string, ushort>, 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<KeyValuePair<string, ushort>, 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<string, ushort>(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<NodeArchetype>(),
};
_cache.Add(groupKey, group);
}
// Add node to the group
((IList<NodeArchetype>)group.Archetypes).Add(node);
continue;
}
// Structure
if (scriptType.IsValueType)
{
if (scriptType.IsVoid)
continue;
// Create group archetype
var groupKey = new KeyValuePair<string, ushort>(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<NodeArchetype>(),
};
_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<NodeArchetype>)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<NodeArchetype>)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;
}
}
/// <summary>
/// The state machine editing context menu.
/// </summary>
@@ -152,6 +348,23 @@ namespace FlaxEditor.Surface
SetPrimaryMenu(menu);
}
/// <inheritdoc />
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();
}
/// <inheritdoc />
public override string GetTypeName(ScriptType type)
{
@@ -230,6 +443,8 @@ namespace FlaxEditor.Surface
/// <inheritdoc />
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();
}

View File

@@ -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[]
{

View File

@@ -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[]
{

View File

@@ -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;

View File

@@ -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;

View File

@@ -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:

View File

@@ -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);