using System; using System.Collections.Generic; using System.IO; using System.Linq; using DTT.PublishingTools.Attributes; using UnityEditor; using UnityEditor.Callbacks; using UnityEditor.Compilation; using UnityEngine; using System.Reflection; using System.Text; using DTT.Utils.Workflow; using Assembly = System.Reflection.Assembly; using UnityAssembly = UnityEditor.Compilation.Assembly; namespace DTT.PublishingTools { /// /// Handles DTT Asset attribute implementation. /// internal static class DTTAssetService { /// /// Represents a scriptable object that uses an asset attribute. /// private readonly struct AssetWithAttribute { /// /// The attribute used by the scriptable object. /// public readonly DTTAssetAttribute attribute; /// /// The type of asset to be created. /// public readonly Type assetType; /// /// Creates a new instance of the stucture. /// /// The attribute used by the scriptable object. /// The type of asset to be created. public AssetWithAttribute(DTTAssetAttribute attribute, Type assetType) { this.attribute = attribute; this.assetType = assetType; } } /// /// Called when scripts are reloaded to go through dtt's runtime assemblies /// and create assets for scriptable objects with an Asset attribute. /// [DidReloadScripts] private static void CreateDTTAssetsIfNecessary() { AssetWithAttribute[] attributes = GetAssetsWithAttributes(); for(int i = 0; i < attributes.Length; i++) CreateDTTAssetIfNecessary(attributes[i]); } /// /// Returns an array of scriptable object representations that implement /// an asset attribute. /// /// The array of scriptable object representations. private static AssetWithAttribute[] GetAssetsWithAttributes() { // Get all dtt assemblies. var assetsWithAttributes = new List(); var dttAssemblies = CompilationPipeline.GetAssemblies().Where(IsDTTRuntimeAssembly); // Start adding types with their asset attributes from assemblies to the result list. foreach (UnityAssembly assembly in dttAssemblies) { Type[] types; try { string dataPathWithoutAssets = Application.dataPath.Substring(0, Application.dataPath.LastIndexOf('/') + 1); string combinedPath = Path.Combine(dataPathWithoutAssets, assembly.outputPath); types = Assembly.LoadFile(combinedPath).GetTypes(); } catch(FileNotFoundException) { // Catch file not found exceptions that sometimes occur when trying to load assembly's from unity. continue; } for (int i = 0; i < types.Length; i++) { var attribute = types[i].GetCustomAttributes(typeof(DTTAssetAttribute)).FirstOrDefault(); if (attribute != null) assetsWithAttributes.Add(new AssetWithAttribute((DTTAssetAttribute)attribute, types[i])); } } return assetsWithAttributes.ToArray(); } /// /// Creates a dtt asset if necessary based on whether there already is one created. /// /// The asset that should be created. private static void CreateDTTAssetIfNecessary(AssetWithAttribute asset) { string fullPackageName = asset.attribute.fullPackageName; if (fullPackageName == null) { Debug.LogWarning("DTT asset could not be created :: full package name was null."); return; } AssetJson assetJson = DTTEditorConfig.GetAssetJson(fullPackageName); if (assetJson == null) return; if (!asset.assetType.IsSubclassOf(typeof(ScriptableObject))) { Debug.LogWarning("DTT asset could not be created :: " + "Only scriptable objects can use the DTTAsset attribute."); return; } string projectFolderPath = DTTEditorConfig.DTTProjectFolder + "/" + assetJson.displayName; StringBuilder assetPathBuilder = new StringBuilder(projectFolderPath); // Apply relative path or resources path if necessary. if (asset.attribute.relativePath != null) AppendAssetPath(assetPathBuilder, asset.attribute.relativePath); else if (asset.attribute.isResource) AppendAssetPath(assetPathBuilder, "Resources"); // Ensure the directory exists before creating the asset. EnsureDirectoryExistence(assetPathBuilder.ToString()); // Append an asset name if none has been added manually. if(!Path.HasExtension(assetPathBuilder.ToString())) AppendAssetPath(assetPathBuilder, GetNameForAsset(asset)); string assetPath = assetPathBuilder.ToString(); // Do nothing if the file for the asset already exists. if (File.Exists(assetPath)) return; // If the asset has not yet been created, create one at the asset path. var scriptableObject = ScriptableObject.CreateInstance(asset.assetType); AssetDatabase.CreateAsset(scriptableObject, assetPath); AssetDatabase.SaveAssets(); } /// /// Returns whether a unity assembly is a dtt runtime assembly. /// /// The assembly to check. /// Whether it is a dtt runtime assembly. private static bool IsDTTRuntimeAssembly(UnityAssembly assembly) => assembly.name.StartsWith("DTT") && assembly.name.EndsWith("Runtime"); /// /// Ensures the existence of the directory at given asset path. /// /// The asset path of which to ensure the directory's existence. private static void EnsureDirectoryExistence(string assetPath) { if (Path.HasExtension(assetPath)) assetPath = assetPath.Remove(assetPath.LastIndexOf('/')); PathUtility.EnsureDirectoryExistence(assetPath); } /// /// Returns the name for an asset with an attribute. /// /// The asset with an attribute to get the name for. /// The name for the asset. private static string GetNameForAsset(AssetWithAttribute asset) { string name = asset.attribute.assetName; if (name == null) return asset.assetType.Name + ".asset"; if (!name.EndsWith(".asset")) name += ".asset"; return name; } /// /// Appends a given path to an asset path builder. /// /// The asset path builder to append the path to. /// The path to append. private static void AppendAssetPath(StringBuilder assetPathBuilder, string pathToAppend) { assetPathBuilder.Append("/"); assetPathBuilder.Append(pathToAppend); } } }