// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using FlaxEditor.Scripting; using FlaxEngine; using FlaxEngine.Json; using Object = FlaxEngine.Object; namespace FlaxEditor.Content { sealed class VisualScriptParameterInfo : IScriptMemberInfo { private readonly VisualScriptType _type; private readonly VisjectGraphParameter _parameter; internal VisualScriptParameterInfo(VisualScriptType type, VisjectGraphParameter parameter) { _type = type; _parameter = parameter; } /// public string Name => _parameter.Name; /// public bool IsPublic => _parameter.IsPublic; /// public bool IsStatic => false; /// public bool IsVirtual => false; /// public bool IsAbstract => false; /// public bool IsGeneric => false; /// public bool IsField => true; /// public bool IsProperty => false; /// public bool IsMethod => false; /// public bool IsEvent => false; /// public bool HasGet => true; /// public bool HasSet => true; /// public int ParametersCount => 0; /// public ScriptType DeclaringType => new ScriptType(_type); /// public ScriptType ValueType { get { var type = TypeUtils.GetType(_parameter.TypeTypeName); return type ? type : new ScriptType(_parameter.Type); } } /// public int MetadataToken => 0; /// public bool HasAttribute(Type attributeType, bool inherit) { return Surface.SurfaceMeta.HasAttribute(_parameter, attributeType); } /// public object[] GetAttributes(bool inherit) { return Surface.SurfaceMeta.GetAttributes(_parameter); } /// public ScriptMemberInfo.Parameter[] GetParameters() { return null; } /// public object GetValue(object obj) { return _type.Asset.GetScriptInstanceParameterValue(_parameter.Name, (Object)obj); } /// public void SetValue(object obj, object value) { _type.Asset.SetScriptInstanceParameterValue(_parameter.Name, (Object)obj, value); } } sealed class VisualScriptMethodInfo : IScriptMemberInfo { [Flags] private enum Flags { None = 0, Static = 1, Virtual = 2, Override = 4, } private readonly VisualScriptType _type; private readonly int _index; private readonly string _name; private readonly byte _flags; private readonly ScriptType _returnType; private readonly ScriptMemberInfo.Parameter[] _parameters; private object[] _attributes; internal VisualScriptMethodInfo(VisualScriptType type, int index) { _type = type; _index = index; type.Asset.GetMethodSignature(index, out _name, out _flags, out var returnTypeName, out var paramNames, out var paramTypeNames, out var paramOuts); _returnType = TypeUtils.GetType(returnTypeName); if (paramNames.Length != 0) { _parameters = new ScriptMemberInfo.Parameter[paramNames.Length]; for (int i = 0; i < _parameters.Length; i++) { _parameters[i] = new ScriptMemberInfo.Parameter { Name = paramNames[i], Type = TypeUtils.GetType(paramTypeNames[i]), IsOut = paramOuts[i], }; } } else { _parameters = Utils.GetEmptyArray(); } } /// public string Name => _name; /// public bool IsPublic => true; /// public bool IsStatic => (_flags & (byte)Flags.Static) != 0; /// public bool IsVirtual => (_flags & (byte)Flags.Virtual) != 0; /// public bool IsAbstract => false; /// public bool IsGeneric => false; /// public bool IsField => false; /// public bool IsProperty => false; /// public bool IsMethod => true; /// public bool IsEvent => false; /// public bool HasGet => false; /// public bool HasSet => false; /// public int ParametersCount => _parameters.Length; /// public ScriptType DeclaringType => (_flags & (byte)Flags.Override) != 0 ? _type.BaseType : new ScriptType(_type); /// public ScriptType ValueType => _returnType; public int MetadataToken => 0; /// public bool HasAttribute(Type attributeType, bool inherit) { return GetAttributes(inherit).Any(x => x.GetType() == attributeType); } /// public object[] GetAttributes(bool inherit) { if (_attributes == null) { var data = _type.Asset.GetMethodMetaData(_index, Surface.SurfaceMeta.AttributeMetaTypeID); _attributes = Surface.SurfaceMeta.GetAttributes(data); } return _attributes; } /// public ScriptMemberInfo.Parameter[] GetParameters() { return _parameters; } /// public object GetValue(object obj) { throw new NotSupportedException(); } /// public void SetValue(object obj, object value) { throw new NotSupportedException(); } } /// /// The implementation of the /// /// [HideInEditor] public sealed class VisualScriptType : IScriptType { private VisualScript _asset; private ScriptMemberInfo[] _parameters; private ScriptMemberInfo[] _methods; private object[] _attributes; /// /// Gets the Visual Script asset that contains this type. /// public VisualScript Asset => _asset; internal VisualScriptType(VisualScript asset) { _asset = asset; } private void CacheData() { if (_parameters != null) return; if (_asset.WaitForLoaded()) return; FlaxEngine.Content.AssetReloading += OnAssetReloading; // Cache Visual Script parameters info var parameters = _asset.Parameters; if (parameters.Length != 0) { _parameters = new ScriptMemberInfo[parameters.Length]; for (int i = 0; i < parameters.Length; i++) _parameters[i] = new ScriptMemberInfo(new VisualScriptParameterInfo(this, parameters[i])); } else _parameters = Utils.GetEmptyArray(); // Cache Visual Script methods info var methodsCount = _asset.GetMethodsCount(); if (methodsCount != 0) { _methods = new ScriptMemberInfo[methodsCount]; for (int i = 0; i < methodsCount; i++) _methods[i] = new ScriptMemberInfo(new VisualScriptMethodInfo(this, i)); } else _methods = Utils.GetEmptyArray(); // Cache Visual Script attributes var attributesData = _asset.GetMetaData(Surface.SurfaceMeta.AttributeMetaTypeID); if (attributesData != null && attributesData.Length != 0) { _attributes = Surface.SurfaceMeta.GetAttributes(attributesData); } else _attributes = Utils.GetEmptyArray(); } private void OnAssetReloading(Asset asset) { if (asset == _asset) { _parameters = null; _methods = null; FlaxEngine.Content.AssetReloading -= OnAssetReloading; } } internal void Dispose() { OnAssetReloading(_asset); _asset = null; } /// public string Name => Path.GetFileNameWithoutExtension(_asset.Path); /// public string Namespace => string.Empty; /// public string TypeName => JsonSerializer.GetStringID(_asset.ID); /// public bool IsPublic => true; /// public bool IsAbstract => _asset && !_asset.WaitForLoaded() && (_asset.Meta.Flags & VisualScript.Flags.Abstract) != 0; /// public bool IsSealed => _asset && !_asset.WaitForLoaded() && (_asset.Meta.Flags & VisualScript.Flags.Sealed) != 0; /// public bool IsEnum => false; /// public bool IsClass => true; /// public bool IsInterface => false; /// public bool IsArray => false; /// public bool IsValueType => false; /// public bool IsGenericType => false; /// public bool IsReference => false; /// public bool IsPointer => false; /// public bool IsStatic => false; /// public bool CanCreateInstance => !IsAbstract; /// public ScriptType BaseType => _asset && !_asset.WaitForLoaded() ? TypeUtils.GetType(_asset.Meta.BaseTypename) : ScriptType.Null; /// public ContentItem ContentItem => _asset ? Editor.Instance.ContentDatabase.FindAsset(_asset.ID) : null; /// public object CreateInstance() { return Object.New(TypeName); } /// public bool HasInterface(ScriptType c) { return BaseType.HasInterface(c); } /// public bool HasAttribute(Type attributeType, bool inherit) { if (inherit && BaseType.HasAttribute(attributeType, true)) return true; CacheData(); return _attributes.Any(x => x.GetType() == attributeType); } /// public object[] GetAttributes(bool inherit) { CacheData(); if (inherit) { var thisAttributes = _attributes; var baseAttributes = BaseType.GetAttributes(true); if (thisAttributes.Length != 0) { if (baseAttributes.Length != 0) { var resultAttributes = new object[thisAttributes.Length + baseAttributes.Length]; Array.Copy(thisAttributes, 0, resultAttributes, 0, thisAttributes.Length); Array.Copy(baseAttributes, 0, resultAttributes, thisAttributes.Length, baseAttributes.Length); return resultAttributes; } return thisAttributes; } return baseAttributes; } return _attributes; } /// public ScriptMemberInfo[] GetMembers(string name, MemberTypes type, BindingFlags bindingAttr) { var members = new List(); if ((bindingAttr & BindingFlags.DeclaredOnly) == 0) { var baseType = BaseType; if (baseType) members.AddRange(baseType.GetMembers(name, type, bindingAttr)); } CacheData(); if ((type & MemberTypes.Field) != 0) { foreach (var parameter in _parameters) { if (parameter.Filter(name, bindingAttr)) members.Add(parameter); } } if ((type & MemberTypes.Method) != 0) { foreach (var method in _methods) { if (method.Filter(name, bindingAttr)) members.Add(method); } } return members.ToArray(); } /// public ScriptMemberInfo[] GetMembers(BindingFlags bindingAttr) { var members = new List(); if ((bindingAttr & BindingFlags.DeclaredOnly) == 0) { var baseType = BaseType; if (baseType) members.AddRange(baseType.GetMembers(bindingAttr)); } CacheData(); foreach (var parameter in _parameters) { if (parameter.Filter(bindingAttr)) members.Add(parameter); } foreach (var method in _methods) { if (method.Filter(bindingAttr)) members.Add(method); } return members.ToArray(); } /// public ScriptMemberInfo[] GetFields(BindingFlags bindingAttr) { CacheData(); var baseType = BaseType; if (baseType) { var baseFields = baseType.GetFields(bindingAttr); var newArray = new ScriptMemberInfo[_parameters.Length + baseFields.Length]; Array.Copy(_parameters, newArray, _parameters.Length); Array.Copy(baseFields, 0, newArray, _parameters.Length, baseFields.Length); return newArray; } return _parameters; } /// public ScriptMemberInfo[] GetProperties(BindingFlags bindingAttr) { var baseType = BaseType; if (baseType) return baseType.GetProperties(bindingAttr); return Utils.GetEmptyArray(); } /// public ScriptMemberInfo[] GetMethods(BindingFlags bindingAttr) { CacheData(); var baseType = BaseType; if (baseType) { var baseMethods = baseType.GetMethods(bindingAttr); var newArray = new ScriptMemberInfo[_methods.Length + baseMethods.Length]; Array.Copy(_methods, newArray, _methods.Length); Array.Copy(baseMethods, 0, newArray, _methods.Length, baseMethods.Length); return newArray; } return _methods; } } /// /// Implementation of for assets. /// /// public class VisualScriptItem : BinaryAssetItem { /// /// The cached list of all Visual Script assets found in the workspace (engine, game and plugin projects). Updated on workspace modifications. /// public static readonly List VisualScripts = new List(); private VisualScriptType _scriptType; /// /// The Visual Script type. Can be null if failed to load asset. /// public ScriptType ScriptType { get { if (_scriptType == null) { var asset = FlaxEngine.Content.LoadAsync(ID); if (asset) _scriptType = new VisualScriptType(asset); } return new ScriptType(_scriptType); } } /// public VisualScriptItem(string path, ref Guid id, string typeName, Type type) : base(path, ref id, typeName, type, ContentItemSearchFilter.Script) { VisualScripts.Add(this); Editor.Instance.CodeEditing.ClearTypes(); } /// public override bool OnEditorDrag(object context) { return new ScriptType(typeof(Actor)).IsAssignableFrom(ScriptType) && ScriptType.CanCreateInstance; } /// public override Actor OnEditorDrop(object context) { return (Actor)ScriptType.CreateInstance(); } /// public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.VisualScript128; /// protected override bool DrawShadow => false; /// public override void OnDestroy() { if (IsDisposing) return; if (_scriptType != null) { Editor.Instance.CodeEditing.ClearTypes(); _scriptType.Dispose(); _scriptType = null; } VisualScripts.Remove(this); base.OnDestroy(); } } }