diff --git a/Source/Editor/Surface/Archetypes/Collections.cs b/Source/Editor/Surface/Archetypes/Collections.cs new file mode 100644 index 000000000..9ce2d811b --- /dev/null +++ b/Source/Editor/Surface/Archetypes/Collections.cs @@ -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 +{ + /// + /// Contains archetypes for nodes from the Collections group. + /// + [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; + } + + /// + /// The nodes for that group. + /// + 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 + }; + } +} diff --git a/Source/Editor/Surface/Archetypes/Flow.cs b/Source/Editor/Surface/Archetypes/Flow.cs index d7fc5dcc2..3e79bfe23 100644 --- a/Source/Editor/Surface/Archetypes/Flow.cs +++ b/Source/Editor/Surface/Archetypes/Flow.cs @@ -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), + } + }, }; } } diff --git a/Source/Editor/Surface/Elements/Box.cs b/Source/Editor/Surface/Elements/Box.cs index 271ff0c28..7ee0aaf07 100644 --- a/Source/Editor/Surface/Elements/Box.cs +++ b/Source/Editor/Surface/Elements/Box.cs @@ -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; diff --git a/Source/Editor/Surface/NodeArchetype.cs b/Source/Editor/Surface/NodeArchetype.cs index b2a859cc5..7b74128ef 100644 --- a/Source/Editor/Surface/NodeArchetype.cs +++ b/Source/Editor/Surface/NodeArchetype.cs @@ -52,6 +52,11 @@ namespace FlaxEditor.Surface /// Enum = 16, + /// + /// Allow any array types connections. + /// + Array = 32, + /// /// Allow any scalar or vector numeric value types connections (bool, int, float, vector2, color..). /// @@ -60,7 +65,7 @@ namespace FlaxEditor.Surface /// /// All flags. /// - All = Scalar | Vector | Enum | Anything | Value, + All = Scalar | Vector | Enum | Anything | Value | Array, } /// @@ -152,6 +157,11 @@ namespace FlaxEditor.Surface /// public int[] DependentBoxes; + /// + /// Custom function to convert type for dependant box (optional). + /// + public Func DependentBoxFilter; + /// /// Array with default elements descriptions. /// diff --git a/Source/Editor/Surface/NodeFactory.cs b/Source/Editor/Surface/NodeFactory.cs index 2888010f9..96d381669 100644 --- a/Source/Editor/Surface/NodeFactory.cs +++ b/Source/Editor/Surface/NodeFactory.cs @@ -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 + }, }; /// diff --git a/Source/Editor/Surface/SurfaceNode.cs b/Source/Editor/Surface/SurfaceNode.cs index d52a845c4..a3e1e6ddf 100644 --- a/Source/Editor/Surface/SurfaceNode.cs +++ b/Source/Editor/Surface/SurfaceNode.cs @@ -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; } } diff --git a/Source/Editor/Surface/SurfaceStyle.cs b/Source/Editor/Surface/SurfaceStyle.cs index 198fd7c67..f14aa8de4 100644 --- a/Source/Editor/Surface/SurfaceStyle.cs +++ b/Source/Editor/Surface/SurfaceStyle.cs @@ -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)) diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp index 3cd379d79..47b815e66 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -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; } } } diff --git a/Source/Engine/Visject/VisjectGraph.cpp b/Source/Engine/Visject/VisjectGraph.cpp index 1c0c79176..63a262d11 100644 --- a/Source/Engine/Visject/VisjectGraph.cpp +++ b/Source/Engine/Visject/VisjectGraph.cpp @@ -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(), 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(), 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(), 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(), 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(), 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(), 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(); diff --git a/Source/Engine/Visject/VisjectGraph.h b/Source/Engine/Visject/VisjectGraph.h index 4ffd97db7..4cbe47b2e 100644 --- a/Source/Engine/Visject/VisjectGraph.h +++ b/Source/Engine/Visject/VisjectGraph.h @@ -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: