using System; using System.Collections.Generic; using System.IO; using Uigenx.Protocol; using UnityEngine; namespace Uigenx.Editor { /// /// The on disk twin of `sync.bundle.manifest`, written by `ugx export --to unity`. It exists so /// an export with no daemon running still produces a working registry: CI, a teammate who was /// handed the files, and the first export before anyone starts the studio. /// /// /// The screen, action and panel shapes are the protocol types, so the file and the wire can /// never drift apart. Only is extra. /// public sealed class UgxManifestFile { public const string FileName = "ugx-manifest.json"; /// Bumped only when the file shape changes incompatibly. public const int SupportedSchemaVersion = 1; public int SchemaVersion { get; set; } = SupportedSchemaVersion; public List Screens { get; set; } = new List(); public List Actions { get; set; } = new List(); public List Panels { get; set; } = new List(); public List Fonts { get; set; } = new List(); public static string PathFor(string assetsUiFolder) => (assetsUiFolder ?? string.Empty).TrimEnd('/') + "/Generated/" + FileName; public static bool Exists(string assetsUiFolder) => File.Exists(PathFor(assetsUiFolder)); /// /// Reads the manifest. Returns false with a message the caller can log rather than throwing, /// because a missing file is the normal state in a project that only ever syncs live. /// public static bool TryLoad(string assetsUiFolder, out UgxManifestFile manifest, out string problem) { manifest = null; var path = PathFor(assetsUiFolder); if (!File.Exists(path)) { problem = "no manifest at " + path; return false; } try { manifest = UgxJson.Deserialize(File.ReadAllText(path)); } catch (Exception e) { problem = path + " is not readable: " + e.Message; return false; } if (manifest == null) { problem = path + " is empty"; return false; } if (manifest.SchemaVersion > SupportedSchemaVersion) { problem = path + " is schema version " + manifest.SchemaVersion + " but this package " + "understands " + SupportedSchemaVersion + ". Update com.uigenx.runtime."; manifest = null; return false; } manifest.Screens = manifest.Screens ?? new List(); manifest.Actions = manifest.Actions ?? new List(); manifest.Panels = manifest.Panels ?? new List(); manifest.Fonts = manifest.Fonts ?? new List(); problem = null; return true; } public UgxBundleManifest ToManifest() => new UgxBundleManifest { Screens = Screens ?? new List(), Actions = Actions ?? new List(), Panels = Panels ?? new List(), Fonts = Fonts ?? new List(), }; /// Writes the file. Used by tests and by anything that wants to seed a project. public void Save(string assetsUiFolder) { var path = PathFor(assetsUiFolder); Directory.CreateDirectory(Path.GetDirectoryName(path) ?? assetsUiFolder); File.WriteAllText(path, UgxJson.Serialize(this) + "\n"); } } /// /// Rebuilds the registry whenever the manifest lands in the project, so dropping an offline /// export into `Assets/` is enough. Without this the files import but nothing registers. /// public sealed class UgxManifestPostprocessor : UnityEditor.AssetPostprocessor { static bool _pending; static void OnPostprocessAllAssets(string[] imported, string[] deleted, string[] moved, string[] movedFrom) { var folder = UgxProjectSettings.Load().AssetsUiFolder; var manifestPath = UgxManifestFile.PathFor(folder); if (!Mentions(imported, manifestPath) && !Mentions(moved, manifestPath)) return; if (_pending) return; _pending = true; // A batch run quits before delayCall is ever pumped, so a headless export would import // the manifest and register nothing, which is the whole point of the offline path. // Building here is safe: this callback only fires for the manifest, and nothing it // writes is the manifest. if (Application.isBatchMode) { _pending = false; Build(folder); return; } // Deferred: writing the registry asset from inside an import callback re-enters the // asset pipeline. UnityEditor.EditorApplication.delayCall += () => { _pending = false; Build(folder); }; } static void Build(string folder) { UgxScreenRegistryBuilder.RebuildFromDisk(folder); foreach (var warning in UgxFontAssetBuilder.BuildFromDisk(folder).Warnings) Debug.LogWarning("[ugx] " + warning); } static bool Mentions(string[] paths, string target) { if (paths == null) return false; for (int i = 0; i < paths.Length; i++) if (string.Equals(paths[i], target, StringComparison.Ordinal)) return true; return false; } } }