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<BakeImport>();
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<BakeImport>(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);
}
}
}
}