Add Arrays to Visual Scripting

This commit is contained in:
Wojciech Figat
2021-11-17 19:58:29 +01:00
parent 649059eba1
commit e16c7f3ac4
10 changed files with 492 additions and 3 deletions

View File

@@ -0,0 +1,264 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using FlaxEngine;
using FlaxEditor.Scripting;
using FlaxEditor.Surface.Elements;
namespace FlaxEditor.Surface.Archetypes
{
/// <summary>
/// Contains archetypes for nodes from the Collections group.
/// </summary>
[HideInEditor]
public static class Collections
{
internal static ScriptType GetArrayItemType(Box box, ScriptType type)
{
if (type == ScriptType.Null)
return box.DefaultType;
return box.DefaultType != null ? new ScriptType(type.GetElementType()) : type;
}
/// <summary>
/// The nodes for that group.
/// </summary>
public static NodeArchetype[] Nodes =
{
new NodeArchetype
{
TypeID = 1,
Title = "Array Length",
Description = "Gets the length of the arary (amount of the items).",
AlternativeTitles = new[] { "Count" },
Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph,
Size = new Vector2(150, 20),
ConnectionsHints = ConnectionsHint.Array,
IndependentBoxes = new int[] { 0 },
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, "Array", true, null, 0),
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(int), 1)
}
},
new NodeArchetype
{
TypeID = 2,
Title = "Array Contains",
Description = "Returns the true if arrayt contains a given item, otherwise false.",
AlternativeTitles = new[] { "Contains" },
Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph,
Size = new Vector2(150, 40),
ConnectionsHints = ConnectionsHint.Array,
IndependentBoxes = new int[] { 0 },
DependentBoxes = new int[] { },
DependentBoxFilter = GetArrayItemType,
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, "Array", true, null, 0),
NodeElementArchetype.Factory.Input(1, "Item", true, typeof(object), 1),
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(bool), 2)
}
},
new NodeArchetype
{
TypeID = 3,
Title = "Array Index Of",
Description = "Returns the zero-based index of the item found in the array or -1 if nothing found.",
AlternativeTitles = new[] { "IndexOf", "Find" },
Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph,
Size = new Vector2(150, 40),
ConnectionsHints = ConnectionsHint.Array,
IndependentBoxes = new int[] { 0 },
DependentBoxes = new int[] { 1, 2 },
DependentBoxFilter = GetArrayItemType,
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, "Array", true, null, 0),
NodeElementArchetype.Factory.Input(1, "Item", true, typeof(object), 1),
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(int), 2)
}
},
new NodeArchetype
{
TypeID = 4,
Title = "Array Last Index Of",
Description = "Returns the zero-based index of the item found in the array or -1 if nothing found (searches from back to front).",
AlternativeTitles = new[] { "LastIndexOf", "FindLast" },
Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph,
Size = new Vector2(150, 40),
ConnectionsHints = ConnectionsHint.Array,
IndependentBoxes = new int[] { 0 },
DependentBoxes = new int[] { 1, 2 },
DependentBoxFilter = GetArrayItemType,
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, "Array", true, null, 0),
NodeElementArchetype.Factory.Input(1, "Item", true, typeof(object), 1),
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(int), 2)
}
},
new NodeArchetype
{
TypeID = 5,
Title = "Array Clear",
Description = "Clears array.",
Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph,
Size = new Vector2(150, 20),
ConnectionsHints = ConnectionsHint.Array,
IndependentBoxes = new int[] { 0 },
DependentBoxes = new int[] { 1 },
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, "Array", true, null, 0),
NodeElementArchetype.Factory.Output(0, string.Empty, null, 1)
}
},
new NodeArchetype
{
TypeID = 6,
Title = "Array Remove",
Description = "Removes the given item from the array.",
Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph,
Size = new Vector2(150, 40),
ConnectionsHints = ConnectionsHint.Array,
IndependentBoxes = new int[] { 0 },
DependentBoxes = new int[] { 1, 2 },
DependentBoxFilter = GetArrayItemType,
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, "Array", true, null, 0),
NodeElementArchetype.Factory.Input(1, "Item", true, typeof(object), 1),
NodeElementArchetype.Factory.Output(0, string.Empty, null, 2)
}
},
new NodeArchetype
{
TypeID = 7,
Title = "Array Remove At",
Description = "Removes an item at the given index from the array.",
Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph,
Size = new Vector2(170, 40),
DefaultValues = new object[] { 0 },
ConnectionsHints = ConnectionsHint.Array,
IndependentBoxes = new int[] { 0 },
DependentBoxes = new int[] { 2 },
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, "Array", true, null, 0),
NodeElementArchetype.Factory.Input(1, "Index", true, typeof(int), 1, 0),
NodeElementArchetype.Factory.Output(0, string.Empty, null, 2)
}
},
new NodeArchetype
{
TypeID = 8,
Title = "Array Add",
Description = "Adds the item to the array (to the end).",
Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph,
Size = new Vector2(150, 40),
ConnectionsHints = ConnectionsHint.Array,
IndependentBoxes = new int[] { 0 },
DependentBoxes = new int[] { 1, 2 },
DependentBoxFilter = GetArrayItemType,
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, "Array", true, null, 0),
NodeElementArchetype.Factory.Input(1, "Item", true, typeof(object), 1),
NodeElementArchetype.Factory.Output(0, string.Empty, null, 2)
}
},
new NodeArchetype
{
TypeID = 9,
Title = "Array Insert",
Description = "Inserts the item to the array at the given index.",
Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph,
Size = new Vector2(150, 60),
DefaultValues = new object[] { 0 },
ConnectionsHints = ConnectionsHint.Array,
IndependentBoxes = new int[] { 0 },
DependentBoxes = new int[] { 1, 3 },
DependentBoxFilter = GetArrayItemType,
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, "Array", true, null, 0),
NodeElementArchetype.Factory.Input(1, "Item", true, typeof(object), 1),
NodeElementArchetype.Factory.Input(2, "Index", true, typeof(int), 2, 0),
NodeElementArchetype.Factory.Output(0, string.Empty, null, 3)
}
},
new NodeArchetype
{
TypeID = 10,
Title = "Array Get",
Description = "Gets the item from the array (at the given zero-based index).",
Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph,
Size = new Vector2(150, 40),
DefaultValues = new object[] { 0 },
ConnectionsHints = ConnectionsHint.Array,
IndependentBoxes = new int[] { 0 },
DependentBoxes = new int[] { 2 },
DependentBoxFilter = GetArrayItemType,
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, "Array", true, null, 0),
NodeElementArchetype.Factory.Input(1, "Index", true, typeof(int), 1, 0),
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(object), 2)
}
},
new NodeArchetype
{
TypeID = 11,
Title = "Array Set",
Description = "Sets the item in the array (at the given zero-based index).",
Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph,
Size = new Vector2(150, 60),
DefaultValues = new object[] { 0 },
ConnectionsHints = ConnectionsHint.Array,
IndependentBoxes = new int[] { 0 },
DependentBoxes = new int[] { 2, 3 },
DependentBoxFilter = GetArrayItemType,
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, "Array", true, null, 0),
NodeElementArchetype.Factory.Input(1, "Index", true, typeof(int), 1, 0),
NodeElementArchetype.Factory.Input(2, "Item", true, typeof(object), 2),
NodeElementArchetype.Factory.Output(0, string.Empty, null, 3)
}
},
new NodeArchetype
{
TypeID = 12,
Title = "Array Sort",
Description = "Sorts the items in the array (ascending).",
Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph,
Size = new Vector2(150, 20),
ConnectionsHints = ConnectionsHint.Array,
IndependentBoxes = new int[] { 0 },
DependentBoxes = new int[] { 1 },
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, "Array", true, null, 0),
NodeElementArchetype.Factory.Output(0, string.Empty, null, 1)
}
},
new NodeArchetype
{
TypeID = 13,
Title = "Array Reverse",
Description = "Reverses the order of the items in the array.",
Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph,
Size = new Vector2(150, 20),
ConnectionsHints = ConnectionsHint.Array,
IndependentBoxes = new int[] { 0 },
DependentBoxes = new int[] { 1 },
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, "Array", true, null, 0),
NodeElementArchetype.Factory.Output(0, string.Empty, null, 1)
}
},
// first 100 IDs reserved for arrays
};
}
}

