From 158c29e5989d807e2012a0facfdaea29ad5b5a0a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 27 Apr 2022 22:47:54 +0200 Subject: [PATCH] Add **Dictionaries to Visual Scripting** --- Source/Editor/Scripting/ScriptType.cs | 32 ++-- Source/Editor/Scripting/TypeUtils.cs | 69 ++++++++ .../Editor/Surface/Archetypes/Collections.cs | 145 ++++++++++++++++- Source/Editor/Surface/Archetypes/Constants.cs | 147 +++++++++++++++++- Source/Editor/Surface/Archetypes/Flow.cs | 23 +++ Source/Editor/Surface/Elements/Box.cs | 19 +-- Source/Editor/Surface/NodeArchetype.cs | 7 +- Source/Editor/Surface/SurfaceUtils.cs | 7 +- Source/Editor/Surface/VisjectSurface.cs | 12 +- Source/Editor/Utilities/VariantUtils.cs | 66 ++++++-- .../Windows/Assets/VisualScriptWindow.cs | 88 +++++++---- Source/Editor/Windows/GameCookerWindow.cs | 2 + Source/Engine/Content/Assets/VisualScript.cpp | 79 ++++++++++ Source/Engine/Core/Collections/Dictionary.h | 7 +- Source/Engine/Core/Types/Variant.cpp | 8 +- Source/Engine/Core/Types/Variant.h | 1 + .../InternalCalls/ManagedDictionary.h | 31 +++- Source/Engine/Scripting/ManagedCLR/MUtils.cpp | 146 ++++++++++++++++- Source/Engine/Serialization/Stream.cpp | 6 +- Source/Engine/Visject/VisjectGraph.cpp | 71 +++++++++ 20 files changed, 852 insertions(+), 114 deletions(-) diff --git a/Source/Editor/Scripting/ScriptType.cs b/Source/Editor/Scripting/ScriptType.cs index 11fdd5fb7..41f7540ed 100644 --- a/Source/Editor/Scripting/ScriptType.cs +++ b/Source/Editor/Scripting/ScriptType.cs @@ -714,7 +714,7 @@ namespace FlaxEditor.Scripting public IScriptType IScriptType => _custom; /// - /// Gets a type name (eg. name of the class or enum without leading namespace). + /// Gets a type display name (eg. name of the class or enum without leading namespace). /// public string Name { @@ -724,19 +724,7 @@ namespace FlaxEditor.Scripting return _custom.Name; if (_managed == null) return string.Empty; - if (_managed == typeof(float)) - return "Float"; - if (_managed == typeof(int)) - return "Int"; - if (_managed == typeof(uint)) - return "Uint"; - if (_managed == typeof(short)) - return "Int16"; - if (_managed == typeof(ushort)) - return "Uint16"; - if (_managed == typeof(bool)) - return "Bool"; - return _managed.Name; + return _managed.GetTypeDisplayName(); } } @@ -790,6 +778,11 @@ namespace FlaxEditor.Scripting /// public bool IsArray => _managed != null ? _managed.IsArray : _custom != null && _custom.IsArray; + /// + /// Gets a value indicating whether the type is a dictionary. + /// + public bool IsDictionary => IsGenericType && GetGenericTypeDefinition() == typeof(Dictionary<,>); + /// /// Gets a value indicating whether the type is a value type (basic type, enumeration or a structure). /// @@ -981,7 +974,7 @@ namespace FlaxEditor.Scripting public override string ToString() { if (_managed != null) - return _managed.FullName ?? string.Empty; + return _managed.GetTypeDisplayName() ?? string.Empty; if (_custom != null) return _custom.TypeName; return ""; @@ -1122,6 +1115,15 @@ namespace FlaxEditor.Scripting throw new NotImplementedException("TODO: Script.Type.MakeArrayType for custom types"); } + /// + /// Returns a type object that represents a dictionary of the key and value types. + /// + /// A type object representing a dictionary of the key and value types. + public static ScriptType MakeDictionaryType(ScriptType keyType, ScriptType valueType) + { + return new ScriptType(typeof(Dictionary<,>).MakeGenericType(TypeUtils.GetType(keyType), TypeUtils.GetType(valueType))); + } + /// /// Searches for the specified members of the specified member type, using the specified binding constraints. /// diff --git a/Source/Editor/Scripting/TypeUtils.cs b/Source/Editor/Scripting/TypeUtils.cs index b2b71d370..4bf02911c 100644 --- a/Source/Editor/Scripting/TypeUtils.cs +++ b/Source/Editor/Scripting/TypeUtils.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Reflection; +using System.Text; using FlaxEngine; namespace FlaxEditor.Scripting @@ -33,6 +34,74 @@ namespace FlaxEditor.Scripting return o != null ? new ScriptType(o.GetType()) : ScriptType.Null; } + /// + /// Gets the typename full name. + /// + /// The type. + /// The full typename of the type. + public static string GetTypeName(this Type type) + { + if (type.IsGenericType) + { + // For generic types (eg. Dictionary) FullName returns generic parameter types with fully qualified name so simplify it manually + var sb = new StringBuilder(); + sb.Append(type.Namespace); + sb.Append('.'); + sb.Append(type.Name); + sb.Append('['); + var genericArgs = type.GetGenericArguments(); + for (var i = 0; i < genericArgs.Length; i++) + { + if (i != 0) + sb.Append(','); + sb.Append(genericArgs[i].GetTypeName()); + } + sb.Append(']'); + return sb.ToString(); + } + return type.FullName; + } + + /// + /// Gets the typename name for UI. + /// + /// The type. + /// The display of the type. + public static string GetTypeDisplayName(this Type type) + { + // Special display for in-built basic types + if (type == typeof(bool)) + return "Bool"; + if (type == typeof(float)) + return "Float"; + if (type == typeof(int)) + return "Int"; + if (type == typeof(uint)) + return "Uint"; + + // For generic types (eg. Dictionary) Name returns generic parameter types with fully qualified name so simplify it manually + if (type.IsGenericType) + { + var sb = new StringBuilder(); + var name = type.Name; + var idx = name.IndexOf('`'); + sb.Append(idx != -1 ? name.Substring(0, idx) : name); + sb.Append('<'); + var genericArgs = type.GetGenericArguments(); + for (var i = 0; i < genericArgs.Length; i++) + { + if (i != 0) + sb.Append(", "); + sb.Append(genericArgs[i].GetTypeDisplayName()); + } + sb.Append('>'); + return sb.ToString(); + } + + // Default name + return type.Name; + } + /// /// Gets the default value for the given type (can be value type or reference type). /// diff --git a/Source/Editor/Surface/Archetypes/Collections.cs b/Source/Editor/Surface/Archetypes/Collections.cs index d09b02a69..3880d0975 100644 --- a/Source/Editor/Surface/Archetypes/Collections.cs +++ b/Source/Editor/Surface/Archetypes/Collections.cs @@ -19,6 +19,15 @@ namespace FlaxEditor.Surface.Archetypes return box.DefaultType != null ? new ScriptType(type.GetElementType()) : type; } + internal static ScriptType GetDictionaryItemType(Box box, ScriptType type) + { + if (type == ScriptType.Null) + return box.DefaultType; + // BoxID = 1 is Key + // BoxID = 2 is Value + return box.DefaultType != null ? new ScriptType(type.GetGenericArguments()[box.ID == 1 ? 0 : 1]) : type; + } + /// /// The nodes for that group. /// @@ -28,7 +37,7 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 1, Title = "Array Length", - Description = "Gets the length of the arary (amount of the items).", + Description = "Gets the length of the array (amount of the items).", AlternativeTitles = new[] { "Count" }, Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph, Size = new Vector2(150, 20), @@ -44,7 +53,7 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 2, Title = "Array Contains", - Description = "Returns the true if arrayt contains a given item, otherwise false.", + Description = "Returns the true if array contains a given item, otherwise false.", AlternativeTitles = new[] { "Contains" }, Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph, Size = new Vector2(150, 40), @@ -277,6 +286,138 @@ namespace FlaxEditor.Surface.Archetypes } }, // first 100 IDs reserved for arrays + + new NodeArchetype + { + TypeID = 101, + Title = "Dictionary Count", + Description = "Gets the size of the dictionary (amount of the items).", + AlternativeTitles = new[] { "size" }, + Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph, + Size = new Vector2(180, 20), + ConnectionsHints = ConnectionsHint.Dictionary, + IndependentBoxes = new int[] { 0 }, + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, "Dictionary", true, null, 0), + NodeElementArchetype.Factory.Output(0, string.Empty, typeof(int), 1) + } + }, + new NodeArchetype + { + TypeID = 102, + Title = "Dictionary Contains Key", + Description = "Returns the true if dictionary contains a given key, otherwise false.", + AlternativeTitles = new[] { "Contains" }, + Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph, + Size = new Vector2(240, 40), + DefaultValues = new object[] { 0 }, + ConnectionsHints = ConnectionsHint.Dictionary, + IndependentBoxes = new int[] { 0 }, + DependentBoxes = new int[] { 1 }, + DependentBoxFilter = GetDictionaryItemType, + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, "Dictionary", true, null, 0), + NodeElementArchetype.Factory.Input(1, "Key", true, typeof(object), 1, 0), + NodeElementArchetype.Factory.Output(0, string.Empty, typeof(bool), 3) + } + }, + new NodeArchetype + { + TypeID = 103, + Title = "Dictionary Contains Value", + Description = "Returns the true if dictionary contains a given value, otherwise false.", + AlternativeTitles = new[] { "Contains" }, + Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph, + Size = new Vector2(240, 40), + DefaultValues = new object[] { 0 }, + ConnectionsHints = ConnectionsHint.Dictionary, + IndependentBoxes = new int[] { 0 }, + DependentBoxes = new int[] { 2 }, + DependentBoxFilter = GetDictionaryItemType, + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, "Dictionary", true, null, 0), + NodeElementArchetype.Factory.Input(1, "Value", true, typeof(object), 2, 0), + NodeElementArchetype.Factory.Output(0, string.Empty, typeof(bool), 3) + } + }, + new NodeArchetype + { + TypeID = 104, + Title = "Dictionary Clear", + Description = "Clears dictionary.", + Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph, + Size = new Vector2(180, 20), + ConnectionsHints = ConnectionsHint.Dictionary, + IndependentBoxes = new int[] { 0 }, + DependentBoxes = new int[] { 1 }, + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, "Dictionary", true, null, 0), + NodeElementArchetype.Factory.Output(0, string.Empty, null, 1) + } + }, + new NodeArchetype + { + TypeID = 105, + Title = "Dictionary Remove", + Description = "Removes the given item from the dictionary (by key).", + Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph, + Size = new Vector2(180, 40), + DefaultValues = new object[] { 0 }, + ConnectionsHints = ConnectionsHint.Dictionary, + IndependentBoxes = new int[] { 0 }, + DependentBoxes = new int[] { 1, 3 }, + DependentBoxFilter = GetDictionaryItemType, + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, "Dictionary", true, null, 0), + NodeElementArchetype.Factory.Input(1, "Key", true, typeof(object), 1, 0), + NodeElementArchetype.Factory.Output(0, string.Empty, null, 3) + } + }, + new NodeArchetype + { + TypeID = 106, + Title = "Dictionary Set", + Description = "Set the item in the dictionary (a pair of key and value). Adds or updates the pair.", + Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph, + Size = new Vector2(180, 60), + DefaultValues = new object[] { 0, 0 }, + ConnectionsHints = ConnectionsHint.Dictionary, + IndependentBoxes = new int[] { 0 }, + DependentBoxes = new int[] { 1, 2, 3 }, + DependentBoxFilter = GetDictionaryItemType, + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, "Dictionary", true, null, 0), + NodeElementArchetype.Factory.Input(1, "Key", true, typeof(object), 1, 0), + NodeElementArchetype.Factory.Input(2, "Value", true, typeof(object), 2, 1), + NodeElementArchetype.Factory.Output(0, string.Empty, null, 3) + } + }, + new NodeArchetype + { + TypeID = 107, + Title = "Dictionary Get", + Description = "Gets the item from the dictionary (a pair of key and value).", + Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph, + Size = new Vector2(180, 40), + DefaultValues = new object[] { 0 }, + ConnectionsHints = ConnectionsHint.Dictionary, + IndependentBoxes = new int[] { 0 }, + DependentBoxes = new int[] { 1, 3 }, + DependentBoxFilter = GetDictionaryItemType, + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, "Dictionary", true, null, 0), + NodeElementArchetype.Factory.Input(1, "Key", true, typeof(object), 1, 0), + NodeElementArchetype.Factory.Output(0, string.Empty, typeof(object), 3) + } + }, + // second 100 IDs reserved for dictionaries }; } } diff --git a/Source/Editor/Surface/Archetypes/Constants.cs b/Source/Editor/Surface/Archetypes/Constants.cs index 8e1d98e1f..9d4a30459 100644 --- a/Source/Editor/Surface/Archetypes/Constants.cs +++ b/Source/Editor/Surface/Archetypes/Constants.cs @@ -132,7 +132,7 @@ namespace FlaxEditor.Surface.Archetypes Array.Copy(prev, next, Mathf.Min(prev.Length, next.Length)); SetValue(0, next); } - + public override void OnSurfaceCanEditChanged(bool canEdit) { base.OnSurfaceCanEditChanged(canEdit); @@ -141,7 +141,7 @@ namespace FlaxEditor.Surface.Archetypes _addButton.Enabled = canEdit; _removeButton.Enabled = canEdit; } - + public override void OnDestroy() { _output = null; @@ -179,7 +179,7 @@ namespace FlaxEditor.Surface.Archetypes break; RemoveElement(box); } - + var canEdit = Surface.CanEdit; _typePicker.Enabled = canEdit; _addButton.Enabled = count < countMax && canEdit; @@ -212,6 +212,132 @@ namespace FlaxEditor.Surface.Archetypes } } + private class DictionaryNode : SurfaceNode + { + private OutputBox _output; + private TypePickerControl _keyTypePicker; + private TypePickerControl _valueTypePicker; + private bool _isUpdatingUI; + + public DictionaryNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) + : base(id, context, nodeArch, groupArch) + { + } + + public override void OnValuesChanged() + { + UpdateUI(); + + base.OnValuesChanged(); + } + + public override void OnLoaded() + { + base.OnLoaded(); + + _output = (OutputBox)Elements[0]; + _keyTypePicker = new TypePickerControl + { + Bounds = new Rectangle(FlaxEditor.Surface.Constants.NodeMarginX, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize, 160, 16), + Parent = this, + }; + _keyTypePicker.ValueChanged += OnKeyTypeChanged; + _valueTypePicker = new TypePickerControl + { + Bounds = new Rectangle(_keyTypePicker.X, _keyTypePicker.Y + FlaxEditor.Surface.Constants.LayoutOffsetY, _keyTypePicker.Width, _keyTypePicker.Height), + Parent = this, + }; + _valueTypePicker.ValueChanged += OnValueTypeChanged; + + UpdateUI(); + } + + private void OnKeyTypeChanged() + { + if (_isUpdatingUI) + return; + SetValue(0, _keyTypePicker.ValueTypeName); + } + + private void OnValueTypeChanged() + { + if (_isUpdatingUI) + return; + SetValue(1, _valueTypePicker.ValueTypeName); + } + + public override void OnSurfaceCanEditChanged(bool canEdit) + { + base.OnSurfaceCanEditChanged(canEdit); + + _keyTypePicker.Enabled = canEdit; + _valueTypePicker.Enabled = canEdit; + } + + public override void OnDestroy() + { + _output = null; + _keyTypePicker = null; + _valueTypePicker = null; + + base.OnDestroy(); + } + + private void UpdateUI() + { + if (_isUpdatingUI) + return; + var keyTypeName = (string)Values[0]; + var valueTypeName = (string)Values[1]; + var keyType = TypeUtils.GetType(keyTypeName); + var valueType = TypeUtils.GetType(valueTypeName); + if (keyType == ScriptType.Null) + { + Editor.LogError("Missing type " + keyTypeName); + keyType = ScriptType.Object; + } + if (valueType == ScriptType.Null) + { + Editor.LogError("Missing type " + valueTypeName); + valueType = ScriptType.Object; + } + var dictionaryType = ScriptType.MakeDictionaryType(keyType, valueType); + + _isUpdatingUI = true; + _keyTypePicker.Value = keyType; + _valueTypePicker.Value = valueType; + _output.CurrentType = dictionaryType; + _isUpdatingUI = false; + + var canEdit = Surface.CanEdit; + _keyTypePicker.Enabled = canEdit; + _valueTypePicker.Enabled = canEdit; + + Title = Surface.GetTypeName(dictionaryType); + _keyTypePicker.Width = 160.0f; + _valueTypePicker.Width = 160.0f; + ResizeAuto(); + _keyTypePicker.Width = Width - 30; + _valueTypePicker.Width = Width - 30; + } + + private object GetBoxValue(InputBox box) + { + var array = (Array)Values[0]; + return array.GetValue(box.ID - 1); + } + + private void SetBoxValue(InputBox box, object value) + { + if (_isDuringValuesEditing || !Surface.CanEdit) + return; + var array = (Array)Values[0]; + array = (Array)array.Clone(); + array.SetValue(value, box.ID - 1); + SetValue(0, array); + } + } + /// /// The nodes for that group. /// @@ -236,12 +362,12 @@ namespace FlaxEditor.Surface.Archetypes TryParseText = (string filterText, out object[] data) => { data = null; - if (filterText == "true") + if (string.Equals(filterText, bool.TrueString, StringComparison.OrdinalIgnoreCase)) { data = new object[] { true }; return true; } - if (filterText == "false") + if (string.Equals(filterText, bool.FalseString, StringComparison.OrdinalIgnoreCase)) { data = new object[] { false }; return true; @@ -544,6 +670,17 @@ namespace FlaxEditor.Surface.Archetypes DefaultValues = new object[] { new int[] { 0, 1, 2 } }, Elements = new[] { NodeElementArchetype.Factory.Output(0, string.Empty, null, 0) } }, + new NodeArchetype + { + TypeID = 14, + Title = "Dictionary", + Create = (id, context, arch, groupArch) => new DictionaryNode(id, context, arch, groupArch), + Description = "Creates an empty dictionary.", + Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph, + Size = new Vector2(150, 40), + DefaultValues = new object[] { typeof(int).FullName, typeof(string).FullName }, + Elements = new[] { NodeElementArchetype.Factory.Output(0, string.Empty, null, 0) } + }, }; /// diff --git a/Source/Editor/Surface/Archetypes/Flow.cs b/Source/Editor/Surface/Archetypes/Flow.cs index f955a0197..787975625 100644 --- a/Source/Editor/Surface/Archetypes/Flow.cs +++ b/Source/Editor/Surface/Archetypes/Flow.cs @@ -331,6 +331,29 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Output(3, "Done", typeof(void), 6, true), } }, + new NodeArchetype + { + TypeID = 8, + Title = "Dictionary For Each", + AlternativeTitles = new[] { "foreach" }, + Description = "Iterates over the dictionary items.", + Flags = NodeFlags.VisualScriptGraph, + Size = new Vector2(180, 80), + ConnectionsHints = ConnectionsHint.Dictionary, + IndependentBoxes = new int[] { 4 }, + DependentBoxes = new int[] { 1, 2, }, + DependentBoxFilter = Collections.GetDictionaryItemType, + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, string.Empty, false, typeof(void), 0), + NodeElementArchetype.Factory.Input(1, "Dictionary", true, null, 4), + NodeElementArchetype.Factory.Input(2, "Break", false, typeof(void), 5), + NodeElementArchetype.Factory.Output(0, "Loop", typeof(void), 3, true), + NodeElementArchetype.Factory.Output(1, "Key", typeof(object), 1), + NodeElementArchetype.Factory.Output(2, "Value", typeof(object), 2), + NodeElementArchetype.Factory.Output(3, "Done", typeof(void), 6, true), + } + }, }; } } diff --git a/Source/Editor/Surface/Elements/Box.cs b/Source/Editor/Surface/Elements/Box.cs index 19c936063..cf133d4c9 100644 --- a/Source/Editor/Surface/Elements/Box.cs +++ b/Source/Editor/Surface/Elements/Box.cs @@ -202,6 +202,8 @@ namespace FlaxEditor.Surface.Elements return "Scalar"; if ((hint & ConnectionsHint.Array) == ConnectionsHint.Array) return "Array"; + if ((hint & ConnectionsHint.Dictionary) == ConnectionsHint.Dictionary) + return "Dictionary"; return null; } @@ -215,7 +217,6 @@ namespace FlaxEditor.Surface.Elements // Check direct connection if (Surface.CanUseDirectCast(type, _currentType)) { - // Can return true; } @@ -224,25 +225,15 @@ namespace FlaxEditor.Surface.Elements if (Archetype.ConnectionsType == ScriptType.Null && connectionsHints != ConnectionsHint.None) { if ((connectionsHints & ConnectionsHint.Anything) == ConnectionsHint.Anything) - { - // Can return true; - } if ((connectionsHints & ConnectionsHint.Value) == ConnectionsHint.Value && type.Type != typeof(void)) - { - // Can return true; - } if ((connectionsHints & ConnectionsHint.Enum) == ConnectionsHint.Enum && type.IsEnum) - { - // Can return true; - } if ((connectionsHints & ConnectionsHint.Array) == ConnectionsHint.Array && type.IsArray) - { - // Can return true; - } + if ((connectionsHints & ConnectionsHint.Dictionary) == ConnectionsHint.Dictionary && type.IsDictionary) + return true; if ((connectionsHints & ConnectionsHint.Vector) == ConnectionsHint.Vector) { var t = type.Type; @@ -251,7 +242,6 @@ namespace FlaxEditor.Surface.Elements t == typeof(Vector4) || t == typeof(Color)) { - // Can return true; } } @@ -270,7 +260,6 @@ namespace FlaxEditor.Surface.Elements t == typeof(float) || t == typeof(double)) { - // Can return true; } } diff --git a/Source/Editor/Surface/NodeArchetype.cs b/Source/Editor/Surface/NodeArchetype.cs index b28bef8f0..5ecffe957 100644 --- a/Source/Editor/Surface/NodeArchetype.cs +++ b/Source/Editor/Surface/NodeArchetype.cs @@ -57,6 +57,11 @@ namespace FlaxEditor.Surface /// Array = 32, + /// + /// Allow any dictionary types connections. + /// + Dictionary = 64, + /// /// Allow any scalar or vector numeric value types connections (bool, int, float, vector2, color..). /// @@ -65,7 +70,7 @@ namespace FlaxEditor.Surface /// /// All flags. /// - All = Scalar | Vector | Enum | Anything | Value | Array, + All = Scalar | Vector | Enum | Anything | Value | Array | Dictionary, } /// diff --git a/Source/Editor/Surface/SurfaceUtils.cs b/Source/Editor/Surface/SurfaceUtils.cs index d53049b16..5718e68ab 100644 --- a/Source/Editor/Surface/SurfaceUtils.cs +++ b/Source/Editor/Surface/SurfaceUtils.cs @@ -415,8 +415,13 @@ namespace FlaxEditor.Surface internal static bool IsValidVisualScriptType(ScriptType scriptType) { - if (scriptType.IsGenericType || !scriptType.IsPublic || scriptType.HasAttribute(typeof(HideInEditorAttribute), true)) + if (!scriptType.IsPublic || scriptType.HasAttribute(typeof(HideInEditorAttribute), true)) return false; + if (scriptType.IsGenericType) + { + // Only Dictionary generic type is valid + return scriptType.GetGenericTypeDefinition() == typeof(Dictionary<,>); + } var managedType = TypeUtils.GetType(scriptType); return !TypeUtils.IsDelegate(managedType); } diff --git a/Source/Editor/Surface/VisjectSurface.cs b/Source/Editor/Surface/VisjectSurface.cs index 8ad91d588..8fd9042ea 100644 --- a/Source/Editor/Surface/VisjectSurface.cs +++ b/Source/Editor/Surface/VisjectSurface.cs @@ -387,17 +387,7 @@ namespace FlaxEditor.Surface /// The display name (for UI). public virtual string GetTypeName(ScriptType type) { - if (type == ScriptType.Null) - return null; - if (type.Type == typeof(float)) - return "Float"; - if (type.Type == typeof(int)) - return "Int"; - if (type.Type == typeof(uint)) - return "Uint"; - if (type.Type == typeof(bool)) - return "Bool"; - return type.Name; + return type == ScriptType.Null ? null : type.Name; } /// diff --git a/Source/Editor/Utilities/VariantUtils.cs b/Source/Editor/Utilities/VariantUtils.cs index 85f055ec8..98e4a6730 100644 --- a/Source/Editor/Utilities/VariantUtils.cs +++ b/Source/Editor/Utilities/VariantUtils.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. using System; +using System.Collections; using System.Collections.Generic; using System.IO; using FlaxEditor.Scripting; @@ -134,7 +135,7 @@ namespace FlaxEditor.Utilities variantType = VariantType.Blob; else if (type.IsArray) variantType = VariantType.Array; - else if (type == typeof(Dictionary)) + else if (type == typeof(Dictionary) || (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))) variantType = VariantType.Dictionary; else if (type.IsPointer || type.IsByRef) { @@ -206,10 +207,13 @@ namespace FlaxEditor.Utilities case VariantType.Enum: case VariantType.Structure: case VariantType.ManagedObject: - case VariantType.Typename: stream.Write(int.MaxValue); stream.WriteStrAnsi(type.FullName, 77); break; + case VariantType.Typename: + stream.Write(int.MaxValue); + stream.WriteStrAnsi(type.GetTypeName(), 77); + break; case VariantType.Array: if (type != typeof(object[])) { @@ -219,6 +223,15 @@ namespace FlaxEditor.Utilities else stream.Write(0); break; + case VariantType.Dictionary: + if (type != typeof(Dictionary)) + { + stream.Write(int.MaxValue); + stream.WriteStrAnsi(type.GetTypeName(), 77); + } + else + stream.Write(0); + break; default: stream.Write(0); break; @@ -455,7 +468,7 @@ namespace FlaxEditor.Utilities if (type == null) type = typeof(object[]); else if (!type.IsArray) - throw new Exception("Invalid arry type for the Variant array " + typeName); + throw new Exception("Invalid type for the Variant array " + typeName); var count = stream.ReadInt32(); var result = Array.CreateInstance(type.GetElementType(), count); for (int i = 0; i < count; i++) @@ -464,8 +477,12 @@ namespace FlaxEditor.Utilities } case VariantType.Dictionary: { + if (type == null) + type = typeof(Dictionary); + else if (!type.IsGenericType || type.GetGenericTypeDefinition() != typeof(Dictionary<,>)) + throw new Exception("Invalid type for the Variant dictionary " + typeName); var count = stream.ReadInt32(); - var result = new Dictionary(); + var result = (IDictionary)Activator.CreateInstance(type); for (int i = 0; i < count; i++) result.Add(stream.ReadVariant(), stream.ReadVariant()); return result; @@ -500,7 +517,7 @@ namespace FlaxEditor.Utilities } return null; } - default: throw new ArgumentOutOfRangeException($"Unknown Variant Type {variantType}." + (type != null ? $" Type: {type.FullName}" : string.Empty)); + default: throw new ArgumentOutOfRangeException($"Unknown Variant Type {variantType}." + (type != null ? $" Type: {type.GetTypeDisplayName()}" : string.Empty)); } } @@ -550,6 +567,15 @@ namespace FlaxEditor.Utilities else stream.Write(0); break; + case VariantType.Dictionary: + if (type != typeof(Dictionary)) + { + stream.Write(int.MaxValue); + stream.WriteStrAnsi(type.GetTypeName(), 77); + } + else + stream.Write(0); + break; default: stream.Write(0); break; @@ -660,16 +686,19 @@ namespace FlaxEditor.Utilities break; } case VariantType.Dictionary: - stream.Write(((Dictionary)value).Count); - foreach (var e in (Dictionary)value) + { + var dictionary = (IDictionary)value; + stream.Write(dictionary.Count); + foreach (var key in dictionary.Keys) { - stream.WriteVariant(e.Key); - stream.WriteVariant(e.Value); + stream.WriteVariant(key); + stream.WriteVariant(dictionary[key]); } break; + } case VariantType.Typename: if (value is Type) - stream.WriteStrAnsi(((Type)value).FullName, -14); + stream.WriteStrAnsi(((Type)value).GetTypeName(), -14); else if (value is ScriptType) stream.WriteStrAnsi(((ScriptType)value).TypeName, -14); break; @@ -708,6 +737,10 @@ namespace FlaxEditor.Utilities if (value != typeof(object[])) withoutTypeName = false; break; + case VariantType.Dictionary: + if (value != typeof(Dictionary)) + withoutTypeName = false; + break; } if (withoutTypeName) { @@ -721,7 +754,7 @@ namespace FlaxEditor.Utilities stream.WriteValue((int)variantType); stream.WritePropertyName("TypeName"); - stream.WriteValue(value.FullName); + stream.WriteValue(value.GetTypeName()); stream.WriteEndObject(); } @@ -1114,19 +1147,20 @@ namespace FlaxEditor.Utilities case VariantType.Dictionary: { stream.WriteStartArray(); - foreach (var e in (Dictionary)value) + var dictionary = (IDictionary)value; + foreach (var key in dictionary.Keys) { stream.WritePropertyName("Key"); - stream.WriteVariant(e.Key); + stream.WriteVariant(key); stream.WritePropertyName("Value"); - stream.WriteVariant(e.Value); + stream.WriteVariant(dictionary[key]); } stream.WriteEndArray(); break; } case VariantType.Typename: if (value is Type) - stream.WriteValue(((Type)value).FullName); + stream.WriteValue(((Type)value).GetTypeName()); else if (value is ScriptType) stream.WriteValue(((ScriptType)value).TypeName); break; @@ -1137,7 +1171,7 @@ namespace FlaxEditor.Utilities stream.WriteRaw(json); break; } - default: throw new ArgumentOutOfRangeException($"Unknown Variant Type {variantType} for type {type.FullName}."); + default: throw new ArgumentOutOfRangeException($"Unknown Variant Type {variantType} for type {type.GetTypeDisplayName()}."); } // ReSharper restore PossibleNullReferenceException diff --git a/Source/Editor/Windows/Assets/VisualScriptWindow.cs b/Source/Editor/Windows/Assets/VisualScriptWindow.cs index 2fab58b9f..ee395cb94 100644 --- a/Source/Editor/Windows/Assets/VisualScriptWindow.cs +++ b/Source/Editor/Windows/Assets/VisualScriptWindow.cs @@ -175,37 +175,51 @@ namespace FlaxEditor.Windows.Assets // Parameter type editing var cmType = menu.AddChildMenu("Type"); { - var b = cmType.ContextMenu.AddButton(window.Surface.GetTypeName(param.Type) + "...", () => + var isArray = param.Type.IsArray; + var isDictionary = !isArray && param.Type.IsDictionary; + ScriptType singleValueType, arrayType, dictionaryType; + ContextMenuButton b; + if (isDictionary) { - // Show context menu with list of parameter types to use - var cm = new ItemsListContextMenu(180); - var newParameterTypes = window.NewParameterTypes; - foreach (var newParameterType in newParameterTypes) - { - var item = new TypeSearchPopup.TypeItemView(newParameterType); - if (newParameterType.Type != null) - item.Name = window.VisjectSurface.GetTypeName(newParameterType); - cm.AddItem(item); - } - cm.ItemClicked += (ItemsListContextMenu.Item item) => window.SetParamType(index, (ScriptType)item.Tag); - cm.SortChildren(); - cm.Show(window, window.PointFromScreen(Input.MouseScreenPosition)); - }); - b.Enabled = window._canEdit; - b.TooltipText = "Opens the type picker window to change the parameter type."; - cmType.ContextMenu.AddSeparator(); + var args = param.Type.GetGenericArguments(); + singleValueType = new ScriptType(args[0]); + arrayType = singleValueType.MakeArrayType(); + dictionaryType = param.Type; + var keyName = window.Surface.GetTypeName(new ScriptType(args[0])); + var valueName = window.Surface.GetTypeName(new ScriptType(args[1])); - ScriptType singleValueType, arrayType; - if (param.Type.IsArray) - { - singleValueType = new ScriptType(param.Type.GetElementType()); - arrayType = param.Type; + b = cmType.ContextMenu.AddButton($"Dictionary<{keyName}, {valueName}>"); + b.Enabled = false; + + b = cmType.ContextMenu.AddButton($"Edit key type: {keyName}...", () => OnChangeType(item => window.SetParamType(index, ScriptType.MakeDictionaryType((ScriptType)item.Tag, new ScriptType(args[1]))))); + b.Enabled = window._canEdit; + b.TooltipText = "Opens the type picker window to change the parameter type."; + + b = cmType.ContextMenu.AddButton($"Edit value type: {valueName}...", () => OnChangeType(item => window.SetParamType(index, ScriptType.MakeDictionaryType(new ScriptType(args[0]), (ScriptType)item.Tag)))); + b.Enabled = window._canEdit; + b.TooltipText = "Opens the type picker window to change the parameter type."; } else { - singleValueType = param.Type; - arrayType = param.Type.MakeArrayType(); + if (isArray) + { + singleValueType = new ScriptType(param.Type.GetElementType()); + arrayType = param.Type; + dictionaryType = ScriptType.MakeDictionaryType(new ScriptType(typeof(int)), singleValueType); + b = cmType.ContextMenu.AddButton(window.Surface.GetTypeName(singleValueType) + "[]...", () => OnChangeType(item => window.SetParamType(index, ((ScriptType)item.Tag).MakeArrayType()))); + } + else + { + singleValueType = param.Type; + arrayType = param.Type.MakeArrayType(); + dictionaryType = ScriptType.MakeDictionaryType(new ScriptType(typeof(int)), singleValueType); + b = cmType.ContextMenu.AddButton(window.Surface.GetTypeName(param.Type) + "...", () => OnChangeType(item => window.SetParamType(index, (ScriptType)item.Tag))); + } + b.Enabled = window._canEdit; + b.TooltipText = "Opens the type picker window to change the parameter type."; } + cmType.ContextMenu.AddSeparator(); + b = cmType.ContextMenu.AddButton("Value", () => window.SetParamType(index, singleValueType)); b.Checked = param.Type == singleValueType; b.Enabled = window._canEdit; @@ -214,8 +228,30 @@ namespace FlaxEditor.Windows.Assets b.Checked = param.Type == arrayType; b.Enabled = window._canEdit; b.TooltipText = "Changes parameter type to an array."; + b = cmType.ContextMenu.AddButton("Dictionary", () => window.SetParamType(index, dictionaryType)); + b.Checked = param.Type == dictionaryType; + b.Enabled = window._canEdit; + b.TooltipText = "Changes parameter type to a dictionary."; } } + + private void OnChangeType(Action itemClicked) + { + // Show context menu with list of parameter types to use + var cm = new ItemsListContextMenu(180); + var window = (VisualScriptWindow)Values[0]; + var newParameterTypes = window.NewParameterTypes; + foreach (var newParameterType in newParameterTypes) + { + var item = new TypeSearchPopup.TypeItemView(newParameterType); + if (newParameterType.Type != null) + item.Name = window.VisjectSurface.GetTypeName(newParameterType); + cm.AddItem(item); + } + cm.ItemClicked += itemClicked; + cm.SortChildren(); + cm.Show(window, window.PointFromScreen(Input.MouseScreenPosition)); + } } private sealed class PropertiesProxy @@ -366,7 +402,7 @@ namespace FlaxEditor.Windows.Assets gridControl.Height = Button.DefaultHeight; gridControl.SlotsHorizontally = 2; gridControl.SlotsVertically = 1; - + var addOverride = grid.Button("Add Override"); addOverride.Button.Clicked += OnOverrideMethodClicked; // TODO: Add sender arg to button clicked action? diff --git a/Source/Editor/Windows/GameCookerWindow.cs b/Source/Editor/Windows/GameCookerWindow.cs index 01cd8efae..1c95addad 100644 --- a/Source/Editor/Windows/GameCookerWindow.cs +++ b/Source/Editor/Windows/GameCookerWindow.cs @@ -28,6 +28,7 @@ namespace FlaxEditor.Windows /// /// Proxy object for the Build tab. /// + [HideInEditor] [CustomEditor(typeof(BuildTabProxy.Editor))] private class BuildTabProxy { @@ -65,6 +66,7 @@ namespace FlaxEditor.Windows PerPlatformOptions[PlatformType.Mac].Init("Output/Mac", "Mac"); } + [HideInEditor] abstract class Platform { [HideInEditor] diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp index 6df65d99d..5ada55b7c 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -1190,6 +1190,85 @@ void VisualScriptExecutor::ProcessGroupFlow(Box* boxBase, Node* node, Value& val } break; } + // Dictionary For Each + case 8: + { + 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 dictionaryIndex = 0; + for (; iteratorIndex < scope->ReturnedValues.Count(); dictionaryIndex++) + { + const auto& e = scope->ReturnedValues[dictionaryIndex]; + if (e.NodeId == node->ID && e.BoxId == 1) + break; + } + switch (boxBase->ID) + { + // Loop + case 0: + { + if (iteratorIndex == scope->ReturnedValues.Count()) + { + if (dictionaryIndex == scope->ReturnedValues.Count()) + dictionaryIndex++; + scope->ReturnedValues.AddOne(); + } + if (dictionaryIndex == scope->ReturnedValues.Count()) + scope->ReturnedValues.AddOne(); + auto& iteratorValue = scope->ReturnedValues[iteratorIndex]; + iteratorValue.NodeId = node->ID; + iteratorValue.BoxId = 0; + auto& dictionaryValue = scope->ReturnedValues[dictionaryIndex]; + dictionaryValue.NodeId = node->ID; + dictionaryValue.BoxId = 1; + dictionaryValue.Value = tryGetValue(node->GetBox(4), Value::Null); + if (dictionaryValue.Value.Type.Type != VariantType::Dictionary) + { + OnError(node, boxBase, String::Format(TEXT("Input value {0} is not a dictionary."), dictionaryValue.Value)); + return; + } + auto& dictionary = *dictionaryValue.Value.AsDictionary; + iteratorValue.Value = dictionary.Begin().Index(); + int32 end = dictionary.End().Index(); + while (iteratorValue.Value.AsInt < end) + { + boxBase = node->GetBox(3); + if (boxBase->HasConnection()) + eatBox(node, boxBase->FirstConnection()); + Dictionary::Iterator it(dictionary, iteratorValue.Value.AsInt); + ++it; + iteratorValue.Value.AsInt = it.Index(); + } + boxBase = node->GetBox(6); + if (boxBase->HasConnection()) + eatBox(node, boxBase->FirstConnection()); + break; + } + // Key + case 1: + if (iteratorIndex != scope->ReturnedValues.Count() && dictionaryIndex != scope->ReturnedValues.Count()) + value = Dictionary::Iterator(*scope->ReturnedValues[dictionaryIndex].Value.AsDictionary, scope->ReturnedValues[iteratorIndex].Value.AsInt)->Key; + break; + // Value + case 2: + if (iteratorIndex != scope->ReturnedValues.Count() && dictionaryIndex != scope->ReturnedValues.Count()) + value = Dictionary::Iterator(*scope->ReturnedValues[dictionaryIndex].Value.AsDictionary, scope->ReturnedValues[iteratorIndex].Value.AsInt)->Value; + break; + // Break + case 5: + // Reset loop iterator + if (iteratorIndex != scope->ReturnedValues.Count()) + scope->ReturnedValues[iteratorIndex].Value.AsInt = MAX_int32 - 1; + break; + } + break; + } } } diff --git a/Source/Engine/Core/Collections/Dictionary.h b/Source/Engine/Core/Collections/Dictionary.h index 941ef88d5..ce146fd2b 100644 --- a/Source/Engine/Core/Collections/Dictionary.h +++ b/Source/Engine/Core/Collections/Dictionary.h @@ -245,6 +245,7 @@ public: Dictionary& _collection; int32 _index; + public: Iterator(Dictionary& collection, const int32 index) : _collection(collection) , _index(index) @@ -257,8 +258,6 @@ public: { } - public: - Iterator(const Iterator& i) : _collection(i._collection) , _index(i._index) @@ -272,6 +271,10 @@ public: } public: + FORCE_INLINE int32 Index() const + { + return _index; + } FORCE_INLINE bool IsEnd() const { diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index 33a416b38..46abe6cac 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -66,7 +66,7 @@ namespace "FlaxEngine.Ray",// Ray "FlaxEngine.Matrix",// Matrix "System.Object[]",// Array - "Dictionary",// Dictionary + "System.Collections.Generic.Dictionary`2[System.Object,System.Object]",// Dictionary "System.Object",// ManagedObject "System.Type",// Typename "FlaxEngine.Int2"// Int2 @@ -767,6 +767,12 @@ Variant::Variant(const Array& v) new(array)Array(v); } +Variant::Variant(Dictionary&& v) + : Type(VariantType::Dictionary) +{ + AsDictionary = New>(MoveTemp(v)); +} + Variant::Variant(const Dictionary& v) : Type(VariantType::Dictionary) { diff --git a/Source/Engine/Core/Types/Variant.h b/Source/Engine/Core/Types/Variant.h index 9d6a97688..95722b026 100644 --- a/Source/Engine/Core/Types/Variant.h +++ b/Source/Engine/Core/Types/Variant.h @@ -228,6 +228,7 @@ public: explicit Variant(const Matrix& v); Variant(Array&& v); Variant(const Array& v); + explicit Variant(Dictionary&& v); explicit Variant(const Dictionary& v); explicit Variant(const Span& v); explicit Variant(const CommonValue& v); diff --git a/Source/Engine/Scripting/InternalCalls/ManagedDictionary.h b/Source/Engine/Scripting/InternalCalls/ManagedDictionary.h index 1d2720141..1d123e067 100644 --- a/Source/Engine/Scripting/InternalCalls/ManagedDictionary.h +++ b/Source/Engine/Scripting/InternalCalls/ManagedDictionary.h @@ -14,6 +14,9 @@ #if USE_MONO #include +/// +/// Utility interop between C++ and C# for Dictionary collection. +/// struct FLAXENGINE_API ManagedDictionary { MonoObject* Instance; @@ -74,17 +77,13 @@ struct FLAXENGINE_API ManagedDictionary return result; } - static ManagedDictionary New(MonoType* keyType, MonoType* valueType) + static MonoReflectionType* GetClass(MonoType* keyType, MonoType* valueType) { - ManagedDictionary result; - auto domain = mono_domain_get(); auto scriptingClass = Scripting::GetStaticClass(); - CHECK_RETURN(scriptingClass, result); + CHECK_RETURN(scriptingClass, nullptr); auto makeGenericMethod = scriptingClass->GetMethod("MakeGenericType", 2); - CHECK_RETURN(makeGenericMethod, result); - auto createMethod = StdTypesContainer::Instance()->ActivatorClass->GetMethod("CreateInstance", 2); - CHECK_RETURN(createMethod, result); + CHECK_RETURN(makeGenericMethod, nullptr); auto genericType = MUtils::GetType(StdTypesContainer::Instance()->DictionaryClass->GetNative()); auto genericArgs = mono_array_new(domain, mono_get_object_class(), 2); @@ -100,9 +99,25 @@ struct FLAXENGINE_API ManagedDictionary { MException ex(exception); ex.Log(LogType::Error, TEXT("")); - return result; + return nullptr; } + return (MonoReflectionType*)dictionaryType; + } + static ManagedDictionary New(MonoType* keyType, MonoType* valueType) + { + ManagedDictionary result; + auto dictionaryType = GetClass(keyType, valueType); + if (!dictionaryType) + return result; + + auto scriptingClass = Scripting::GetStaticClass(); + CHECK_RETURN(scriptingClass, result); + auto createMethod = StdTypesContainer::Instance()->ActivatorClass->GetMethod("CreateInstance", 2); + CHECK_RETURN(createMethod, result); + + MObject* exception = nullptr; + void* params[2]; params[0] = dictionaryType; params[1] = nullptr; auto instance = createMethod->Invoke(nullptr, params, &exception); diff --git a/Source/Engine/Scripting/ManagedCLR/MUtils.cpp b/Source/Engine/Scripting/ManagedCLR/MUtils.cpp index aa5d1a6a1..a09d45836 100644 --- a/Source/Engine/Scripting/ManagedCLR/MUtils.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MUtils.cpp @@ -19,11 +19,42 @@ #include "Engine/Scripting/Scripting.h" #include "Engine/Scripting/ScriptingObject.h" #include "Engine/Scripting/StdTypesContainer.h" +#include "Engine/Scripting/InternalCalls/ManagedDictionary.h" #include "Engine/Utilities/StringConverter.h" #include "Engine/Content/Asset.h" #if USE_MONO +// Inlined mono private types to access MonoType internals + +typedef struct _MonoGenericClass MonoGenericClass; +typedef struct _MonoGenericContext MonoGenericContext; + +struct _MonoGenericInst +{ + unsigned int id; + unsigned int type_argc : 22; + unsigned int is_open : 1; + MonoType* type_argv[MONO_ZERO_LEN_ARRAY]; +}; + +struct _MonoGenericContext +{ + MonoGenericInst* class_inst; + MonoGenericInst* method_inst; +}; + +struct _MonoGenericClass +{ + MonoClass* container_class; + MonoGenericContext context; + unsigned int is_dynamic : 1; + unsigned int is_tb_open : 1; + unsigned int need_sync : 1; + MonoClass* cached_class; + class MonoImageSet* owner; +}; + struct _MonoType { union @@ -43,6 +74,21 @@ struct _MonoType unsigned int pinned : 1; }; +namespace +{ + // typeName in format System.Collections.Generic.Dictionary`2[KeyType,ValueType] + void GetDictionaryKeyValueTypes(const StringAnsiView& typeName, MonoClass*& keyClass, MonoClass*& valueClass) + { + const int32 keyStart = typeName.Find('['); + const int32 keyEnd = typeName.Find(','); + const int32 valueEnd = typeName.Find(']'); + const StringAnsiView keyTypename(*typeName + keyStart + 1, keyEnd - keyStart - 1); + const StringAnsiView valueTypename(*typeName + keyEnd + 1, valueEnd - keyEnd - 1); + keyClass = Scripting::FindClassNative(keyTypename); + valueClass = Scripting::FindClassNative(valueTypename); + } +} + StringView MUtils::ToString(MonoString* str) { if (str == nullptr) @@ -439,6 +485,30 @@ Variant MUtils::UnboxVariant(MonoObject* value) } return v; } + case MONO_TYPE_GENERICINST: + { + if (StringUtils::Compare(mono_class_get_name(klass), "Dictionary`2") == 0 && StringUtils::Compare(mono_class_get_namespace(klass), "System.Collections.Generic") == 0) + { + // Dictionary + ManagedDictionary managed(value); + MonoArray* managedKeys = managed.GetKeys(); + auto length = managedKeys ? (int32)mono_array_length(managedKeys) : 0; + Dictionary native; + native.EnsureCapacity(length); + for (int32 i = 0; i < length; i++) + { + MonoObject* keyManaged = mono_array_get(managedKeys, MonoObject*, i); + MonoObject* valueManaged = managed.GetValue(keyManaged); + native.Add(UnboxVariant(keyManaged), UnboxVariant(valueManaged)); + } + Variant v(MoveTemp(native)); + StringAnsi typeName; + GetClassFullname(klass, typeName); + v.Type.SetTypeName(typeName); + return v; + } + break; + } } if (mono_class_is_subclass_of(klass, Asset::GetStaticClass()->GetNative(), false) != 0) @@ -473,7 +543,6 @@ Variant MUtils::UnboxVariant(MonoObject* value) } return Variant(value); } - // TODO: support any dictionary unboxing return Variant(value); } @@ -642,7 +711,31 @@ MonoObject* MUtils::BoxVariant(const Variant& value) } return (MonoObject*)managed; } - // TODO: VariantType::Dictionary + case VariantType::Dictionary: + { + // Get dictionary key and value types + MonoClass *keyClass, *valueClass; + GetDictionaryKeyValueTypes(value.Type.GetTypeName(), keyClass, valueClass); + if (!keyClass || !valueClass) + { + LOG(Error, "Invalid type to box {0}", value.Type); + return nullptr; + } + + // Allocate managed dictionary + ManagedDictionary managed = ManagedDictionary::New(mono_class_get_type(keyClass), mono_class_get_type(valueClass)); + if (!managed.Instance) + return nullptr; + + // Add native keys and values + const auto& dictionary = *value.AsDictionary; + for (const auto& e : dictionary) + { + managed.Add(BoxVariant(e.Key), BoxVariant(e.Value)); + } + + return managed.Instance; + } case VariantType::Structure: { if (value.AsBlob.Data == nullptr) @@ -684,7 +777,6 @@ void MUtils::GetClassFullname(MonoObject* obj, MString& fullname) { if (obj == nullptr) return; - MonoClass* monoClass = mono_object_get_class(obj); GetClassFullname(monoClass, fullname); } @@ -693,11 +785,13 @@ void MUtils::GetClassFullname(MonoClass* monoClass, MString& fullname) { static MString plusStr("+"); static MString dotStr("."); - MonoClass* nestingClass = mono_class_get_nesting_type(monoClass); - MonoClass* lastClass = monoClass; + // Name fullname = mono_class_get_name(monoClass); + // Outer class for nested types + MonoClass* nestingClass = mono_class_get_nesting_type(monoClass); + MonoClass* lastClass = monoClass; while (nestingClass) { lastClass = nestingClass; @@ -705,9 +799,27 @@ void MUtils::GetClassFullname(MonoClass* monoClass, MString& fullname) nestingClass = mono_class_get_nesting_type(nestingClass); } + // Namespace const char* lastClassNamespace = mono_class_get_namespace(lastClass); if (lastClassNamespace && *lastClassNamespace) fullname = lastClassNamespace + dotStr + fullname; + + // Generic instance arguments + const MonoType* monoType = mono_class_get_type(monoClass); + if (monoType && monoType->type == MONO_TYPE_GENERICINST) + { + fullname += '['; + MString tmp; + for (unsigned int i = 0; i < monoType->data.generic_class->context.class_inst->type_argc; i++) + { + if (i != 0) + fullname += ','; + MonoType* argType = monoType->data.generic_class->context.class_inst->type_argv[i]; + GetClassFullname(mono_type_get_class(argType), tmp); + fullname += tmp; + } + fullname += ']'; + } } void MUtils::GetClassFullname(MonoReflectionType* type, MString& fullname) @@ -715,7 +827,7 @@ void MUtils::GetClassFullname(MonoReflectionType* type, MString& fullname) if (!type) return; MonoType* monoType = mono_reflection_type_get_type(type); - MonoClass* monoClass = mono_type_get_class(monoType); + MonoClass* monoClass = mono_class_from_mono_type(monoType); GetClassFullname(monoClass, fullname); } @@ -729,7 +841,7 @@ MonoClass* MUtils::GetClass(MonoReflectionType* type) if (!type) return nullptr; MonoType* monoType = mono_reflection_type_get_type(type); - return mono_type_get_class(monoType); + return mono_class_from_mono_type(monoType); } MonoClass* MUtils::GetClass(const VariantType& value) @@ -805,6 +917,17 @@ MonoClass* MUtils::GetClass(const VariantType& value) return mono_array_class_get(mclass->GetNative(), 1); } return mono_array_class_get(mono_get_object_class(), 1); + case VariantType::Dictionary: + { + MonoClass *keyClass, *valueClass; + GetDictionaryKeyValueTypes(value.GetTypeName(), keyClass, valueClass); + if (!keyClass || !valueClass) + { + LOG(Error, "Invalid type to box {0}", value.ToString()); + return nullptr; + } + return GetClass(ManagedDictionary::GetClass(mono_class_get_type(keyClass), mono_class_get_type(valueClass))); + } case VariantType::ManagedObject: return mono_get_object_class(); default: ; @@ -1052,6 +1175,15 @@ void* MUtils::VariantToManagedArgPtr(Variant& value, const MType& type, bool& fa object = nullptr; return object; } + case MONO_TYPE_GENERICINST: + { + if (value.Type.Type == VariantType::Null) + return nullptr; + MonoObject* object = BoxVariant(value); + if (object && !mono_class_is_subclass_of(mono_object_get_class(object), mono_class_from_mono_type(type.GetNative()), false)) + object = nullptr; + return object; + } default: break; } diff --git a/Source/Engine/Serialization/Stream.cpp b/Source/Engine/Serialization/Stream.cpp index d86a941bc..1b63f2c20 100644 --- a/Source/Engine/Serialization/Stream.cpp +++ b/Source/Engine/Serialization/Stream.cpp @@ -10,7 +10,6 @@ #include "Engine/Core/Collections/Dictionary.h" #include "Engine/Content/Asset.h" #include "Engine/Core/Cache.h" -#include "Engine/Debug/DebugLog.h" #include "Engine/Debug/Exceptions/JsonParseException.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Scripting/ManagedSerialization.h" @@ -458,12 +457,11 @@ void ReadStream::ReadVariant(Variant* data) auto& dictionary = *data->AsDictionary; dictionary.Clear(); dictionary.EnsureCapacity(count); - Variant key, value; for (int32 i = 0; i < count; i++) { + Variant key; ReadVariant(&key); - ReadVariant(&value); - dictionary.Add(key, value); + ReadVariant(&dictionary[MoveTemp(key)]); } break; } diff --git a/Source/Engine/Visject/VisjectGraph.cpp b/Source/Engine/Visject/VisjectGraph.cpp index 3f6fd60c4..20c04fafa 100644 --- a/Source/Engine/Visject/VisjectGraph.cpp +++ b/Source/Engine/Visject/VisjectGraph.cpp @@ -133,6 +133,18 @@ void VisjectExecutor::ProcessGroupConstants(Box* box, Node* node, Value& value) } } break; + // Dictionary + case 14: + { + value = Variant(Dictionary()); + String typeName = TEXT("System.Collections.Generic.Dictionary`2["); + typeName += (StringView)node->Values[0]; + typeName += ','; + typeName += (StringView)node->Values[1]; + typeName += ']'; + value.Type.SetTypeName(typeName); + break; + } default: break; } @@ -1353,4 +1365,63 @@ void VisjectExecutor::ProcessGroupCollections(Box* box, Node* node, Value& value break; } } + else if (node->TypeID < 200) + { + // Dictionary + Variant v = tryGetValue(node->GetBox(0), Value::Null); + ENSURE(v.Type.Type == VariantType::Dictionary, String::Format(TEXT("Input value {0} is not a dictionary."), v)); + auto& dictionary = *v.AsDictionary; + switch (node->TypeID) + { + // Count + case 101: + value = dictionary.Count(); + break; + // Contains Key + case 102: + { + Variant inKey = tryGetValue(node->GetBox(1), 0, Value::Null); + value = dictionary.ContainsKey(inKey); + break; + } + // Contains Value + case 103: + { + Variant inValue = tryGetValue(node->GetBox(2), 0, Value::Null); + value = dictionary.ContainsValue(inValue); + break; + } + // Clear + case 104: + dictionary.Clear(); + value = MoveTemp(v); + break; + // Remove + case 105: + { + Variant inKey = tryGetValue(node->GetBox(1), 0, Value::Null); + dictionary.Remove(inKey); + value = MoveTemp(v); + break; + } + // Set + case 106: + { + Variant inKey = tryGetValue(node->GetBox(1), 0, Value::Null); + Variant inValue = tryGetValue(node->GetBox(2), 1, Value::Null); + dictionary[MoveTemp(inKey)] = MoveTemp(inValue); + value = MoveTemp(v); + break; + } + // Get + case 107: + { + Variant key = tryGetValue(node->GetBox(1), 0, Value::Null); + Variant* valuePtr = dictionary.TryGet(key); + ENSURE(valuePtr, TEXT("Missing key to get.")); + value = MoveTemp(*valuePtr); + break; + } + } + } }