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;
}
}
}