View File

@@ -308,6 +308,28 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(void), 2, true),
}
},
new NodeArchetype
{
TypeID = 7,
Title = "Array For Each",
AlternativeTitles = new[] { "foreach" },
Description = "Iterates over the array items.",
Flags = NodeFlags.VisualScriptGraph,
Size = new Vector2(160, 80),
ConnectionsHints = ConnectionsHint.Array,
IndependentBoxes = new int[] { 1 },
DependentBoxes = new int[] { 4, },
DependentBoxFilter = Collections.GetArrayItemType,
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, string.Empty, false, typeof(void), 0),
NodeElementArchetype.Factory.Input(1, "Array", false, null, 1),
NodeElementArchetype.Factory.Input(2, "Break", false, typeof(void), 2),
NodeElementArchetype.Factory.Output(0, "Loop", typeof(void), 3, true),
NodeElementArchetype.Factory.Output(1, "Item", typeof(object), 4),
NodeElementArchetype.Factory.Output(2, "Done", typeof(void), 5, true),
}
},
};
}
}

View File

@@ -200,6 +200,8 @@ namespace FlaxEditor.Surface.Elements
return "Vector";
if ((hint & ConnectionsHint.Scalar) == ConnectionsHint.Scalar)
return "Scalar";
if ((hint & ConnectionsHint.Array) == ConnectionsHint.Array)
return "Array";
return null;
}
@@ -236,6 +238,11 @@ namespace FlaxEditor.Surface.Elements
// Can
return true;
}
if ((connectionsHints & ConnectionsHint.Array) == ConnectionsHint.Array && type.IsArray)
{
// Can
return true;
}
if ((connectionsHints & ConnectionsHint.Vector) == ConnectionsHint.Vector)
{
var t = type.Type;

View File

@@ -52,6 +52,11 @@ namespace FlaxEditor.Surface
/// </summary>
Enum = 16,
/// <summary>
/// Allow any array types connections.
/// </summary>
Array = 32,
/// <summary>
/// Allow any scalar or vector numeric value types connections (bool, int, float, vector2, color..).
/// </summary>
@@ -60,7 +65,7 @@ namespace FlaxEditor.Surface
/// <summary>
/// All flags.
/// </summary>
All = Scalar | Vector | Enum | Anything | Value,
All = Scalar | Vector | Enum | Anything | Value | Array,
}
/// <summary>
@@ -152,6 +157,11 @@ namespace FlaxEditor.Surface
/// </summary>
public int[] DependentBoxes;
/// <summary>
/// Custom function to convert type for dependant box (optional).
/// </summary>
public Func<Elements.Box, ScriptType, ScriptType> DependentBoxFilter;
/// <summary>
/// Array with default elements descriptions.
/// </summary>

