Add **Dictionaries to Visual Scripting**

This commit is contained in:
Wojtek Figat
2022-04-27 22:47:54 +02:00
parent 3c841b1be1
commit 158c29e598
20 changed files with 852 additions and 114 deletions

View File

@@ -714,7 +714,7 @@ namespace FlaxEditor.Scripting
public IScriptType IScriptType => _custom; public IScriptType IScriptType => _custom;
/// <summary> /// <summary>
/// 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).
/// </summary> /// </summary>
public string Name public string Name
{ {
@@ -724,19 +724,7 @@ namespace FlaxEditor.Scripting
return _custom.Name; return _custom.Name;
if (_managed == null) if (_managed == null)
return string.Empty; return string.Empty;
if (_managed == typeof(float)) return _managed.GetTypeDisplayName();
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;
} }
} }
@@ -790,6 +778,11 @@ namespace FlaxEditor.Scripting
/// </summary> /// </summary>
public bool IsArray => _managed != null ? _managed.IsArray : _custom != null && _custom.IsArray; public bool IsArray => _managed != null ? _managed.IsArray : _custom != null && _custom.IsArray;
/// <summary>
/// Gets a value indicating whether the type is a dictionary.
/// </summary>
public bool IsDictionary => IsGenericType && GetGenericTypeDefinition() == typeof(Dictionary<,>);
/// <summary> /// <summary>
/// Gets a value indicating whether the type is a value type (basic type, enumeration or a structure). /// Gets a value indicating whether the type is a value type (basic type, enumeration or a structure).
/// </summary> /// </summary>
@@ -981,7 +974,7 @@ namespace FlaxEditor.Scripting
public override string ToString() public override string ToString()
{ {
if (_managed != null) if (_managed != null)
return _managed.FullName ?? string.Empty; return _managed.GetTypeDisplayName() ?? string.Empty;
if (_custom != null) if (_custom != null)
return _custom.TypeName; return _custom.TypeName;
return "<null>"; return "<null>";
@@ -1122,6 +1115,15 @@ namespace FlaxEditor.Scripting
throw new NotImplementedException("TODO: Script.Type.MakeArrayType for custom types"); throw new NotImplementedException("TODO: Script.Type.MakeArrayType for custom types");
} }
/// <summary>
/// Returns a type object that represents a dictionary of the key and value types.
/// </summary>
/// <returns>A type object representing a dictionary of the key and value types.</returns>
public static ScriptType MakeDictionaryType(ScriptType keyType, ScriptType valueType)
{
return new ScriptType(typeof(Dictionary<,>).MakeGenericType(TypeUtils.GetType(keyType), TypeUtils.GetType(valueType)));
}
/// <summary> /// <summary>
/// Searches for the specified members of the specified member type, using the specified binding constraints. /// Searches for the specified members of the specified member type, using the specified binding constraints.
/// </summary> /// </summary>

View File

@@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using System.Text;
using FlaxEngine; using FlaxEngine;
namespace FlaxEditor.Scripting namespace FlaxEditor.Scripting
@@ -33,6 +34,74 @@ namespace FlaxEditor.Scripting
return o != null ? new ScriptType(o.GetType()) : ScriptType.Null; return o != null ? new ScriptType(o.GetType()) : ScriptType.Null;
} }
/// <summary>
/// Gets the typename full name.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>The full typename of the type.</returns>
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;
}
/// <summary>
/// Gets the typename name for UI.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>The display of the type.</returns>
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;
}
/// <summary> /// <summary>
/// Gets the default value for the given type (can be value type or reference type). /// Gets the default value for the given type (can be value type or reference type).
/// </summary> /// </summary>

View File

@@ -19,6 +19,15 @@ namespace FlaxEditor.Surface.Archetypes
return box.DefaultType != null ? new ScriptType(type.GetElementType()) : type; 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;
}
/// <summary> /// <summary>
/// The nodes for that group. /// The nodes for that group.
/// </summary> /// </summary>
@@ -28,7 +37,7 @@ namespace FlaxEditor.Surface.Archetypes
{ {
TypeID = 1, TypeID = 1,
Title = "Array Length", 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" }, AlternativeTitles = new[] { "Count" },
Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph, Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph,
Size = new Vector2(150, 20), Size = new Vector2(150, 20),
@@ -44,7 +53,7 @@ namespace FlaxEditor.Surface.Archetypes
{ {
TypeID = 2, TypeID = 2,
Title = "Array Contains", 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" }, AlternativeTitles = new[] { "Contains" },
Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph, Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph,
Size = new Vector2(150, 40), Size = new Vector2(150, 40),
@@ -277,6 +286,138 @@ namespace FlaxEditor.Surface.Archetypes
} }
}, },
// first 100 IDs reserved for arrays // 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
}; };
} }
} }

