diff --git a/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl b/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl index 34376caf3..50cf52bbf 100644 --- a/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl +++ b/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl @@ -107,7 +107,7 @@ float4 PS_Forward(PixelInput input) : SV_Target0 Texture2D sceneColorTexture = MATERIAL_REFLECTIONS_SSR_COLOR; float2 screenUV = materialInput.SvPosition.xy * ScreenSize.zw; float stepSize = ScreenSize.z; // 1 / screenWidth - float maxSamples = 32; + float maxSamples = 48; float worldAntiSelfOcclusionBias = 0.1f; float brdfBias = 0.82f; float drawDistance = 5000.0f; diff --git a/Source/Editor/Content/Items/VisualScriptItem.cs b/Source/Editor/Content/Items/VisualScriptItem.cs index 4ce8a0d9d..3fc030420 100644 --- a/Source/Editor/Content/Items/VisualScriptItem.cs +++ b/Source/Editor/Content/Items/VisualScriptItem.cs @@ -375,9 +375,9 @@ namespace FlaxEditor.Content } /// - public bool ImplementInterface(ScriptType c) + public bool HasInterface(ScriptType c) { - return BaseType.ImplementInterface(c); + return BaseType.HasInterface(c); } /// diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs index 14e5e641f..73cc82192 100644 --- a/Source/Editor/CustomEditors/CustomEditor.cs +++ b/Source/Editor/CustomEditors/CustomEditor.cs @@ -548,7 +548,7 @@ namespace FlaxEditor.CustomEditors text = text.Remove(idx, endIdx - idx); } } - else if (new ScriptType(typeof(FlaxEngine.Object)).IsAssignableFrom(Values.Type)) + else if (ScriptType.FlaxObject.IsAssignableFrom(Values.Type)) { // Object reference text = JsonSerializer.GetStringID(Values[0] as FlaxEngine.Object); @@ -598,7 +598,7 @@ namespace FlaxEditor.CustomEditors return false; } } - else if (new ScriptType(typeof(FlaxEngine.Object)).IsAssignableFrom(Values.Type)) + else if (ScriptType.FlaxObject.IsAssignableFrom(Values.Type)) { // Object reference if (text.Length != 32) diff --git a/Source/Editor/CustomEditors/CustomEditorsUtil.cpp b/Source/Editor/CustomEditors/CustomEditorsUtil.cpp index 0542fa260..a02ad1c4a 100644 --- a/Source/Editor/CustomEditors/CustomEditorsUtil.cpp +++ b/Source/Editor/CustomEditors/CustomEditorsUtil.cpp @@ -57,8 +57,9 @@ void OnBinaryModuleLoaded(BinaryModule* module); MonoReflectionType* CustomEditorsUtil::GetCustomEditor(MonoReflectionType* refType) { + if (!refType) + return nullptr; MonoType* type = mono_reflection_type_get_type(refType); - Entry result; if (Cache.TryGet(type, result)) { @@ -68,7 +69,6 @@ MonoReflectionType* CustomEditorsUtil::GetCustomEditor(MonoReflectionType* refTy return MUtils::GetType(editor->GetNative()); } } - return nullptr; } diff --git a/Source/Editor/CustomEditors/CustomEditorsUtil.cs b/Source/Editor/CustomEditors/CustomEditorsUtil.cs index 211e69d3a..a40b223b9 100644 --- a/Source/Editor/CustomEditors/CustomEditorsUtil.cs +++ b/Source/Editor/CustomEditors/CustomEditorsUtil.cs @@ -59,6 +59,8 @@ namespace FlaxEditor.CustomEditors internal static CustomEditor CreateEditor(ScriptType targetType, bool canUseRefPicker = true) { + if (targetType == ScriptType.Null) + return new GenericEditor(); if (targetType.IsArray) { return new ArrayEditor(); @@ -107,7 +109,7 @@ namespace FlaxEditor.CustomEditors } if (targetType.IsGenericType) { - if (DictionaryEditor.CanEditType(targetTypeType)) + if (targetTypeType.GetGenericTypeDefinition() == typeof(Dictionary<,>)) { return new DictionaryEditor(); } diff --git a/Source/Editor/CustomEditors/Editors/ArrayEditor.cs b/Source/Editor/CustomEditors/Editors/ArrayEditor.cs index b1d4494b0..e641dfb33 100644 --- a/Source/Editor/CustomEditors/Editors/ArrayEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ArrayEditor.cs @@ -21,7 +21,7 @@ namespace FlaxEditor.CustomEditors.Editors { var arrayType = Values.Type; var elementType = arrayType.GetElementType(); - return Array.CreateInstance(elementType, size); + return TypeUtils.CreateArrayInstance(elementType, size); } /// @@ -35,7 +35,7 @@ namespace FlaxEditor.CustomEditors.Editors // Allocate new array var arrayType = Values.Type; var elementType = arrayType.GetElementType(); - var newValues = Array.CreateInstance(elementType, newSize); + var newValues = TypeUtils.CreateArrayInstance(elementType, newSize); var sharedCount = Mathf.Min(oldSize, newSize); if (array != null && sharedCount > 0) @@ -52,7 +52,7 @@ namespace FlaxEditor.CustomEditors.Editors else { // Initialize new entries with default values - var defaultValue = TypeUtils.GetDefaultValue(new ScriptType(elementType)); + var defaultValue = TypeUtils.GetDefaultValue(elementType); for (int i = oldSize; i < newSize; i++) newValues.SetValue(defaultValue, i); } @@ -60,7 +60,7 @@ namespace FlaxEditor.CustomEditors.Editors else if (newSize > 0) { // Initialize new entries with default values - var defaultValue = TypeUtils.GetDefaultValue(new ScriptType(elementType)); + var defaultValue = TypeUtils.GetDefaultValue(elementType); for (int i = 0; i < newSize; i++) newValues.SetValue(defaultValue, i); } @@ -79,7 +79,7 @@ namespace FlaxEditor.CustomEditors.Editors var size = array.Length; var arrayType = Values.Type; var elementType = arrayType.GetElementType(); - var cloned = Array.CreateInstance(elementType, size); + var cloned = TypeUtils.CreateArrayInstance(elementType, size); Array.Copy(array, 0, cloned, 0, size); diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs index 5deff5d71..28a15cd47 100644 --- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs +++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs @@ -101,7 +101,7 @@ namespace FlaxEditor.CustomEditors.Editors get { var type = Values.Type; - return new ScriptType(type.IsGenericType ? type.GetGenericArguments()[0] : type.GetElementType()); + return type.IsGenericType ? new ScriptType(type.GetGenericArguments()[0]) : type.GetElementType(); } } diff --git a/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs b/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs index 5f6dacc9f..5035a48f5 100644 --- a/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs +++ b/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs @@ -142,22 +142,6 @@ namespace FlaxEditor.CustomEditors.Editors private bool _canEditKeys; private bool _keyEdited; - /// - /// Determines whether this editor[can edit the specified dictionary type. - /// - /// Type of the dictionary. - /// True if can edit, otherwise false. - public static bool CanEditType(Type type) - { - // Ensure it's a generic dictionary type - if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>)) - { - return true; - } - - return false; - } - /// /// Gets the length of the collection. /// diff --git a/Source/Editor/Scripting/ScriptType.Custom.cs b/Source/Editor/Scripting/ScriptType.Custom.cs new file mode 100644 index 000000000..17fbfa261 --- /dev/null +++ b/Source/Editor/Scripting/ScriptType.Custom.cs @@ -0,0 +1,148 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +using System; +using System.Reflection; +using FlaxEditor.Content; +using FlaxEngine; + +namespace FlaxEditor.Scripting +{ + /// + /// The implementation of the for array of custom script type. + /// + [HideInEditor] + internal sealed class ScriptTypeArray : IScriptType + { + /// + /// The array element type. + /// + public readonly ScriptType ElementType; + + /// + /// Initializes a new instance of the class. + /// + /// Type of the array element. + public ScriptTypeArray(ScriptType elementType) + { + ElementType = elementType; + } + + /// + public string Name => ElementType + "[]"; + + /// + public string Namespace => ElementType.Namespace; + + /// + public string TypeName + { + get + { + var result = ElementType + "[]"; + var nameSpace = ElementType.Namespace; + if (nameSpace.Length != 0) + result = nameSpace + '.'; + return result; + } + } + + /// + public bool IsPublic => ElementType.IsPublic; + + /// + public bool IsAbstract => false; + + /// + public bool IsSealed => true; + + /// + public bool IsEnum => false; + + /// + public bool IsClass => true; + + /// + public bool IsInterface => false; + + /// + public bool IsArray => true; + + /// + public bool IsValueType => false; + + /// + public bool IsGenericType => false; + + /// + public bool IsReference => false; + + /// + public bool IsPointer => false; + + /// + public bool IsStatic => false; + + /// + public bool CanCreateInstance => false; + + /// + public ScriptType BaseType => new ScriptType(typeof(Array)); + + /// + public ContentItem ContentItem => null; + + /// + public object CreateInstance() + { + return null; + } + + /// + public bool HasInterface(ScriptType c) + { + return false; + } + + /// + public bool HasAttribute(Type attributeType, bool inherit) + { + return false; + } + + /// + public object[] GetAttributes(bool inherit) + { + return Utils.GetEmptyArray(); + } + + /// + public ScriptMemberInfo[] GetMembers(string name, MemberTypes type, BindingFlags bindingAttr) + { + return Utils.GetEmptyArray(); + } + + /// + public ScriptMemberInfo[] GetMembers(BindingFlags bindingAttr) + { + return Utils.GetEmptyArray(); + } + + /// + public ScriptMemberInfo[] GetFields(BindingFlags bindingAttr) + { + return Utils.GetEmptyArray(); + } + + /// + public ScriptMemberInfo[] GetProperties(BindingFlags bindingAttr) + { + return Utils.GetEmptyArray(); + } + + /// + public ScriptMemberInfo[] GetMethods(BindingFlags bindingAttr) + { + return Utils.GetEmptyArray(); + } + } +} diff --git a/Source/Editor/Scripting/ScriptType.Interfaces.cs b/Source/Editor/Scripting/ScriptType.Interfaces.cs new file mode 100644 index 000000000..1e27dd5b9 --- /dev/null +++ b/Source/Editor/Scripting/ScriptType.Interfaces.cs @@ -0,0 +1,318 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +using System; +using System.Collections.Generic; +using System.Reflection; +using FlaxEditor.Content; + +namespace FlaxEditor.Scripting +{ + /// + /// Interface for custom scripting languages type object with metadata. + /// + public interface IScriptType + { + /// + /// Gets a type name (eg. name of the class or enum without leading namespace). + /// + string Name { get; } + + /// + /// Gets a full namespace of the type. + /// + string Namespace { get; } + + /// + /// Gets a full name of the type including leading namespace and any nested types names. Uniquely identifies the type and can be used to find it via . + /// + string TypeName { get; } + + /// + /// Gets a value indicating whether the type is declared public. + /// + bool IsPublic { get; } + + /// + /// Gets a value indicating whether the type is abstract and must be overridden. + /// + bool IsAbstract { get; } + + /// + /// Gets a value indicating whether the type is declared sealed and cannot be overridden. + /// + bool IsSealed { get; } + + /// + /// Gets a value indicating whether the type represents an enumeration. + /// + bool IsEnum { get; } + + /// + /// Gets a value indicating whether the type is a class or a delegate; that is, not a value type or interface. + /// + bool IsClass { get; } + + /// + /// Gets a value indicating whether the type is an interface. + /// + bool IsInterface { get; } + + /// + /// Gets a value indicating whether the type is an array. + /// + bool IsArray { get; } + + /// + /// Gets a value indicating whether the type is a value type (basic type, enumeration or a structure). + /// + bool IsValueType { get; } + + /// + /// Gets a value indicating whether the type is generic type and is used as a template. + /// + bool IsGenericType { get; } + + /// + /// Gets a value indicating whether the type is a reference and value is represented by the valid pointer to the data. + /// + bool IsReference { get; } + + /// + /// Gets a value indicating whether the type is a pointer and value is represented by the pointer to the data. + /// + bool IsPointer { get; } + + /// + /// Gets a value indicating whether the type is static. + /// + bool IsStatic { get; } + + /// + /// Gets a value indicating whether can create default instance of this type via method. + /// + bool CanCreateInstance { get; } + + /// + /// Gets the type from which the current type directly inherits. + /// + ScriptType BaseType { get; } + + /// + /// Gets the editor content item that corresponds to this script type. Can be null. + /// + ContentItem ContentItem { get; } + + /// + /// Creates the instance of the object of this type (or throws exception in case of error). + /// + /// The created instance of the object. + object CreateInstance(); + + /// + /// Determines whether the current type implements the specified interface type. Checks this type, its base classes and implemented interfaces base interfaces too. + /// + /// The type of the interface to check. + /// True if this type implements the given interface, otherwise false. + bool HasInterface(ScriptType c); + + /// + /// Determines whether the specified attribute was defined for this type. + /// + /// The attribute type. + /// true to search this member's inheritance chain to find the attributes; otherwise, false. + /// true if the specified type has attribute; otherwise, false. + bool HasAttribute(Type attributeType, bool inherit); + + /// + /// When overridden in a derived class, returns an array of all custom attributes applied to this member. + /// + /// true to search this member's inheritance chain to find the attributes; otherwise, false. + /// An array that contains all the custom attributes applied to this member, or an array with zero elements if no attributes are defined. + object[] GetAttributes(bool inherit); + + /// + /// Searches for the specified members of the specified member type, using the specified binding constraints. + /// + /// The string containing the name of the members to get. + /// The value to search for. + /// A bitmask comprised of one or more that specify how the search is conducted.-or- Zero, to return an empty array. + /// An array of member objects representing the public members with the specified name, if found; otherwise, an empty array. + ScriptMemberInfo[] GetMembers(string name, MemberTypes type, BindingFlags bindingAttr); + + /// + /// When overridden in a derived class, searches for the members defined for the current type using the specified binding constraints. + /// + /// A bitmask comprised of one or more that specify how the search is conducted.-or- Zero (), to return an empty array. + /// An array of member objects representing all members defined for the current type that match the specified binding constraints.-or- An empty array of type member, if no members are defined for the current type, or if none of the defined members match the binding constraints. + ScriptMemberInfo[] GetMembers(BindingFlags bindingAttr); + + /// + /// When overridden in a derived class, searches for the fields defined for the current type using the specified binding constraints. + /// + /// A bitmask comprised of one or more that specify how the search is conducted.-or- Zero (), to return an empty array. + /// An array of member objects representing all fields defined for the current type that match the specified binding constraints.-or- An empty array of type member, if no fields are defined for the current type, or if none of the defined fields match the binding constraints. + ScriptMemberInfo[] GetFields(BindingFlags bindingAttr); + + /// + /// When overridden in a derived class, searches for the properties defined for the current type using the specified binding constraints. + /// + /// A bitmask comprised of one or more that specify how the search is conducted.-or- Zero (), to return an empty array. + /// An array of member objects representing all properties defined for the current type that match the specified binding constraints.-or- An empty array of type member, if no properties are defined for the current type, or if none of the defined properties match the binding constraints. + ScriptMemberInfo[] GetProperties(BindingFlags bindingAttr); + + /// + /// When overridden in a derived class, searches for the methods defined for the current type using the specified binding constraints. + /// + /// A bitmask comprised of one or more that specify how the search is conducted.-or- Zero (), to return an empty array. + /// An array of member objects representing all methods defined for the current type that match the specified binding constraints.-or- An empty array of type member, if no methods are defined for the current type, or if none of the defined methods match the binding constraints. + ScriptMemberInfo[] GetMethods(BindingFlags bindingAttr); + } + + /// + /// Interface for custom scripting languages type object with metadata. + /// + public interface IScriptMemberInfo + { + /// + /// Gets a member name (eg. name of the field or method without leading class name nor namespace prefixes). + /// + string Name { get; } + + /// + /// Gets a metadata token for sorting so it may not be the actual token. + /// + int MetadataToken { get; } + + /// + /// Gets a value indicating whether the type is declared public. + /// + bool IsPublic { get; } + + /// + /// Gets a value indicating whether the type is declared in static scope. + /// + bool IsStatic { get; } + + /// + /// Gets a value indicating whether this method is declared as virtual (can be overriden). + /// + bool IsVirtual { get; } + + /// + /// Gets a value indicating whether this method is declared as abstract (has to be overriden). + /// + bool IsAbstract { get; } + + /// + /// Gets a value indicating whether this method is declared as generic (needs to be inflated with type arguments). + /// + bool IsGeneric { get; } + + /// + /// Gets a value indicating whether this member is a field. + /// + bool IsField { get; } + + /// + /// Gets a value indicating whether this member is a property. + /// + bool IsProperty { get; } + + /// + /// Gets a value indicating whether this member is a method. + /// + bool IsMethod { get; } + + /// + /// Gets a value indicating whether this member is an event. + /// + bool IsEvent { get; } + + /// + /// Gets a value indicating whether this member value can be gathered (via getter method or directly from the field). + /// + bool HasGet { get; } + + /// + /// Gets a value indicating whether this member value can be set (via setter method or directly to the field). + /// + bool HasSet { get; } + + /// + /// Gets the method parameters count (valid for methods only). + /// + int ParametersCount { get; } + + /// + /// Gets the type that declares this member. + /// + ScriptType DeclaringType { get; } + + /// + /// Gets the type of the value (field type, property type or method return value). + /// + ScriptType ValueType { get; } + + /// + /// Determines whether the specified attribute was defined for this member. + /// + /// The attribute type. + /// true to search this member's inheritance chain to find the attributes; otherwise, false. + /// true if the specified member has attribute; otherwise, false. + bool HasAttribute(Type attributeType, bool inherit); + + /// + /// When overridden in a derived class, returns an array of all custom attributes applied to this member. + /// + /// true to search this member's inheritance chain to find the attributes; otherwise, false. + /// An array that contains all the custom attributes applied to this member, or an array with zero elements if no attributes are defined. + object[] GetAttributes(bool inherit); + + /// + /// Gets the method parameters metadata (or event delegate signature parameters). + /// + ScriptMemberInfo.Parameter[] GetParameters(); + + /// + /// Returns the member value of a specified object. + /// + /// The object whose member value will be returned. + /// The member value of the specified object. + object GetValue(object obj); + + /// + /// Sets the member value of a specified object. + /// + /// The object whose member value will be modified. + /// The new member value. + void SetValue(object obj, object value); + } + + /// + /// Interface for custom scripting languages types information containers that can inject types metadata into Editor. + /// + public interface IScriptTypesContainer + { + /// + /// Tries to get the object type from the given full typename. Searches in-build Flax Engine/Editor assemblies and game assemblies. + /// + /// The full name of the type. + /// The type or null if failed. + ScriptType GetType(string typeName); + + /// + /// Gets all the types within all the loaded assemblies. + /// + /// The result collection. Elements will be added to it. Clear it before usage. + /// Additional callback used to check if the given type is valid. Returns true if add type, otherwise false. + void GetTypes(List result, Func checkFunc); + + /// + /// Gets all the derived types from the given base type (excluding that type) within all the loaded assemblies. + /// + /// The base type. + /// The result collection. Elements will be added to it. Clear it before usage. + /// Additional callback used to check if the given type is valid. Returns true if add type, otherwise false. + void GetDerivedTypes(ScriptType baseType, List result, Func checkFunc); + } +} diff --git a/Source/Editor/Scripting/ScriptType.cs b/Source/Editor/Scripting/ScriptType.cs index 11fdd5fb7..c56d2b8d3 100644 --- a/Source/Editor/Scripting/ScriptType.cs +++ b/Source/Editor/Scripting/ScriptType.cs @@ -703,6 +703,11 @@ namespace FlaxEditor.Scripting /// public static readonly ScriptType Object = new ScriptType(typeof(object)); + /// + /// A that is FlaxEngine.Object. + /// + public static readonly ScriptType FlaxObject = new ScriptType(typeof(FlaxEngine.Object)); + /// /// Gets the type of the script as . /// @@ -714,7 +719,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 +729,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 +783,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). /// @@ -834,7 +832,7 @@ namespace FlaxEditor.Scripting { if (_managed != null) return _managed.GetConstructor(Type.EmptyTypes) != null; - return _custom.CanCreateInstance; + return _custom?.CanCreateInstance ?? false; } } @@ -981,7 +979,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 ""; @@ -1010,12 +1008,12 @@ namespace FlaxEditor.Scripting /// /// The type of the interface to check. /// True if this type implements the given interface, otherwise false. - public bool ImplementInterface(ScriptType c) + public bool HasInterface(ScriptType c) { if (c._managed != null && _managed != null) return c._managed.IsAssignableFrom(_managed); if (_custom != null) - return _custom.ImplementInterface(c); + return _custom.HasInterface(c); return false; } @@ -1039,7 +1037,7 @@ namespace FlaxEditor.Scripting public bool IsAssignableFrom(ScriptType c) { if (IsInterface) - return c.ImplementInterface(this); + return c.HasInterface(this); while (c != Null) { if (c == this) @@ -1082,11 +1080,13 @@ namespace FlaxEditor.Scripting /// When overridden in a derived class, returns the type of the object encompassed or referred to by the current array, pointer or reference type. /// /// The type of the object encompassed or referred to by the current array, pointer, or reference type, or if the current type is not an array or a pointer, or is not passed by reference, or represents a generic type or a type parameter in the definition of a generic type or generic method. - public Type GetElementType() + public ScriptType GetElementType() { if (_managed != null) - return _managed.GetElementType(); - throw new NotImplementedException("TODO: Script.Type.GetElementType for custom types"); + return new ScriptType(_managed.GetElementType()); + if (_custom is ScriptTypeArray array) + return array.ElementType; + return Null; } /// @@ -1119,7 +1119,20 @@ namespace FlaxEditor.Scripting { if (_managed != null) return new ScriptType(_managed.MakeArrayType()); - throw new NotImplementedException("TODO: Script.Type.MakeArrayType for custom types"); + if (_custom != null) + return new ScriptType(new ScriptTypeArray(this)); + throw new ArgumentNullException(); + } + + /// + /// 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) + { + if (keyType == Null || valueType == Null) + throw new ArgumentNullException(); + return new ScriptType(typeof(Dictionary<,>).MakeGenericType(TypeUtils.GetType(keyType), TypeUtils.GetType(valueType))); } /// @@ -1368,313 +1381,4 @@ namespace FlaxEditor.Scripting return ScriptMemberInfo.Null; } } - - /// - /// Interface for custom scripting languages type object with metadata. - /// - public interface IScriptType - { - /// - /// Gets a type name (eg. name of the class or enum without leading namespace). - /// - string Name { get; } - - /// - /// Gets a full namespace of the type. - /// - string Namespace { get; } - - /// - /// Gets a full name of the type including leading namespace and any nested types names. Uniquely identifies the type and can be used to find it via . - /// - string TypeName { get; } - - /// - /// Gets a value indicating whether the type is declared public. - /// - bool IsPublic { get; } - - /// - /// Gets a value indicating whether the type is abstract and must be overridden. - /// - bool IsAbstract { get; } - - /// - /// Gets a value indicating whether the type is declared sealed and cannot be overridden. - /// - bool IsSealed { get; } - - /// - /// Gets a value indicating whether the type represents an enumeration. - /// - bool IsEnum { get; } - - /// - /// Gets a value indicating whether the type is a class or a delegate; that is, not a value type or interface. - /// - bool IsClass { get; } - - /// - /// Gets a value indicating whether the type is an interface. - /// - bool IsInterface { get; } - - /// - /// Gets a value indicating whether the type is an array. - /// - bool IsArray { get; } - - /// - /// Gets a value indicating whether the type is a value type (basic type, enumeration or a structure). - /// - bool IsValueType { get; } - - /// - /// Gets a value indicating whether the type is generic type and is used as a template. - /// - bool IsGenericType { get; } - - /// - /// Gets a value indicating whether the type is a reference and value is represented by the valid pointer to the data. - /// - bool IsReference { get; } - - /// - /// Gets a value indicating whether the type is a pointer and value is represented by the pointer to the data. - /// - bool IsPointer { get; } - - /// - /// Gets a value indicating whether the type is static. - /// - bool IsStatic { get; } - - /// - /// Gets a value indicating whether can create default instance of this type via method. - /// - bool CanCreateInstance { get; } - - /// - /// Gets the type from which the current type directly inherits. - /// - ScriptType BaseType { get; } - - /// - /// Gets the editor content item that corresponds to this script type. Can be null. - /// - ContentItem ContentItem { get; } - - /// - /// Creates the instance of the object of this type (or throws exception in case of error). - /// - /// The created instance of the object. - object CreateInstance(); - - /// - /// Determines whether the current type implements the specified interface type. Checks this type, its base classes and implemented interfaces base interfaces too. - /// - /// The type of the interface to check. - /// True if this type implements the given interface, otherwise false. - bool ImplementInterface(ScriptType c); - - /// - /// Determines whether the specified attribute was defined for this type. - /// - /// The attribute type. - /// true to search this member's inheritance chain to find the attributes; otherwise, false. - /// true if the specified type has attribute; otherwise, false. - bool HasAttribute(Type attributeType, bool inherit); - - /// - /// When overridden in a derived class, returns an array of all custom attributes applied to this member. - /// - /// true to search this member's inheritance chain to find the attributes; otherwise, false. - /// An array that contains all the custom attributes applied to this member, or an array with zero elements if no attributes are defined. - object[] GetAttributes(bool inherit); - - /// - /// Searches for the specified members of the specified member type, using the specified binding constraints. - /// - /// The string containing the name of the members to get. - /// The value to search for. - /// A bitmask comprised of one or more that specify how the search is conducted.-or- Zero, to return an empty array. - /// An array of member objects representing the public members with the specified name, if found; otherwise, an empty array. - ScriptMemberInfo[] GetMembers(string name, MemberTypes type, BindingFlags bindingAttr); - - /// - /// When overridden in a derived class, searches for the members defined for the current type using the specified binding constraints. - /// - /// A bitmask comprised of one or more that specify how the search is conducted.-or- Zero (), to return an empty array. - /// An array of member objects representing all members defined for the current type that match the specified binding constraints.-or- An empty array of type member, if no members are defined for the current type, or if none of the defined members match the binding constraints. - ScriptMemberInfo[] GetMembers(BindingFlags bindingAttr); - - /// - /// When overridden in a derived class, searches for the fields defined for the current type using the specified binding constraints. - /// - /// A bitmask comprised of one or more that specify how the search is conducted.-or- Zero (), to return an empty array. - /// An array of member objects representing all fields defined for the current type that match the specified binding constraints.-or- An empty array of type member, if no fields are defined for the current type, or if none of the defined fields match the binding constraints. - ScriptMemberInfo[] GetFields(BindingFlags bindingAttr); - - /// - /// When overridden in a derived class, searches for the properties defined for the current type using the specified binding constraints. - /// - /// A bitmask comprised of one or more that specify how the search is conducted.-or- Zero (), to return an empty array. - /// An array of member objects representing all properties defined for the current type that match the specified binding constraints.-or- An empty array of type member, if no properties are defined for the current type, or if none of the defined properties match the binding constraints. - ScriptMemberInfo[] GetProperties(BindingFlags bindingAttr); - - /// - /// When overridden in a derived class, searches for the methods defined for the current type using the specified binding constraints. - /// - /// A bitmask comprised of one or more that specify how the search is conducted.-or- Zero (), to return an empty array. - /// An array of member objects representing all methods defined for the current type that match the specified binding constraints.-or- An empty array of type member, if no methods are defined for the current type, or if none of the defined methods match the binding constraints. - ScriptMemberInfo[] GetMethods(BindingFlags bindingAttr); - } - - /// - /// Interface for custom scripting languages type object with metadata. - /// - public interface IScriptMemberInfo - { - /// - /// Gets a member name (eg. name of the field or method without leading class name nor namespace prefixes). - /// - string Name { get; } - - /// - /// Gets a metadata token for sorting so it may not be the actual token. - /// - int MetadataToken { get; } - - /// - /// Gets a value indicating whether the type is declared public. - /// - bool IsPublic { get; } - - /// - /// Gets a value indicating whether the type is declared in static scope. - /// - bool IsStatic { get; } - - /// - /// Gets a value indicating whether this method is declared as virtual (can be overriden). - /// - bool IsVirtual { get; } - - /// - /// Gets a value indicating whether this method is declared as abstract (has to be overriden). - /// - bool IsAbstract { get; } - - /// - /// Gets a value indicating whether this method is declared as generic (needs to be inflated with type arguments). - /// - bool IsGeneric { get; } - - /// - /// Gets a value indicating whether this member is a field. - /// - bool IsField { get; } - - /// - /// Gets a value indicating whether this member is a property. - /// - bool IsProperty { get; } - - /// - /// Gets a value indicating whether this member is a method. - /// - bool IsMethod { get; } - - /// - /// Gets a value indicating whether this member is an event. - /// - bool IsEvent { get; } - - /// - /// Gets a value indicating whether this member value can be gathered (via getter method or directly from the field). - /// - bool HasGet { get; } - - /// - /// Gets a value indicating whether this member value can be set (via setter method or directly to the field). - /// - bool HasSet { get; } - - /// - /// Gets the method parameters count (valid for methods only). - /// - int ParametersCount { get; } - - /// - /// Gets the type that declares this member. - /// - ScriptType DeclaringType { get; } - - /// - /// Gets the type of the value (field type, property type or method return value). - /// - ScriptType ValueType { get; } - - /// - /// Determines whether the specified attribute was defined for this member. - /// - /// The attribute type. - /// true to search this member's inheritance chain to find the attributes; otherwise, false. - /// true if the specified member has attribute; otherwise, false. - bool HasAttribute(Type attributeType, bool inherit); - - /// - /// When overridden in a derived class, returns an array of all custom attributes applied to this member. - /// - /// true to search this member's inheritance chain to find the attributes; otherwise, false. - /// An array that contains all the custom attributes applied to this member, or an array with zero elements if no attributes are defined. - object[] GetAttributes(bool inherit); - - /// - /// Gets the method parameters metadata (or event delegate signature parameters). - /// - ScriptMemberInfo.Parameter[] GetParameters(); - - /// - /// Returns the member value of a specified object. - /// - /// The object whose member value will be returned. - /// The member value of the specified object. - object GetValue(object obj); - - /// - /// Sets the member value of a specified object. - /// - /// The object whose member value will be modified. - /// The new member value. - void SetValue(object obj, object value); - } - - /// - /// Interface for custom scripting languages types information containers that can inject types metadata into Editor. - /// - public interface IScriptTypesContainer - { - /// - /// Tries to get the object type from the given full typename. Searches in-build Flax Engine/Editor assemblies and game assemblies. - /// - /// The full name of the type. - /// The type or null if failed. - ScriptType GetType(string typeName); - - /// - /// Gets all the types within all the loaded assemblies. - /// - /// The result collection. Elements will be added to it. Clear it before usage. - /// Additional callback used to check if the given type is valid. Returns true if add type, otherwise false. - void GetTypes(List result, Func checkFunc); - - /// - /// Gets all the derived types from the given base type (excluding that type) within all the loaded assemblies. - /// - /// The base type. - /// The result collection. Elements will be added to it. Clear it before usage. - /// Additional callback used to check if the given type is valid. Returns true if add type, otherwise false. - void GetDerivedTypes(ScriptType baseType, List result, Func checkFunc); - } } diff --git a/Source/Editor/Scripting/TypeUtils.cs b/Source/Editor/Scripting/TypeUtils.cs index b2b71d370..cc8f082f6 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). /// @@ -258,18 +327,10 @@ namespace FlaxEditor.Scripting return ScriptType.Null; // C#/C++ types - var assemblies = AppDomain.CurrentDomain.GetAssemblies(); - for (int i = 0; i < assemblies.Length; i++) { - var assembly = assemblies[i]; - if (assembly != null) - { - var type = assembly.GetType(typeName); - if (type != null) - { - return new ScriptType(type); - } - } + var type = Type.GetType(typeName); + if (type != null) + return new ScriptType(type); } // Custom types @@ -277,11 +338,23 @@ namespace FlaxEditor.Scripting { var type = customTypesInfo.GetType(typeName); if (type) - { return type; + } + if (typeName.EndsWith("[]")) + { + // Array of custom type + if (typeName[0] == '.') + typeName = typeName.Substring(1); + typeName = typeName.Substring(0, typeName.Length - 2); + foreach (var customTypesInfo in CustomTypes) + { + var type = customTypesInfo.GetType(typeName); + if (type) + return type.MakeArrayType(); } } + Editor.LogWarning($"Failed to find type '{typeName}'."); return ScriptType.Null; } @@ -292,10 +365,10 @@ namespace FlaxEditor.Scripting /// The created object or null if failed. public static object CreateInstance(string typeName) { - var type = GetType(typeName); - if (type) + object obj = null; + ScriptType type = GetType(typeName); + if (type && type.CanCreateInstance) { - object obj = null; try { return obj = type.CreateInstance(); @@ -304,11 +377,19 @@ namespace FlaxEditor.Scripting { Debug.LogException(ex); } - - return obj; } + return obj; + } - return null; + /// + /// Creates a one-dimensional of the specified type and length. + /// + /// The type of the array to create. + /// The length of the array to create. + /// The created object or null if failed. + public static Array CreateArrayInstance(ScriptType elementType, int size) + { + return Array.CreateInstance(GetType(elementType), size); } /// @@ -333,6 +414,8 @@ namespace FlaxEditor.Scripting /// The managed type. public static Type GetType(ScriptType type) { + if (type == ScriptType.Null) + return null; while (type.Type == null) type = type.BaseType; return type.Type; diff --git a/Source/Editor/Surface/Archetypes/Collections.cs b/Source/Editor/Surface/Archetypes/Collections.cs index d09b02a69..59198e350 100644 --- a/Source/Editor/Surface/Archetypes/Collections.cs +++ b/Source/Editor/Surface/Archetypes/Collections.cs @@ -16,7 +16,16 @@ namespace FlaxEditor.Surface.Archetypes { if (type == ScriptType.Null) return box.DefaultType; - return box.DefaultType != null ? new ScriptType(type.GetElementType()) : type; + return box.DefaultType != null ? 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; } /// @@ -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/Archetypes/Parameters.cs b/Source/Editor/Surface/Archetypes/Parameters.cs index 4c7b62c32..10748c447 100644 --- a/Source/Editor/Surface/Archetypes/Parameters.cs +++ b/Source/Editor/Surface/Archetypes/Parameters.cs @@ -181,9 +181,9 @@ namespace FlaxEditor.Surface.Archetypes private NodeElementArchetype[] GetElementArchetypes(SurfaceParameter selected) { - if (selected != null && selected.Type.Type != null) + if (selected != null && selected.Type != ScriptType.Null) { - if (Prototypes != null && Prototypes.TryGetValue(selected.Type.Type, out var elements)) + if (selected.Type.Type != null && Prototypes != null && Prototypes.TryGetValue(selected.Type.Type, out var elements)) { // Special case for Normal Maps if (selected.Type.Type == typeof(Texture) && UseNormalMaps && selected.Value != null) diff --git a/Source/Editor/Surface/Archetypes/Tools.cs b/Source/Editor/Surface/Archetypes/Tools.cs index 6b4da738a..3bf8909b6 100644 --- a/Source/Editor/Surface/Archetypes/Tools.cs +++ b/Source/Editor/Surface/Archetypes/Tools.cs @@ -796,7 +796,7 @@ namespace FlaxEditor.Surface.Archetypes { _picker = new TypePickerControl { - Type = new ScriptType(typeof(FlaxEngine.Object)), + Type = ScriptType.FlaxObject, Bounds = new Rectangle(FlaxEditor.Surface.Constants.NodeMarginX + 20, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize, 160, 16), Parent = this, }; @@ -833,7 +833,7 @@ namespace FlaxEditor.Surface.Archetypes { var type = TypeUtils.GetType((string)Values[0]); var box = (OutputBox)GetBox(0); - box.CurrentType = type ? type : new ScriptType(typeof(FlaxEngine.Object)); + box.CurrentType = type ? type : ScriptType.FlaxObject; } /// @@ -905,7 +905,7 @@ namespace FlaxEditor.Surface.Archetypes { _picker = new TypePickerControl { - Type = new ScriptType(typeof(FlaxEngine.Object)), + Type = ScriptType.FlaxObject, Bounds = new Rectangle(FlaxEditor.Surface.Constants.NodeMarginX + 20, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize, 160, 16), Parent = this, }; 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/Elements/InputBox.cs b/Source/Editor/Surface/Elements/InputBox.cs index 237ae69f0..06c83de7f 100644 --- a/Source/Editor/Surface/Elements/InputBox.cs +++ b/Source/Editor/Surface/Elements/InputBox.cs @@ -1078,7 +1078,7 @@ namespace FlaxEditor.Surface.Elements object obj; var type = CurrentType; - if (new ScriptType(typeof(FlaxEngine.Object)).IsAssignableFrom(type)) + if (ScriptType.FlaxObject.IsAssignableFrom(type)) { // Object reference if (text.Length != 32) 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/SurfaceStyle.cs b/Source/Editor/Surface/SurfaceStyle.cs index 79cab8156..b11fd698e 100644 --- a/Source/Editor/Surface/SurfaceStyle.cs +++ b/Source/Editor/Surface/SurfaceStyle.cs @@ -157,7 +157,7 @@ namespace FlaxEditor.Surface GetConnectionColor(type, hint, out color); } else if (type.IsArray) - GetConnectionColor(new ScriptType(type.GetElementType()), hint, out color); + GetConnectionColor(type.GetElementType(), hint, out color); else if (type.Type == typeof(void)) color = Colors.Impulse; else if (type.Type == typeof(bool)) @@ -180,7 +180,7 @@ namespace FlaxEditor.Surface color = Colors.Enum; else if (type.IsValueType) color = Colors.Structures; - else if (new ScriptType(typeof(FlaxEngine.Object)).IsAssignableFrom(type) || type.IsInterface) + else if (ScriptType.FlaxObject.IsAssignableFrom(type) || type.IsInterface) color = Colors.Object; else if (hint == ConnectionsHint.Vector) color = Colors.Vector; 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.Connecting.cs b/Source/Editor/Surface/VisjectSurface.Connecting.cs index 28fcf1f1f..fb6bc9f84 100644 --- a/Source/Editor/Surface/VisjectSurface.Connecting.cs +++ b/Source/Editor/Surface/VisjectSurface.Connecting.cs @@ -76,7 +76,7 @@ namespace FlaxEditor.Surface // Implicit casting is supported for object reference to test whenever it is valid var toType = to.Type; - if (_supportsImplicitCastFromObjectToBoolean && toType == typeof(bool) && new ScriptType(typeof(FlaxEngine.Object)).IsAssignableFrom(from)) + if (_supportsImplicitCastFromObjectToBoolean && toType == typeof(bool) && ScriptType.FlaxObject.IsAssignableFrom(from)) { return true; } 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/Surface/VisualScriptSurface.cs b/Source/Editor/Surface/VisualScriptSurface.cs index 9e3b8f62d..995aee881 100644 --- a/Source/Editor/Surface/VisualScriptSurface.cs +++ b/Source/Editor/Surface/VisualScriptSurface.cs @@ -492,6 +492,19 @@ namespace FlaxEditor.Surface return true; } } + if (box.HasSingleConnection) + { + var connectedBox = box.Connections[0]; + for (int i = 0; i < state.Locals.Length; i++) + { + ref var local = ref state.Locals[i]; + if (local.BoxId == connectedBox.ID && local.NodeId == connectedBox.ParentNode.ID) + { + text = $"{local.Value ?? string.Empty} ({local.ValueTypeName})"; + return true; + } + } + } } // Evaluate the value using the Visual Scripting backend diff --git a/Source/Editor/Utilities/MemberInfoPath.cs b/Source/Editor/Utilities/MemberInfoPath.cs index 99a4c7799..c76130d61 100644 --- a/Source/Editor/Utilities/MemberInfoPath.cs +++ b/Source/Editor/Utilities/MemberInfoPath.cs @@ -41,7 +41,7 @@ namespace FlaxEditor.Utilities // Special case for collections if (Index != null) - result = new ScriptType(result.GetElementType()); + result = result.GetElementType(); return result; } diff --git a/Source/Editor/Utilities/ObjectSnapshot.cs b/Source/Editor/Utilities/ObjectSnapshot.cs index 7b70438d0..3c7e7ec3a 100644 --- a/Source/Editor/Utilities/ObjectSnapshot.cs +++ b/Source/Editor/Utilities/ObjectSnapshot.cs @@ -64,11 +64,11 @@ namespace FlaxEditor.Utilities && memberValue != null && !refStack.Contains(memberValue)) { - if (memberType.IsArray && !typeof(FlaxEngine.Object).IsAssignableFrom(memberType.GetElementType())) + if (memberType.IsArray && !ScriptType.FlaxObject.IsAssignableFrom(memberType.GetElementType())) { // Array var array = (Array)memberValue; - var elementType = new ScriptType(memberType.GetElementType()); + var elementType = memberType.GetElementType(); var length = array.Length; refStack.Push(memberValue); for (int i = 0; i < length; i++) @@ -78,7 +78,7 @@ namespace FlaxEditor.Utilities } refStack.Pop(); } - else if (typeof(IList).IsAssignableFrom(memberType.Type) && !typeof(FlaxEngine.Object).IsAssignableFrom(memberType.GetElementType())) + else if (typeof(IList).IsAssignableFrom(memberType.Type) && !ScriptType.FlaxObject.IsAssignableFrom(memberType.GetElementType())) { // List var list = (IList)memberValue; @@ -105,7 +105,7 @@ namespace FlaxEditor.Utilities GetEntries(new MemberInfoPath.Entry(member.Member, key), membersPath, result, values, refStack, valueType, value); } } - else if (memberType.IsClass && !new ScriptType(typeof(FlaxEngine.Object)).IsAssignableFrom(memberType)) + else if (memberType.IsClass && !ScriptType.FlaxObject.IsAssignableFrom(memberType)) { // Object refStack.Push(memberValue); diff --git a/Source/Editor/Utilities/VariantUtils.cs b/Source/Editor/Utilities/VariantUtils.cs index 85f055ec8..8cca826ff 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; @@ -269,7 +282,7 @@ namespace FlaxEditor.Utilities case VariantType.Pointer: return new ScriptType(typeof(IntPtr)); case VariantType.String: return new ScriptType(typeof(string)); case VariantType.Typename: return new ScriptType(typeof(Type)); - case VariantType.Object: return new ScriptType(typeof(FlaxEngine.Object)); + case VariantType.Object: return ScriptType.FlaxObject; case VariantType.Asset: return new ScriptType(typeof(Asset)); case VariantType.Vector2: return new ScriptType(typeof(Vector2)); case VariantType.Vector3: return new ScriptType(typeof(Vector3)); @@ -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 bb3844eab..4f80d2ea6 100644 --- a/Source/Editor/Windows/Assets/VisualScriptWindow.cs +++ b/Source/Editor/Windows/Assets/VisualScriptWindow.cs @@ -175,37 +175,56 @@ 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.SortItems(); - 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 (param.Type == ScriptType.Null) + { + b = cmType.ContextMenu.AddButton(window.Surface.GetTypeName(param.Type) + "...", () => OnChangeType(item => window.SetParamType(index, (ScriptType)item.Tag))); + return; + } + else if (isArray) + { + singleValueType = 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 +233,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.SortItems(); + cm.Show(window, window.PointFromScreen(Input.MouseScreenPosition)); + } } private sealed class PropertiesProxy @@ -366,7 +407,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? @@ -1198,6 +1239,22 @@ namespace FlaxEditor.Windows.Assets AfterType = type, AfterValue = TypeUtils.GetDefaultValue(type), }; + if (action.BeforeValue != null) + { + // Try to maintain existing value + var beforeType = TypeUtils.GetObjectType(action.BeforeValue); + if (type.IsArray && beforeType.IsArray && type.GetElementType().IsAssignableFrom(beforeType.GetElementType())) + { + var beforeArray = (Array)action.BeforeValue; + var afterArray = TypeUtils.CreateArrayInstance(type.GetElementType(), beforeArray.Length); + Array.Copy(beforeArray, afterArray, beforeArray.Length); + action.AfterValue = afterArray; + } + else if (type.IsAssignableFrom(beforeType)) + { + action.AfterValue = action.BeforeValue; + } + } _undo.AddAction(action); action.Do(); } diff --git a/Source/Editor/Windows/GameCookerWindow.cs b/Source/Editor/Windows/GameCookerWindow.cs index 74ca3ea9b..c67c90644 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..9f1023ad0 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -222,7 +222,7 @@ void VisualScriptExecutor::ProcessGroupParameters(Box* box, Node* node, Value& v LOG(Error, "Failed to access Visual Script parameter for {0}.", stack.Stack->Instance->ToString()); PrintStack(LogType::Error); } - if (node->Boxes[2].HasConnection()) + if (box->ID == 0 && node->Boxes[2].HasConnection()) eatBox(node, node->Boxes[2].FirstConnection()); break; } @@ -1154,17 +1154,19 @@ void VisualScriptExecutor::ProcessGroupFlow(Box* boxBase, Node* node, Value& val arrayValue.NodeId = node->ID; arrayValue.BoxId = 1; arrayValue.Value = tryGetValue(node->GetBox(1), Value::Null); - if (arrayValue.Value.Type.Type != VariantType::Array) + if (arrayValue.Value.Type.Type == VariantType::Array) + { + const int32 count = arrayValue.Value.AsArray().Count(); + for (; iteratorValue.Value.AsInt < count; iteratorValue.Value.AsInt++) + { + boxBase = node->GetBox(3); + if (boxBase->HasConnection()) + eatBox(node, boxBase->FirstConnection()); + } + } + else if (arrayValue.Value.Type.Type != VariantType::Null) { OnError(node, boxBase, String::Format(TEXT("Input value {0} is not an array."), arrayValue.Value)); - return; - } - const int32 count = arrayValue.Value.AsArray().Count(); - for (; iteratorValue.Value.AsInt < count; iteratorValue.Value.AsInt++) - { - boxBase = node->GetBox(3); - if (boxBase->HasConnection()) - eatBox(node, boxBase->FirstConnection()); } boxBase = node->GetBox(6); if (boxBase->HasConnection()) @@ -1190,6 +1192,88 @@ 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) + { + 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(); + } + } + else if (dictionaryValue.Value.Type.Type != VariantType::Null) + { + OnError(node, boxBase, String::Format(TEXT("Input value {0} is not a dictionary."), dictionaryValue.Value)); + return; + } + 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 2de3cecc9..1979c4e74 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/Math/Vector2.cs b/Source/Engine/Core/Math/Vector2.cs index 170ca2bef..0b3de37ac 100644 --- a/Source/Engine/Core/Math/Vector2.cs +++ b/Source/Engine/Core/Math/Vector2.cs @@ -1679,7 +1679,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Vector2 left, Vector2 right) { - return Mathf.NearEqual(left.X, left.X) && Mathf.NearEqual(left.Y, right.Y); + return Mathf.NearEqual(left.X, right.X) && Mathf.NearEqual(left.Y, right.Y); } /// @@ -1691,7 +1691,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Vector2 left, Vector2 right) { - return !Mathf.NearEqual(left.X, left.X) || !Mathf.NearEqual(left.Y, right.Y); + return !Mathf.NearEqual(left.X, right.X) || !Mathf.NearEqual(left.Y, right.Y); } /// diff --git a/Source/Engine/Core/Math/Vector4.cs b/Source/Engine/Core/Math/Vector4.cs index 3ff17840d..5048bd622 100644 --- a/Source/Engine/Core/Math/Vector4.cs +++ b/Source/Engine/Core/Math/Vector4.cs @@ -1445,7 +1445,7 @@ namespace FlaxEngine [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Vector4 left, Vector4 right) { - return left.Equals(ref right); + return Mathf.NearEqual(left.X, right.X) && Mathf.NearEqual(left.Y, right.Y) && Mathf.NearEqual(left.Z, right.Z) && Mathf.NearEqual(left.W, right.W); } /// 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/Level/Actor.h b/Source/Engine/Level/Actor.h index 339b61c28..be11049e4 100644 --- a/Source/Engine/Level/Actor.h +++ b/Source/Engine/Level/Actor.h @@ -210,7 +210,7 @@ public: /// /// Type of the actor to search for. Includes any actors derived from the type. /// The child actor or null. - API_FUNCTION() Actor* GetChild(const MClass* type) const; + API_FUNCTION() Actor* GetChild(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type) const; /// /// Gets the child actor of the given type. @@ -227,7 +227,7 @@ public: /// /// Type of the actor to search for. Includes any actors derived from the type. /// The child actors. - API_FUNCTION() Array GetChildren(const MClass* type) const; + API_FUNCTION() Array GetChildren(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type) const; /// /// Gets the child actors of the given type. @@ -273,7 +273,7 @@ public: /// /// Type of the script to search for. Includes any scripts derived from the type. /// The script or null. - API_FUNCTION() Script* GetScript(const MClass* type) const; + API_FUNCTION() Script* GetScript(API_PARAM(Attributes="TypeReference(typeof(Script))") const MClass* type) const; /// /// Gets the script of the given type from this actor. @@ -290,7 +290,7 @@ public: /// /// Type of the script to search for. Includes any scripts derived from the type. /// The scripts. - API_FUNCTION() Array GetScripts(const MClass* type) const; + API_FUNCTION() Array GetScripts(API_PARAM(Attributes="TypeReference(typeof(Script))") const MClass* type) const; /// /// Gets the scripts of the given type from this actor. @@ -702,7 +702,7 @@ public: /// /// Type of the actor to search for. Includes any actors derived from the type. /// Actor instance if found, null otherwise. - API_FUNCTION() Actor* FindActor(const MClass* type) const; + API_FUNCTION() Actor* FindActor(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type) const; /// /// Tries to find the actor of the given type in this actor hierarchy (checks this actor and all children hierarchy). @@ -719,7 +719,7 @@ public: /// /// Type of the actor to search for. Includes any actors derived from the type. /// Script instance if found, null otherwise. - API_FUNCTION() Script* FindScript(const MClass* type) const; + API_FUNCTION() Script* FindScript(API_PARAM(Attributes="TypeReference(typeof(Script))") const MClass* type) const; /// /// Tries to find the script of the given type in this actor hierarchy (checks this actor and all children hierarchy). diff --git a/Source/Engine/Level/Level.h b/Source/Engine/Level/Level.h index 34691c474..8a6e53aae 100644 --- a/Source/Engine/Level/Level.h +++ b/Source/Engine/Level/Level.h @@ -362,7 +362,7 @@ public: /// /// Type of the actor to search for. Includes any actors derived from the type. /// Found actor or null. - API_FUNCTION() static Actor* FindActor(const MClass* type); + API_FUNCTION() static Actor* FindActor(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type); /// /// Tries to find the actor of the given type in all the loaded scenes. @@ -379,7 +379,7 @@ public: /// /// Type of the script to search for. Includes any scripts derived from the type. /// Found script or null. - API_FUNCTION() static Script* FindScript(const MClass* type); + API_FUNCTION() static Script* FindScript(API_PARAM(Attributes="TypeReference(typeof(Script))") const MClass* type); /// /// Tries to find the script of the given type in all the loaded scenes. @@ -396,14 +396,14 @@ public: /// /// Type of the actor to search for. Includes any actors derived from the type. /// Found actors list. - API_FUNCTION() static Array GetActors(const MClass* type); + API_FUNCTION() static Array GetActors(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type); /// /// Finds all the scripts of the given type in all the loaded scenes. /// /// Type of the script to search for. Includes any scripts derived from the type. /// Found scripts list. - API_FUNCTION() static Array GetScripts(const MClass* type); + API_FUNCTION() static Array GetScripts(API_PARAM(Attributes="TypeReference(typeof(Script))") const MClass* type); /// /// Tries to find scene with given ID. 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 2ece08172..9d2d8a25c 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; } @@ -1257,6 +1269,8 @@ void VisjectExecutor::ProcessGroupCollections(Box* box, Node* node, Value& value { // Array Variant v = tryGetValue(node->GetBox(0), Value::Null); + if (v.Type.Type == VariantType::Null) + v = Variant(Array()); ENSURE(v.Type.Type == VariantType::Array, String::Format(TEXT("Input value {0} is not an array."), v)); auto& array = v.AsArray(); Box* b; @@ -1359,4 +1373,65 @@ void VisjectExecutor::ProcessGroupCollections(Box* box, Node* node, Value& value break; } } + else if (node->TypeID < 200) + { + // Dictionary + Variant v = tryGetValue(node->GetBox(0), Value::Null); + if (v.Type.Type == VariantType::Null) + v = Variant(Dictionary()); + 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; + } + } + } }