View File

@@ -168,6 +168,13 @@ namespace FlaxEditor.Surface
Color = new Color(237, 136, 64),
Archetypes = Archetypes.Flow.Nodes
},
new GroupArchetype
{
GroupID = 18,
Name = "Collections",
Color = new Color(110, 180, 81),
Archetypes = Archetypes.Collections.Nodes
},
};
/// <summary>

View File

@@ -476,7 +476,7 @@ namespace FlaxEditor.Surface
var b = GetBox(Archetype.DependentBoxes[i]);
if (b != null)
{
b.CurrentType = type;
b.CurrentType = Archetype.DependentBoxFilter != null ? Archetype.DependentBoxFilter(b, type) : type;
}
}

View File

@@ -156,6 +156,8 @@ namespace FlaxEditor.Surface
type = TypeUtils.GetType(typeName.Substring(0, typeName.Length - 1));
GetConnectionColor(type, hint, out color);
}
else if (type.IsArray)
GetConnectionColor(new ScriptType(type.GetElementType()), hint, out color);
else if (type.Type == typeof(void))
color = Colors.Impulse;
else if (type.Type == typeof(bool))

View File

@@ -1112,6 +1112,76 @@ void VisualScriptExecutor::ProcessGroupFlow(Box* boxBase, Node* node, Value& val
eatBox(node, boxBase->FirstConnection());
}
break;
}
// Array For Each
case 7:
{
const auto scope = ThreadStacks.Get().Stack->Scope;
int32 iteratorIndex = 0;
for (; iteratorIndex < scope->ReturnedValues.Count(); iteratorIndex++)
{
const auto& e = scope->ReturnedValues[iteratorIndex];
if (e.NodeId == node->ID && e.BoxId == 0)
break;
}
int32 arrayIndex = 0;
for (; iteratorIndex < scope->ReturnedValues.Count(); arrayIndex++)
{
const auto& e = scope->ReturnedValues[arrayIndex];
if (e.NodeId == node->ID && e.BoxId == 1)
break;
}
switch (boxBase->ID)
{
// Loop
case 0:
{
if (iteratorIndex == scope->ReturnedValues.Count())
{
if (arrayIndex == scope->ReturnedValues.Count())
arrayIndex++;
scope->ReturnedValues.AddOne();
}
if (arrayIndex == scope->ReturnedValues.Count())
scope->ReturnedValues.AddOne();
auto& iteratorValue = scope->ReturnedValues[iteratorIndex];
iteratorValue.NodeId = node->ID;
iteratorValue.BoxId = 0;
iteratorValue.Value = 0;
auto& arrayValue = scope->ReturnedValues[arrayIndex];
arrayValue.NodeId = node->ID;
arrayValue.BoxId = 1;
arrayValue.Value = tryGetValue(node->GetBox(1), Value::Null);
if (arrayValue.Value.Type.Type != VariantType::Array)
{
OnError(node, boxBase, String::Format(TEXT("Input value {0} is not an array."), arrayValue.Value));
return;
}
const int32 count = arrayValue.Value.AsArray().Count();
for (; iteratorValue.Value.AsInt < count; iteratorValue.Value.AsInt++)
{
boxBase = node->GetBox(3);
if (boxBase->HasConnection())
eatBox(node, boxBase->FirstConnection());
}
boxBase = node->GetBox(5);
if (boxBase->HasConnection())
eatBox(node, boxBase->FirstConnection());
break;
}
// Break
case 2:
// Reset loop iterator
if (iteratorIndex != scope->ReturnedValues.Count())
scope->ReturnedValues[iteratorIndex].Value.AsInt = MAX_int32 - 1;
break;
// Item
case 4:
if (iteratorIndex != scope->ReturnedValues.Count() && arrayIndex != scope->ReturnedValues.Count())
value = scope->ReturnedValues[arrayIndex].Value.AsArray()[(int32)scope->ReturnedValues[iteratorIndex].Value];
break;
}
break;
}
}
}