View File

@@ -132,7 +132,7 @@ namespace FlaxEditor.Surface.Archetypes
Array.Copy(prev, next, Mathf.Min(prev.Length, next.Length)); Array.Copy(prev, next, Mathf.Min(prev.Length, next.Length));
SetValue(0, next); SetValue(0, next);
} }
public override void OnSurfaceCanEditChanged(bool canEdit) public override void OnSurfaceCanEditChanged(bool canEdit)
{ {
base.OnSurfaceCanEditChanged(canEdit); base.OnSurfaceCanEditChanged(canEdit);
@@ -141,7 +141,7 @@ namespace FlaxEditor.Surface.Archetypes
_addButton.Enabled = canEdit; _addButton.Enabled = canEdit;
_removeButton.Enabled = canEdit; _removeButton.Enabled = canEdit;
} }
public override void OnDestroy() public override void OnDestroy()
{ {
_output = null; _output = null;
@@ -179,7 +179,7 @@ namespace FlaxEditor.Surface.Archetypes
break; break;
RemoveElement(box); RemoveElement(box);
} }
var canEdit = Surface.CanEdit; var canEdit = Surface.CanEdit;
_typePicker.Enabled = canEdit; _typePicker.Enabled = canEdit;
_addButton.Enabled = count < countMax && 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);
}
}
/// <summary> /// <summary>
/// The nodes for that group. /// The nodes for that group.
/// </summary> /// </summary>
@@ -236,12 +362,12 @@ namespace FlaxEditor.Surface.Archetypes
TryParseText = (string filterText, out object[] data) => TryParseText = (string filterText, out object[] data) =>
{ {
data = null; data = null;
if (filterText == "true") if (string.Equals(filterText, bool.TrueString, StringComparison.OrdinalIgnoreCase))
{ {
data = new object[] { true }; data = new object[] { true };
return true; return true;
} }
if (filterText == "false") if (string.Equals(filterText, bool.FalseString, StringComparison.OrdinalIgnoreCase))
{ {
data = new object[] { false }; data = new object[] { false };
return true; return true;
@@ -544,6 +670,17 @@ namespace FlaxEditor.Surface.Archetypes
DefaultValues = new object[] { new int[] { 0, 1, 2 } }, DefaultValues = new object[] { new int[] { 0, 1, 2 } },
Elements = new[] { NodeElementArchetype.Factory.Output(0, string.Empty, null, 0) } 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) }
},
}; };
/// <summary> /// <summary>

View File

@@ -331,6 +331,29 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Output(3, "Done", typeof(void), 6, true), 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),
}
},
}; };
} }
} }

View File

@@ -202,6 +202,8 @@ namespace FlaxEditor.Surface.Elements
return "Scalar"; return "Scalar";
if ((hint & ConnectionsHint.Array) == ConnectionsHint.Array) if ((hint & ConnectionsHint.Array) == ConnectionsHint.Array)
return "Array"; return "Array";
if ((hint & ConnectionsHint.Dictionary) == ConnectionsHint.Dictionary)
return "Dictionary";
return null; return null;
} }
@@ -215,7 +217,6 @@ namespace FlaxEditor.Surface.Elements
// Check direct connection // Check direct connection
if (Surface.CanUseDirectCast(type, _currentType)) if (Surface.CanUseDirectCast(type, _currentType))
{ {
// Can
return true; return true;
} }
@@ -224,25 +225,15 @@ namespace FlaxEditor.Surface.Elements
if (Archetype.ConnectionsType == ScriptType.Null && connectionsHints != ConnectionsHint.None) if (Archetype.ConnectionsType == ScriptType.Null && connectionsHints != ConnectionsHint.None)
{ {
if ((connectionsHints & ConnectionsHint.Anything) == ConnectionsHint.Anything) if ((connectionsHints & ConnectionsHint.Anything) == ConnectionsHint.Anything)
{
// Can
return true; return true;
}
if ((connectionsHints & ConnectionsHint.Value) == ConnectionsHint.Value && type.Type != typeof(void)) if ((connectionsHints & ConnectionsHint.Value) == ConnectionsHint.Value && type.Type != typeof(void))
{
// Can
return true; return true;
}
if ((connectionsHints & ConnectionsHint.Enum) == ConnectionsHint.Enum && type.IsEnum) if ((connectionsHints & ConnectionsHint.Enum) == ConnectionsHint.Enum && type.IsEnum)
{
// Can
return true; return true;
}
if ((connectionsHints & ConnectionsHint.Array) == ConnectionsHint.Array && type.IsArray) if ((connectionsHints & ConnectionsHint.Array) == ConnectionsHint.Array && type.IsArray)
{
// Can
return true; return true;
} if ((connectionsHints & ConnectionsHint.Dictionary) == ConnectionsHint.Dictionary && type.IsDictionary)
return true;
if ((connectionsHints & ConnectionsHint.Vector) == ConnectionsHint.Vector) if ((connectionsHints & ConnectionsHint.Vector) == ConnectionsHint.Vector)
{ {
var t = type.Type; var t = type.Type;
@@ -251,7 +242,6 @@ namespace FlaxEditor.Surface.Elements
t == typeof(Vector4) || t == typeof(Vector4) ||
t == typeof(Color)) t == typeof(Color))
{ {
// Can
return true; return true;
} }
} }
@@ -270,7 +260,6 @@ namespace FlaxEditor.Surface.Elements
t == typeof(float) || t == typeof(float) ||
t == typeof(double)) t == typeof(double))
{ {
// Can
return true; return true;
} }
} }

View File

@@ -57,6 +57,11 @@ namespace FlaxEditor.Surface
/// </summary> /// </summary>
Array = 32, Array = 32,
/// <summary>
/// Allow any dictionary types connections.
/// </summary>
Dictionary = 64,
/// <summary> /// <summary>
/// Allow any scalar or vector numeric value types connections (bool, int, float, vector2, color..). /// Allow any scalar or vector numeric value types connections (bool, int, float, vector2, color..).
/// </summary> /// </summary>
@@ -65,7 +70,7 @@ namespace FlaxEditor.Surface
/// <summary> /// <summary>
/// All flags. /// All flags.
/// </summary> /// </summary>
All = Scalar | Vector | Enum | Anything | Value | Array, All = Scalar | Vector | Enum | Anything | Value | Array | Dictionary,
} }
/// <summary> /// <summary>

View File

@@ -415,8 +415,13 @@ namespace FlaxEditor.Surface
internal static bool IsValidVisualScriptType(ScriptType scriptType) 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; return false;
if (scriptType.IsGenericType)
{
// Only Dictionary generic type is valid
return scriptType.GetGenericTypeDefinition() == typeof(Dictionary<,>);
}
var managedType = TypeUtils.GetType(scriptType); var managedType = TypeUtils.GetType(scriptType);
return !TypeUtils.IsDelegate(managedType); return !TypeUtils.IsDelegate(managedType);
} }

View File

@@ -387,17 +387,7 @@ namespace FlaxEditor.Surface
/// <returns>The display name (for UI).</returns> /// <returns>The display name (for UI).</returns>
public virtual string GetTypeName(ScriptType type) public virtual string GetTypeName(ScriptType type)
{ {
if (type == ScriptType.Null) return type == ScriptType.Null ? null : type.Name;
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;
} }
/// <summary> /// <summary>

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using FlaxEditor.Scripting; using FlaxEditor.Scripting;
@@ -134,7 +135,7 @@ namespace FlaxEditor.Utilities
variantType = VariantType.Blob; variantType = VariantType.Blob;
else if (type.IsArray) else if (type.IsArray)
variantType = VariantType.Array; variantType = VariantType.Array;
else if (type == typeof(Dictionary<object, object>)) else if (type == typeof(Dictionary<object, object>) || (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>)))
variantType = VariantType.Dictionary; variantType = VariantType.Dictionary;
else if (type.IsPointer || type.IsByRef) else if (type.IsPointer || type.IsByRef)
{ {
@@ -206,10 +207,13 @@ namespace FlaxEditor.Utilities
case VariantType.Enum: case VariantType.Enum:
case VariantType.Structure: case VariantType.Structure:
case VariantType.ManagedObject: case VariantType.ManagedObject:
case VariantType.Typename:
stream.Write(int.MaxValue); stream.Write(int.MaxValue);
stream.WriteStrAnsi(type.FullName, 77); stream.WriteStrAnsi(type.FullName, 77);
break; break;
case VariantType.Typename:
stream.Write(int.MaxValue);
stream.WriteStrAnsi(type.GetTypeName(), 77);
break;
case VariantType.Array: case VariantType.Array:
if (type != typeof(object[])) if (type != typeof(object[]))
{ {
@@ -219,6 +223,15 @@ namespace FlaxEditor.Utilities
else else
stream.Write(0); stream.Write(0);
break; break;
case VariantType.Dictionary:
if (type != typeof(Dictionary<object, object>))
{
stream.Write(int.MaxValue);
stream.WriteStrAnsi(type.GetTypeName(), 77);
}
else
stream.Write(0);
break;
default: default:
stream.Write(0); stream.Write(0);
break; break;
@@ -455,7 +468,7 @@ namespace FlaxEditor.Utilities
if (type == null) if (type == null)
type = typeof(object[]); type = typeof(object[]);
else if (!type.IsArray) 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 count = stream.ReadInt32();
var result = Array.CreateInstance(type.GetElementType(), count); var result = Array.CreateInstance(type.GetElementType(), count);
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
@@ -464,8 +477,12 @@ namespace FlaxEditor.Utilities
} }
case VariantType.Dictionary: case VariantType.Dictionary:
{ {
if (type == null)
type = typeof(Dictionary<object, object>);
else if (!type.IsGenericType || type.GetGenericTypeDefinition() != typeof(Dictionary<,>))
throw new Exception("Invalid type for the Variant dictionary " + typeName);
var count = stream.ReadInt32(); var count = stream.ReadInt32();
var result = new Dictionary<object, object>(); var result = (IDictionary)Activator.CreateInstance(type);
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
result.Add(stream.ReadVariant(), stream.ReadVariant()); result.Add(stream.ReadVariant(), stream.ReadVariant());
return result; return result;
@@ -500,7 +517,7 @@ namespace FlaxEditor.Utilities
} }
return null; 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 else
stream.Write(0); stream.Write(0);
break; break;
case VariantType.Dictionary:
if (type != typeof(Dictionary<object, object>))
{
stream.Write(int.MaxValue);
stream.WriteStrAnsi(type.GetTypeName(), 77);
}
else
stream.Write(0);
break;
default: default:
stream.Write(0); stream.Write(0);
break; break;
@@ -660,16 +686,19 @@ namespace FlaxEditor.Utilities
break; break;
} }
case VariantType.Dictionary: case VariantType.Dictionary:
stream.Write(((Dictionary<object, object>)value).Count); {
foreach (var e in (Dictionary<object, object>)value) var dictionary = (IDictionary)value;
stream.Write(dictionary.Count);
foreach (var key in dictionary.Keys)
{ {
stream.WriteVariant(e.Key); stream.WriteVariant(key);
stream.WriteVariant(e.Value); stream.WriteVariant(dictionary[key]);
} }
break; break;
}
case VariantType.Typename: case VariantType.Typename:
if (value is Type) if (value is Type)
stream.WriteStrAnsi(((Type)value).FullName, -14); stream.WriteStrAnsi(((Type)value).GetTypeName(), -14);
else if (value is ScriptType) else if (value is ScriptType)
stream.WriteStrAnsi(((ScriptType)value).TypeName, -14); stream.WriteStrAnsi(((ScriptType)value).TypeName, -14);
break; break;
@@ -708,6 +737,10 @@ namespace FlaxEditor.Utilities
if (value != typeof(object[])) if (value != typeof(object[]))
withoutTypeName = false; withoutTypeName = false;
break; break;
case VariantType.Dictionary:
if (value != typeof(Dictionary<object, object>))
withoutTypeName = false;
break;
} }
if (withoutTypeName) if (withoutTypeName)
{ {
@@ -721,7 +754,7 @@ namespace FlaxEditor.Utilities
stream.WriteValue((int)variantType); stream.WriteValue((int)variantType);
stream.WritePropertyName("TypeName"); stream.WritePropertyName("TypeName");
stream.WriteValue(value.FullName); stream.WriteValue(value.GetTypeName());
stream.WriteEndObject(); stream.WriteEndObject();
} }
@@ -1114,19 +1147,20 @@ namespace FlaxEditor.Utilities
case VariantType.Dictionary: case VariantType.Dictionary:
{ {
stream.WriteStartArray(); stream.WriteStartArray();
foreach (var e in (Dictionary<object, object>)value) var dictionary = (IDictionary)value;
foreach (var key in dictionary.Keys)
{ {
stream.WritePropertyName("Key"); stream.WritePropertyName("Key");
stream.WriteVariant(e.Key); stream.WriteVariant(key);
stream.WritePropertyName("Value"); stream.WritePropertyName("Value");
stream.WriteVariant(e.Value); stream.WriteVariant(dictionary[key]);
} }
stream.WriteEndArray(); stream.WriteEndArray();
break; break;
} }
case VariantType.Typename: case VariantType.Typename:
if (value is Type) if (value is Type)
stream.WriteValue(((Type)value).FullName); stream.WriteValue(((Type)value).GetTypeName());
else if (value is ScriptType) else if (value is ScriptType)
stream.WriteValue(((ScriptType)value).TypeName); stream.WriteValue(((ScriptType)value).TypeName);
break; break;
@@ -1137,7 +1171,7 @@ namespace FlaxEditor.Utilities
stream.WriteRaw(json); stream.WriteRaw(json);
break; 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 // ReSharper restore PossibleNullReferenceException

View File

@@ -175,37 +175,51 @@ namespace FlaxEditor.Windows.Assets
// Parameter type editing // Parameter type editing
var cmType = menu.AddChildMenu("Type"); 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 args = param.Type.GetGenericArguments();
var cm = new ItemsListContextMenu(180); singleValueType = new ScriptType(args[0]);
var newParameterTypes = window.NewParameterTypes; arrayType = singleValueType.MakeArrayType();
foreach (var newParameterType in newParameterTypes) dictionaryType = param.Type;
{ var keyName = window.Surface.GetTypeName(new ScriptType(args[0]));
var item = new TypeSearchPopup.TypeItemView(newParameterType); var valueName = window.Surface.GetTypeName(new ScriptType(args[1]));
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();
ScriptType singleValueType, arrayType; b = cmType.ContextMenu.AddButton($"Dictionary<{keyName}, {valueName}>");
if (param.Type.IsArray) b.Enabled = false;
{
singleValueType = new ScriptType(param.Type.GetElementType()); b = cmType.ContextMenu.AddButton($"Edit key type: {keyName}...", () => OnChangeType(item => window.SetParamType(index, ScriptType.MakeDictionaryType((ScriptType)item.Tag, new ScriptType(args[1])))));
arrayType = param.Type; 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 else
{ {
singleValueType = param.Type; if (isArray)
arrayType = param.Type.MakeArrayType(); {
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 = cmType.ContextMenu.AddButton("Value", () => window.SetParamType(index, singleValueType));
b.Checked = param.Type == singleValueType; b.Checked = param.Type == singleValueType;
b.Enabled = window._canEdit; b.Enabled = window._canEdit;
@@ -214,8 +228,30 @@ namespace FlaxEditor.Windows.Assets
b.Checked = param.Type == arrayType; b.Checked = param.Type == arrayType;
b.Enabled = window._canEdit; b.Enabled = window._canEdit;
b.TooltipText = "Changes parameter type to an array."; 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<ItemsListContextMenu.Item> 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 private sealed class PropertiesProxy
@@ -366,7 +402,7 @@ namespace FlaxEditor.Windows.Assets
gridControl.Height = Button.DefaultHeight; gridControl.Height = Button.DefaultHeight;
gridControl.SlotsHorizontally = 2; gridControl.SlotsHorizontally = 2;
gridControl.SlotsVertically = 1; gridControl.SlotsVertically = 1;
var addOverride = grid.Button("Add Override"); var addOverride = grid.Button("Add Override");
addOverride.Button.Clicked += OnOverrideMethodClicked; addOverride.Button.Clicked += OnOverrideMethodClicked;
// TODO: Add sender arg to button clicked action? // TODO: Add sender arg to button clicked action?

View File

@@ -28,6 +28,7 @@ namespace FlaxEditor.Windows
/// <summary> /// <summary>
/// Proxy object for the Build tab. /// Proxy object for the Build tab.
/// </summary> /// </summary>
[HideInEditor]
[CustomEditor(typeof(BuildTabProxy.Editor))] [CustomEditor(typeof(BuildTabProxy.Editor))]
private class BuildTabProxy private class BuildTabProxy
{ {
@@ -65,6 +66,7 @@ namespace FlaxEditor.Windows
PerPlatformOptions[PlatformType.Mac].Init("Output/Mac", "Mac"); PerPlatformOptions[PlatformType.Mac].Init("Output/Mac", "Mac");
} }
[HideInEditor]
abstract class Platform abstract class Platform
{ {
[HideInEditor] [HideInEditor]

View File

@@ -1190,6 +1190,85 @@ void VisualScriptExecutor::ProcessGroupFlow(Box* boxBase, Node* node, Value& val
} }
break; 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<Variant, Variant>::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<Variant, Variant>::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<Variant, Variant>::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;
}
} }
} }

View File

@@ -245,6 +245,7 @@ public:
Dictionary& _collection; Dictionary& _collection;
int32 _index; int32 _index;
public:
Iterator(Dictionary& collection, const int32 index) Iterator(Dictionary& collection, const int32 index)
: _collection(collection) : _collection(collection)
, _index(index) , _index(index)
@@ -257,8 +258,6 @@ public:
{ {
} }
public:
Iterator(const Iterator& i) Iterator(const Iterator& i)
: _collection(i._collection) : _collection(i._collection)
, _index(i._index) , _index(i._index)
@@ -272,6 +271,10 @@ public:
} }
public: public:
FORCE_INLINE int32 Index() const
{
return _index;
}
FORCE_INLINE bool IsEnd() const FORCE_INLINE bool IsEnd() const
{ {

View File

@@ -66,7 +66,7 @@ namespace
"FlaxEngine.Ray",// Ray "FlaxEngine.Ray",// Ray
"FlaxEngine.Matrix",// Matrix "FlaxEngine.Matrix",// Matrix
"System.Object[]",// Array "System.Object[]",// Array
"Dictionary<System.Object,System.Object>",// Dictionary "System.Collections.Generic.Dictionary`2[System.Object,System.Object]",// Dictionary
"System.Object",// ManagedObject "System.Object",// ManagedObject
"System.Type",// Typename "System.Type",// Typename
"FlaxEngine.Int2"// Int2 "FlaxEngine.Int2"// Int2
@@ -767,6 +767,12 @@ Variant::Variant(const Array<Variant, HeapAllocation>& v)
new(array)Array<Variant, HeapAllocation>(v); new(array)Array<Variant, HeapAllocation>(v);
} }
Variant::Variant(Dictionary<Variant, Variant>&& v)
: Type(VariantType::Dictionary)
{
AsDictionary = New<Dictionary<Variant, Variant>>(MoveTemp(v));
}
Variant::Variant(const Dictionary<Variant, Variant>& v) Variant::Variant(const Dictionary<Variant, Variant>& v)
: Type(VariantType::Dictionary) : Type(VariantType::Dictionary)
{ {

View File

@@ -228,6 +228,7 @@ public:
explicit Variant(const Matrix& v); explicit Variant(const Matrix& v);
Variant(Array<Variant, HeapAllocation>&& v); Variant(Array<Variant, HeapAllocation>&& v);
Variant(const Array<Variant, HeapAllocation>& v); Variant(const Array<Variant, HeapAllocation>& v);
explicit Variant(Dictionary<Variant, Variant, HeapAllocation>&& v);
explicit Variant(const Dictionary<Variant, Variant, HeapAllocation>& v); explicit Variant(const Dictionary<Variant, Variant, HeapAllocation>& v);
explicit Variant(const Span<byte>& v); explicit Variant(const Span<byte>& v);
explicit Variant(const CommonValue& v); explicit Variant(const CommonValue& v);

View File

@@ -14,6 +14,9 @@
#if USE_MONO #if USE_MONO
#include <ThirdParty/mono-2.0/mono/metadata/appdomain.h> #include <ThirdParty/mono-2.0/mono/metadata/appdomain.h>
/// <summary>
/// Utility interop between C++ and C# for Dictionary collection.
/// </summary>
struct FLAXENGINE_API ManagedDictionary struct FLAXENGINE_API ManagedDictionary
{ {
MonoObject* Instance; MonoObject* Instance;
@@ -74,17 +77,13 @@ struct FLAXENGINE_API ManagedDictionary
return result; return result;
} }
static ManagedDictionary New(MonoType* keyType, MonoType* valueType) static MonoReflectionType* GetClass(MonoType* keyType, MonoType* valueType)
{ {
ManagedDictionary result;
auto domain = mono_domain_get(); auto domain = mono_domain_get();
auto scriptingClass = Scripting::GetStaticClass(); auto scriptingClass = Scripting::GetStaticClass();
CHECK_RETURN(scriptingClass, result); CHECK_RETURN(scriptingClass, nullptr);
auto makeGenericMethod = scriptingClass->GetMethod("MakeGenericType", 2); auto makeGenericMethod = scriptingClass->GetMethod("MakeGenericType", 2);
CHECK_RETURN(makeGenericMethod, result); CHECK_RETURN(makeGenericMethod, nullptr);
auto createMethod = StdTypesContainer::Instance()->ActivatorClass->GetMethod("CreateInstance", 2);
CHECK_RETURN(createMethod, result);
auto genericType = MUtils::GetType(StdTypesContainer::Instance()->DictionaryClass->GetNative()); auto genericType = MUtils::GetType(StdTypesContainer::Instance()->DictionaryClass->GetNative());
auto genericArgs = mono_array_new(domain, mono_get_object_class(), 2); auto genericArgs = mono_array_new(domain, mono_get_object_class(), 2);
@@ -100,9 +99,25 @@ struct FLAXENGINE_API ManagedDictionary
{ {
MException ex(exception); MException ex(exception);
ex.Log(LogType::Error, TEXT("")); 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[0] = dictionaryType;
params[1] = nullptr; params[1] = nullptr;
auto instance = createMethod->Invoke(nullptr, params, &exception); auto instance = createMethod->Invoke(nullptr, params, &exception);

View File

@@ -19,11 +19,42 @@
#include "Engine/Scripting/Scripting.h" #include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/ScriptingObject.h" #include "Engine/Scripting/ScriptingObject.h"
#include "Engine/Scripting/StdTypesContainer.h" #include "Engine/Scripting/StdTypesContainer.h"
#include "Engine/Scripting/InternalCalls/ManagedDictionary.h"
#include "Engine/Utilities/StringConverter.h" #include "Engine/Utilities/StringConverter.h"
#include "Engine/Content/Asset.h" #include "Engine/Content/Asset.h"
#if USE_MONO #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 struct _MonoType
{ {
union union
@@ -43,6 +74,21 @@ struct _MonoType
unsigned int pinned : 1; 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) StringView MUtils::ToString(MonoString* str)
{ {
if (str == nullptr) if (str == nullptr)
@@ -439,6 +485,30 @@ Variant MUtils::UnboxVariant(MonoObject* value)
} }
return v; 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<Variant, Variant> 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) if (mono_class_is_subclass_of(klass, Asset::GetStaticClass()->GetNative(), false) != 0)
@@ -473,7 +543,6 @@ Variant MUtils::UnboxVariant(MonoObject* value)
} }
return Variant(value); return Variant(value);
} }
// TODO: support any dictionary unboxing
return Variant(value); return Variant(value);
} }
@@ -642,7 +711,31 @@ MonoObject* MUtils::BoxVariant(const Variant& value)
} }
return (MonoObject*)managed; 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: case VariantType::Structure:
{ {
if (value.AsBlob.Data == nullptr) if (value.AsBlob.Data == nullptr)
@@ -684,7 +777,6 @@ void MUtils::GetClassFullname(MonoObject* obj, MString& fullname)
{ {
if (obj == nullptr) if (obj == nullptr)
return; return;
MonoClass* monoClass = mono_object_get_class(obj); MonoClass* monoClass = mono_object_get_class(obj);
GetClassFullname(monoClass, fullname); GetClassFullname(monoClass, fullname);
} }
@@ -693,11 +785,13 @@ void MUtils::GetClassFullname(MonoClass* monoClass, MString& fullname)
{ {
static MString plusStr("+"); static MString plusStr("+");
static MString dotStr("."); static MString dotStr(".");
MonoClass* nestingClass = mono_class_get_nesting_type(monoClass);
MonoClass* lastClass = monoClass;
// Name
fullname = mono_class_get_name(monoClass); fullname = mono_class_get_name(monoClass);
// Outer class for nested types
MonoClass* nestingClass = mono_class_get_nesting_type(monoClass);
MonoClass* lastClass = monoClass;
while (nestingClass) while (nestingClass)
{ {
lastClass = nestingClass; lastClass = nestingClass;
@@ -705,9 +799,27 @@ void MUtils::GetClassFullname(MonoClass* monoClass, MString& fullname)
nestingClass = mono_class_get_nesting_type(nestingClass); nestingClass = mono_class_get_nesting_type(nestingClass);
} }
// Namespace
const char* lastClassNamespace = mono_class_get_namespace(lastClass); const char* lastClassNamespace = mono_class_get_namespace(lastClass);
if (lastClassNamespace && *lastClassNamespace) if (lastClassNamespace && *lastClassNamespace)
fullname = lastClassNamespace + dotStr + fullname; 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) void MUtils::GetClassFullname(MonoReflectionType* type, MString& fullname)
@@ -715,7 +827,7 @@ void MUtils::GetClassFullname(MonoReflectionType* type, MString& fullname)
if (!type) if (!type)
return; return;
MonoType* monoType = mono_reflection_type_get_type(type); 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); GetClassFullname(monoClass, fullname);
} }
@@ -729,7 +841,7 @@ MonoClass* MUtils::GetClass(MonoReflectionType* type)
if (!type) if (!type)
return nullptr; return nullptr;
MonoType* monoType = mono_reflection_type_get_type(type); 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) 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(mclass->GetNative(), 1);
} }
return mono_array_class_get(mono_get_object_class(), 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: case VariantType::ManagedObject:
return mono_get_object_class(); return mono_get_object_class();
default: ; default: ;
@@ -1052,6 +1175,15 @@ void* MUtils::VariantToManagedArgPtr(Variant& value, const MType& type, bool& fa
object = nullptr; object = nullptr;
return object; 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: default:
break; break;
} }

View File

@@ -10,7 +10,6 @@
#include "Engine/Core/Collections/Dictionary.h" #include "Engine/Core/Collections/Dictionary.h"
#include "Engine/Content/Asset.h" #include "Engine/Content/Asset.h"
#include "Engine/Core/Cache.h" #include "Engine/Core/Cache.h"
#include "Engine/Debug/DebugLog.h"
#include "Engine/Debug/Exceptions/JsonParseException.h" #include "Engine/Debug/Exceptions/JsonParseException.h"
#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Scripting/ManagedSerialization.h" #include "Engine/Scripting/ManagedSerialization.h"
@@ -458,12 +457,11 @@ void ReadStream::ReadVariant(Variant* data)
auto& dictionary = *data->AsDictionary; auto& dictionary = *data->AsDictionary;
dictionary.Clear(); dictionary.Clear();
dictionary.EnsureCapacity(count); dictionary.EnsureCapacity(count);
Variant key, value;
for (int32 i = 0; i < count; i++) for (int32 i = 0; i < count; i++)
{ {
Variant key;
ReadVariant(&key); ReadVariant(&key);
ReadVariant(&value); ReadVariant(&dictionary[MoveTemp(key)]);
dictionary.Add(key, value);
} }
break; break;
} }

View File

@@ -133,6 +133,18 @@ void VisjectExecutor::ProcessGroupConstants(Box* box, Node* node, Value& value)
} }
} }
break; break;
// Dictionary
case 14:
{
value = Variant(Dictionary<Variant, Variant>());
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: default:
break; break;
} }
@@ -1353,4 +1365,63 @@ void VisjectExecutor::ProcessGroupCollections(Box* box, Node* node, Value& value
break; 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;
}
}
}
} }