Files
2025-11-30 08:35:03 +02:00

231 lines
6.1 KiB
C#

using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Needle.Engine.Core;
using Needle.Engine.Gltf;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using File = UnityEngine.Windows.File;
using Object = UnityEngine.Object;
namespace Needle.Engine
{
// ReSharper disable once UnusedType.Global
internal static class ActionsBatch
{
// ReSharper disable once UnusedMember.Local
private static async void Execute()
{
if (!Application.isBatchMode)
{
Debug.LogError("This method can only be called in batch mode!");
return;
}
var success = true;
var isDebugMode = false;
EditorWindow consoleWindow = default;
try
{
var args = Environment.GetCommandLineArgs();
isDebugMode = args.Contains("-debug");
if (isDebugMode)
consoleWindow = TryOpenConsoleWindow();
Debug.Log($"Begin commandline process");
Task<bool> task = default;
if (ProcessCommandLineArgs(args, out var cl))
{
task = Execute(cl);
}
if (task != null)
success = await task;
if (!success)
{
Debug.LogError("Run failed, see logs for details");
}
else
{
Debug.Log("Run succeeded");
}
}
catch (Exception err)
{
Debug.LogException(err);
success = false;
}
finally
{
if (isDebugMode)
{
if (!success)
{
Debug.Log("Run failed - Close the console window to exit the Editor");
// ReSharper disable once LoopVariableIsNeverChangedInsideLoop
while (consoleWindow)
{
await Task.Delay(1000);
}
}
else
{
Debug.Log("Run finished - closing in 5 seconds");
await Task.Delay(5000);
}
}
Debug.LogFormat(LogType.Log, LogOption.NoStacktrace, null, "Build batch finished, closing Editor");
EditorApplication.Exit(success ? 0 : 1);
}
}
// Example command:
// path/to/Unity.exe -batchmode -projectPath "path/to/project" -executeMethod Needle.Engine.ActionsBatch.Execute -buildProduction -scene "Assets/path/to/scene.unity"
private const string availableCommandLineOptions = @"
> Needle Engine Commandline Options:
------------------------------
-scene <path/to/scene_or_asset> | open a specific scene or export a specific asset (e.g. path/to/myPrefab.prefab or path/to/myScene.unity)
-buildProduction | run a production build
-buildDevelopment | run a development build
-outputPath <path/to/output.glb> | set the output path for the build (only valid when building a scene)
-debug | open a console window for debugging
------------------------------
";
private class CommandLineArgs
{
public string ScenePath;
public bool BuildProduction;
public bool BuildDevelopment;
public string OutputPath;
public bool Debug;
}
private static bool ProcessCommandLineArgs(string[] args, out CommandLineArgs res)
{
Debug.LogFormat(LogType.Log, LogOption.NoStacktrace, null, $"Args: [{string.Join(", ", args)}]");
Debug.LogFormat(LogType.Log, LogOption.NoStacktrace, null, availableCommandLineOptions);
res = new CommandLineArgs();
for (var i = 0; i < args.Length; i++)
{
var arg = args[i];
switch (arg)
{
case "-scene":
{
var scenePath = args[i + 1];
res.ScenePath = scenePath;
break;
}
case "-outputPath":
{
var path = args[i + 1];
res.OutputPath = path;
break;
}
case "-buildProduction":
res.BuildProduction = true;
break;
case "-buildDevelopment":
res.BuildDevelopment = true;
break;
case "-debug":
res.Debug = true;
break;
}
}
return true;
}
private static async Task<bool> Execute(CommandLineArgs args)
{
if (!string.IsNullOrEmpty(args.ScenePath))
{
var scenePath = args.ScenePath;
if (!File.Exists(scenePath)) Debug.LogError("File does not exist: " + scenePath);
if (scenePath.EndsWith(".unity"))
{
var scene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Single);
if (!scene.IsValid())
{
Debug.LogError("Scene is not valid: " + scenePath);
return false;
}
if (args.BuildProduction)
return await Actions.ExportAndBuildProduction();
if (args.BuildDevelopment)
return await Actions.ExportAndBuildDevelopment();
}
else
{
if (args.Debug)
{
// For debugging since export glb is blocking
await Task.Delay(1000);
}
var production = args.BuildProduction;
var outputPath = args.OutputPath;
if (string.IsNullOrWhiteSpace(outputPath))
{
Debug.LogError("Missing output path for exporting asset as glb. Please provide a path/to/result.glb");
return false;
}
var asset = AssetDatabase.LoadAssetAtPath<Object>(scenePath);
var buildContext = BuildContext.Distribution(production);
var dir = Path.GetDirectoryName(outputPath);
var objectExportContext = new ObjectExportContext(buildContext, asset, dir, outputPath);
Debug.Log("Running export " + scenePath);
var res = Export.AsGlb(objectExportContext, asset, out _);
if(!res) Debug.LogError("Exporting " + scenePath + " failed");
else
{
Debug.Log($"Running compression ({(production ? "production" : "development")})");
var outputDirectory = Path.GetDirectoryName(outputPath);
res = await ActionsCompression.MakeProgressive(outputDirectory);
res &= await ActionsCompression.CompressFiles(outputDirectory);
}
return res;
}
}
Debug.LogError("Missing scene path. Please provide a path/to/scene.unity or path/to/asset");
return false;
}
private static EditorWindow TryOpenConsoleWindow()
{
try
{
var consoleWindowType = typeof(EditorWindow).Assembly.GetType("UnityEditor.ConsoleWindow");
var consoleWindow = ScriptableObject.CreateInstance(consoleWindowType) as EditorWindow;
if (consoleWindow)
{
consoleWindow.Show(true);
return consoleWindow;
}
Debug.LogError("Could not open console window");
}
catch (Exception e)
{
Debug.LogException(e);
}
return null;
}
}
}