View File

@@ -3,6 +3,7 @@
#include "VisjectGraph.h"
#include "GraphUtilities.h"
#include "Engine/Core/Random.h"
#include "Engine/Core/Collections/Sorting.h"
#include "Engine/Core/Math/Vector4.h"
#include "Engine/Core/Math/Transform.h"
#include "Engine/Engine/GameplayGlobals.h"
@@ -16,6 +17,7 @@
#include "Engine/Utilities/StringConverter.h"
#define RAND Random::Rand()
#define ENSURE(condition, errorMsg) if (!(condition)) { OnError(node, box, errorMsg); return; }
VisjectExecutor::VisjectExecutor()
{
@@ -32,6 +34,7 @@ VisjectExecutor::VisjectExecutor()
_perGroupProcessCall[11] = &VisjectExecutor::ProcessGroupBitwise;
_perGroupProcessCall[12] = &VisjectExecutor::ProcessGroupComparisons;
_perGroupProcessCall[14] = &VisjectExecutor::ProcessGroupParticles;
_perGroupProcessCall[18] = &VisjectExecutor::ProcessGroupCollections;
}
VisjectExecutor::~VisjectExecutor()
@@ -1218,6 +1221,109 @@ void VisjectExecutor::ProcessGroupParticles(Box* box, Node* node, Value& value)
}
}
void VisjectExecutor::ProcessGroupCollections(Box* box, Node* node, Value& value)
{
if (node->TypeID < 100)
{
// Array
Variant v = tryGetValue(node->GetBox(0), Value::Null);
ENSURE(v.Type.Type == VariantType::Array, String::Format(TEXT("Input value {0} is not an array."), v));
auto& array = v.AsArray();
Box* b;
switch (node->TypeID)
{
// Count
case 1:
value = array.Count();
break;
// Contains
case 2:
value = array.Contains(tryGetValue(node->GetBox(1), Value::Null));
break;
// Find
case 3:
b = node->GetBox(1);
ENSURE(b->HasConnection(), TEXT("Missing value to find."));
value = array.Find(eatBox(b->GetParent<Node>(), b->FirstConnection()));
break;
// Find Last
case 4:
b = node->GetBox(1);
ENSURE(b->HasConnection(), TEXT("Missing value to find."));
value = array.FindLast(eatBox(b->GetParent<Node>(), b->FirstConnection()));
break;
// Clear
case 5:
array.Clear();
value = MoveTemp(v);
break;
// Remove
case 6:
b = node->GetBox(1);
ENSURE(b->HasConnection(), TEXT("Missing value to remove."));
array.Remove(eatBox(b->GetParent<Node>(), b->FirstConnection()));
value = MoveTemp(v);
break;
// Remove At
case 7:
{
const int32 index = (int32)tryGetValue(node->GetBox(1), 0, Value::Null);
ENSURE(index >= 0 && index < array.Count(), String::Format(TEXT("Array index {0} is out of range [0;{1}]."), index, array.Count() - 1));
array.RemoveAt(index);
value = MoveTemp(v);
break;
}
// Add
case 8:
b = node->GetBox(1);
ENSURE(b->HasConnection(), TEXT("Missing value to add."));
array.Add(eatBox(b->GetParent<Node>(), b->FirstConnection()));
value = MoveTemp(v);
break;
// Insert
case 9:
{
b = node->GetBox(1);
ENSURE(b->HasConnection(), TEXT("Missing value to add."));
const int32 index = (int32)tryGetValue(node->GetBox(2), 0, Value::Null);
ENSURE(index >= 0 && index <= array.Count(), String::Format(TEXT("Array index {0} is out of range [0;{1}]."), index, array.Count()));
array.Insert(index, eatBox(b->GetParent<Node>(), b->FirstConnection()));
value = MoveTemp(v);
break;
}
// Get
case 10:
{
const int32 index = (int32)tryGetValue(node->GetBox(1), 0, Value::Null);
ENSURE(index >= 0 && index < array.Count(), String::Format(TEXT("Array index {0} is out of range [0;{1}]."), index, array.Count() - 1));
value = MoveTemp(array[index]);
break;
}
// Set
case 11:
{
b = node->GetBox(2);
ENSURE(b->HasConnection(), TEXT("Missing value to set."));
const int32 index = (int32)tryGetValue(node->GetBox(1), 0, Value::Null);
ENSURE(index >= 0 && index < array.Count(), String::Format(TEXT("Array index {0} is out of range [0;{1}]."), index, array.Count() - 1));
array[index] = MoveTemp(eatBox(b->GetParent<Node>(), b->FirstConnection()));
value = MoveTemp(v);
break;
}
// Sort
case 12:
Sorting::QuickSort(array.Get(), array.Count());
value = MoveTemp(v);
break;
// Reverse
case 13:
array.Reverse();
value = MoveTemp(v);
break;
}
}
}
VisjectExecutor::Value VisjectExecutor::tryGetValue(Box* box, int32 defaultValueBoxIndex, const Value& defaultValue)
{
const auto parentNode = box->GetParent<Node>();

View File

@@ -226,7 +226,7 @@ public:
protected:
ProcessBoxHandler _perGroupProcessCall[18];
ProcessBoxHandler _perGroupProcessCall[19];
public:
@@ -256,6 +256,7 @@ public:
void ProcessGroupBitwise(Box* box, Node* node, Value& value);
void ProcessGroupComparisons(Box* box, Node* node, Value& value);
void ProcessGroupParticles(Box* box, Node* node, Value& value);
void ProcessGroupCollections(Box* box, Node* node, Value& value);
protected: