using System.IO; using System.Threading; using UnityEditor.AssetImporters; using UnityEditor.Experimental; using UnityEditor.LightBaking; using UnityEngine; using UnityEngine.LightTransport; using static UnityEditor.LightBaking.LightBaker; namespace UnityEditor.PathTracing.LightBakerBridge { [ScriptedImporter(version: 1, ext: "ext_unused_lightbaker")] internal class LightBakerWorkerProcessImporter : ScriptedImporter { private const string LightBakerWorkerProcess = "[LightBaker worker process] "; private const string BakeAssetFolder = "Temp/LightBakerAssetImport"; internal static bool BakeWithWorkerProcess(string bakeInputPath, string lightmapRequestsPath, string lightProbeRequestsPath, string bakeOutputFolderPath, int progressPort) { // Setup hidden import folder. if (!AssetDatabase.AssetPathExists(BakeAssetFolder)) Directory.CreateDirectory(BakeAssetFolder); var assetPath = $"{BakeAssetFolder}/{nameof(BakeImport)}.asset"; // Write the 'recipe' asset. var importAsset = ScriptableObject.CreateInstance(); importAsset.name = nameof(BakeImport); importAsset.BakeInputPath = bakeInputPath; importAsset.LightmapRequestsPath = lightmapRequestsPath; importAsset.LightProbeRequestsPath = lightProbeRequestsPath; importAsset.BakeOutputFolderPath = bakeOutputFolderPath; importAsset.ProgressPort = progressPort; // Import it. var guid = GUID.Generate(); UnityEditorInternal.InternalEditorUtility.SaveToSerializedFileAndForget(new Object[] { importAsset }, assetPath, true); File.WriteAllText(assetPath + ".meta", $"fileFormatVersion: 2\nguid: {guid}\nDefaultImporter:\n externalObjects: {{}}\n assetBundleName:\n assetBundleVariant:\n"); AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate); // Generate an artifact asynchronously, kicking off the bake. var headKey = new ArtifactKey(guid, typeof(LightBakerWorkerProcessImporter)); AssetDatabaseExperimental.ProduceArtifactAsync(headKey); return true; } public override void OnImportAsset(AssetImportContext ctx) { CancellationTokenSource bakeIsFinished = new(); ExternalProcessConnection progressConnection = new(); BakeProgressState progressState = new(); using (progressConnection) using (bakeIsFinished) using (progressState) { try { var bakeImport = AssetDatabase.LoadAssetAtPath(ctx.assetPath); if (bakeImport == null) { Debug.LogError($"{LightBakerWorkerProcess}Failed to load asset at path: {ctx.assetPath}."); return; } // If the Editor is waiting for us to connect, it will time out if we cannot connect. if (!progressConnection.Connect(bakeImport.ProgressPort)) { Debug.LogError($"{LightBakerWorkerProcess}Failed to connect to the parent process on port '{bakeImport.ProgressPort}'."); return; } // Bake while reporting progress. Thread progressReporterThread = new(() => ProgressReporterThreadFunction(progressConnection, bakeIsFinished.Token, progressState)); progressReporterThread.Start(); bool bakeOk = LightBakerStrangler.Bake(bakeImport.BakeInputPath, bakeImport.LightmapRequestsPath, bakeImport.LightProbeRequestsPath, bakeImport.BakeOutputFolderPath, progressState); // Stop the progress thread and wait for the thread. bakeIsFinished.Cancel(); progressReporterThread.Join(); ReportResultToParentProcess( bakeOk ? new Result { type = ResultType.Success, message = "Success." } : new Result { type = ResultType.JobFailed, message = "LightBakerStrangler.Bake failed." }, progressConnection); } finally { // Stop the progress thread. bakeIsFinished.Cancel(); } } } private static void ProgressReporterThreadFunction(ExternalProcessConnection connection, CancellationToken bakeIsFinished, BakeProgressState progressState) { if (connection == null) return; const int waitBetweenProgressReportsMs = 100; while (!progressState.WasCancelled() && !bakeIsFinished.IsCancellationRequested) { bool cancellationRequested = LightBaker.GetCancellationRequested(connection); if (cancellationRequested) progressState.Cancel(); float progress = progressState.Progress(); ReportProgressToParentProcess(progress, connection); Thread.Sleep(waitBetweenProgressReportsMs); } } } }