//#define DEBUG_IMPORTER using UnityEditor; using UnityEngine; using UnityEditor.AssetImporters; using UnityEngine.Video; using System; using System.Linq; using System.IO; using System.Collections.Generic; [ScriptedImporter(2, "xaml")] class NoesisXamlImporter : ScriptedImporter { [UnityEditor.InitializeOnLoadMethod] static void RegisterApplicationResourcesHash() { string projectPath = System.IO.Path.GetDirectoryName(Application.dataPath); string filename = System.IO.Path.Combine(projectPath, "Library", "Noesis", "ApplicationResources_Hash"); if (File.Exists(filename)) { try { string hash = File.ReadAllText(filename); AssetDatabase.RegisterCustomDependency("Noesis_ApplicationResources", Hash128.Parse(hash)); } catch (Exception e) { Debug.LogException(e); } } } [Serializable] struct Dependencies { public string hash; public long timestamp; public List items; } static string HashFile(string path) { var hash = new Hash128(); hash.Append(File.ReadAllText(path)); return hash.ToString(); } static string HashStr(string str) { var hash = new Hash128(); hash.Append(str); return hash.ToString(); } static string DependenciesPath() { string projectPath = System.IO.Path.GetDirectoryName(Application.dataPath); return System.IO.Path.Combine(projectPath, "Library", "Noesis", "Dependencies2"); } static bool LoadDependencies(string path, ref Dependencies deps) { string folder = DependenciesPath(); string filename = Path.Combine(folder, HashStr(path)); if (File.Exists(filename)) { try { string json = File.ReadAllText(filename); deps = JsonUtility.FromJson(json); return true; } catch (Exception e) { Debug.LogException(e); } } return false; } static void SaveDependencies(string path, Dependencies deps) { try { string folder = DependenciesPath(); System.IO.Directory.CreateDirectory(folder); string json = JsonUtility.ToJson(deps); File.WriteAllText(Path.Combine(folder, HashStr(path)), json); } catch (Exception e) { Debug.LogException(e); } } static IEnumerable FindFonts(string uri) { int index = uri.IndexOf('#'); if (index != -1) { string folder = uri.Substring(0, index); if (Directory.Exists(folder)) { string family = uri.Substring(index + 1); var files = Directory.EnumerateFiles(folder).Where(s => IsFont(s)); foreach (var font in files) { using (FileStream file = File.Open(font, FileMode.Open, FileAccess.Read, FileShare.Read)) { if (NoesisUnity.HasFamily(file, family)) { yield return font; } } } } } } static Dependencies GetCachedDependencies(string path) { Dependencies deps = new Dependencies(); bool cached = LoadDependencies(path, ref deps); long timestamp = 0; try { timestamp = File.GetLastWriteTime(path).Ticks; } catch (Exception e) { Debug.LogException(e); } if (cached) { if (timestamp == deps.timestamp) { return deps; } deps.timestamp = timestamp; string hash = HashFile(path); if (hash == deps.hash) { SaveDependencies(path, deps); return deps; } deps.hash = hash; } else { deps.timestamp = timestamp; deps.hash = HashFile(path); } #if DEBUG_IMPORTER Debug.Log($"=> Dependencies {path}"); #endif deps.items = new List(); bool typesWithAttributeRequested = false; TypeCache.TypeCollection typesWithAttribute; using (FileStream file = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read)) { Noesis.GUI.GetXamlDependencies(file, path, (uri_, type) => { try { string assembly = Noesis.UriHelper.GetAssembly(uri_); string uri = Noesis.UriHelper.GetPath(uri_); if (assembly == "Noesis.GUI.Extensions") { uri = $"Packages/com.noesis.noesisgui/{uri}"; } if (type == Noesis.XamlDependencyType.Filename) { deps.items.Add(uri); } else if (type == Noesis.XamlDependencyType.Font) { foreach (var font in FindFonts(uri)) { if (!deps.items.Contains(font)) { deps.items.Add(font); } } } else if (type == Noesis.XamlDependencyType.Class) { if (!typesWithAttributeRequested) { typesWithAttributeRequested = true; typesWithAttribute = TypeCache.GetTypesWithAttribute(); } foreach (var cachedType in typesWithAttribute) { if (cachedType.FullName == uri) { object[] attributes = cachedType.GetCustomAttributes(typeof(Noesis.AssetDependencyAttribute), false); foreach (Noesis.AssetDependencyAttribute attribute in attributes) { if (String.IsNullOrEmpty(AssetDatabase.AssetPathToGUID(attribute.AssetPath))) { Debug.LogWarning($"[{cachedType.FullName}] Invalid AssetDependency '{attribute.AssetPath}'"); } else { deps.items.Add(attribute.AssetPath); } } } } } } catch (Exception e) { Debug.LogException(e); } }); } SaveDependencies(path, deps); return deps; } static string[] GatherDependenciesFromSourceFile(string path) { NoesisUnity.InitCore(); List deps = new List(); try { foreach (var dep in GetCachedDependencies(path).items) { if (File.Exists(dep)) { deps.Add(dep); } } } catch (Exception e) { Debug.LogException(e); } return deps.ToArray(); } static bool AddFont(string uri, ref List fonts) { NoesisFont font = AssetDatabase.LoadAssetAtPath(uri); if (font != null) { fonts.Add(font); return true; } return false; } static bool AddTexture(string uri, ref List textures, ref List sprites) { if (AssetImporter.GetAtPath(uri) is TextureImporter textureImporter) { if (!AssetDatabase.GetLabels(textureImporter).Contains("Noesis")) { Debug.LogWarning($"{uri} is missing Noesis label"); } if (textureImporter.textureType == TextureImporterType.Sprite && textureImporter.spriteImportMode == SpriteImportMode.Single) { Sprite sprite = AssetDatabase.LoadAssetAtPath(uri); if (sprite != null) { sprites.Add(sprite); return true; } } else { Texture texture = AssetDatabase.LoadAssetAtPath(uri); if (texture != null) { textures.Add(texture); return true; } } } return false; } static bool AddAudio(string uri, ref List audios) { AudioClip audio = AssetDatabase.LoadAssetAtPath(uri); if (audio != null) { audios.Add(audio); return true; } return false; } static bool AddVideo(string uri, ref List videos) { VideoClip video = AssetDatabase.LoadAssetAtPath(uri); if (video != null) { videos.Add(video); return true; } return false; } static bool AddRive(string uri, ref List rives) { NoesisRive rive = AssetDatabase.LoadAssetAtPath(uri); if (rive != null) { rives.Add(rive); return true; } return false; } static bool AddXaml(string uri, ref List xamls) { NoesisXaml xaml = AssetDatabase.LoadAssetAtPath(uri); if (xaml != null) { xamls.Add(xaml); return true; } return false; } static bool AddShader(string uri, ref List shaders) { NoesisShader shader = AssetDatabase.LoadAssetAtPath(uri); if (shader != null) { shaders.Add(shader); return true; } return false; } static void ScanDependencies(AssetImportContext ctx, out List fonts_, out List textures_, out List sprites_, out List audios_, out List videos_, out List rives_, out List xamls_, out List shaders_) { List fonts = new List(); List textures = new List(); List sprites = new List(); List audios = new List(); List videos = new List(); List rives = new List(); List xamls = new List(); List shaders = new List(); string filename = ctx.assetPath; try { if (HasExtension(filename, ".xaml")) { // Add dependency to code-behind, just the source, we don't need the artifact // Even if the file doesn't exist we add it to get a reimport first time code-behind is created ctx.DependsOnSourceAsset(filename + ".cs"); } var dependencies = GetCachedDependencies(filename); foreach (var dep in dependencies.items) { // Register the dependency even if it doesn't currently exist. // This ensures we are re-evaluated if it is imported later. ctx.DependsOnArtifact(dep); if (!String.IsNullOrEmpty(AssetDatabase.AssetPathToGUID(dep))) { if (!AddXaml(dep, ref xamls)) { if (!AddTexture(dep, ref textures, ref sprites)) { if (!AddFont(dep, ref fonts)) { if (!AddAudio(dep, ref audios)) { if (!AddVideo(dep, ref videos)) { if (!AddShader(dep, ref shaders)) { AddRive(dep, ref rives); } } } } } } } } } catch (Exception e) { Debug.LogException(e); } fonts_ = fonts; textures_ = textures; sprites_ = sprites; audios_ = audios; videos_ = videos; rives_ = rives; xamls_ = xamls; shaders_ = shaders; } public override void OnImportAsset(AssetImportContext ctx) { NoesisUnity.InitCore(); #if DEBUG_IMPORTER Debug.Log($"=> Import {ctx.assetPath}"); #endif NoesisXaml xaml = (NoesisXaml)ScriptableObject.CreateInstance(); xaml.uri = ctx.assetPath; xaml.content = System.Text.Encoding.UTF8.GetBytes(File.ReadAllText(ctx.assetPath)); // Add dependencies List fonts; List textures; List sprites; List audios; List videos; List rives; List xamls; List shaders; ScanDependencies(ctx, out fonts, out textures, out sprites, out audios, out videos, out rives, out xamls, out shaders); xaml.xamls = xamls.Select(x => new NoesisXaml.Xaml { uri = AssetDatabase.GetAssetPath(x), xaml = x }).ToArray(); xaml.textures = textures.Select(x => new NoesisXaml.Texture { uri = AssetDatabase.GetAssetPath(x), texture = x }).ToArray(); xaml.sprites = sprites.Select(x => new NoesisXaml.Sprite { uri = AssetDatabase.GetAssetPath(x), sprite = x }).ToArray(); xaml.audios = audios.Select(x => new NoesisXaml.Audio { uri = AssetDatabase.GetAssetPath(x), audio = x }).ToArray(); xaml.videos = videos.Select(x => new NoesisXaml.Video { uri = AssetDatabase.GetAssetPath(x), video = x }).ToArray(); xaml.rives = rives.Select(x => new NoesisXaml.Rive { uri = AssetDatabase.GetAssetPath(x), rive = x }).ToArray(); xaml.fonts = fonts.Select(x => new NoesisXaml.Font { uri = AssetDatabase.GetAssetPath(x), font = x }).ToArray(); xaml.shaders = shaders.Select(x => new NoesisXaml.Shader { uri = AssetDatabase.GetAssetPath(x), shader = x} ).ToArray(); // Depends on global dictionary ctx.DependsOnCustomDependency("Noesis_ApplicationResources"); ctx.AddObjectToAsset("XAML", xaml); ctx.SetMainObject(xaml); } static bool HasExtension(string filename, string extension) { return filename.EndsWith(extension, StringComparison.OrdinalIgnoreCase); } static bool IsFont(string filename) { return HasExtension(filename, ".ttf") || HasExtension(filename, ".otf") || HasExtension(filename, ".ttc"); } }