Skip to content

Commit 89b7e2b

Browse files
jared-marsauAdamSzApple
authored andcommitted
Batch mode support (#42)
* Microsoft's Unity linter asked me to fix this. * Reorganizing some calls and order of operations for two reasons: - Compatibility with Unity's batchmode - Avoid interactions with the Asset Database while it's in an undefined state - defer to `OnPostprocessAllAssets` * Unity project version bump. * I feel like this is pretty close to what it needs to be. I'll test it out tomorrow - but currently it works in just the local Apple.Core Unity project. I'll go through various builds in both Editor and Batch mode builds to check. * Addressing review feedback and cleaning up some comment formatting here and there. * No gelpers allowed. * Version bump. * Tested with the following and works: - Built all plug-ins - Created new project and imported Apple.Core and the GameKit plug-ins - Built in the Editor for iPad, generated Xcode project works as expected - Built on command line with batch mode, generated Xcode project works as expected. --------- Co-authored-by: Adam Szofran <[email protected]>
1 parent 875c39d commit 89b7e2b

File tree

5 files changed

+141
-61
lines changed

5 files changed

+141
-61
lines changed

plug-ins/Apple.Core/Apple.Core_Unity/Assets/Apple.Core/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
# CHANGELOG
22
All notable changes to this project will be documented in this file.
33

4+
## [3.1.6] - 2024-10-15
5+
### Updated
6+
- Added support for batch mode builds.
7+
- `ApplePlugInEnvironment` now inherits from Unity's `AssetPostprocessor` class
8+
- Moved asset operations out of `ApplePlugInEnvironment`'s static constructor and into the `OnPostprocessAllAssets` method inherited from `AssetPostprocessor`.
9+
- Cleaned up some comment typos and inconsistencies.
10+
411
## [3.1.5] - 2024-10-01
512
### Fixed
613
- Fixed a bug in the Apple.Core editor scripts that failed loading of plug-ins that were built for only a single platform.

plug-ins/Apple.Core/Apple.Core_Unity/Assets/Apple.Core/Editor/AppleBuildProfile.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public static AppleBuildProfile DefaultProfile()
4747
defaultProfile = (AppleBuildProfile)AssetDatabase.LoadMainAssetAtPath(DefaultBuildSettingsAssetPath);
4848
}
4949

50-
if (defaultProfile is null)
50+
if (defaultProfile == null)
5151
{
5252
Debug.Log("[AppleBuildProfile] Creating Apple Unity Plug-Ins build setting asset.");
5353
defaultProfile = CreateInstance<AppleBuildProfile>();

plug-ins/Apple.Core/Apple.Core_Unity/Assets/Apple.Core/Editor/ApplePlugInEnvironment.cs

Lines changed: 131 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using UnityEditor.PackageManager;
1111
using UnityEditor.PackageManager.Requests;
1212
using UnityEngine;
13+
using System.Threading;
1314

1415
namespace Apple.Core
1516
{
@@ -39,7 +40,7 @@ static ApplePlatformID()
3940
}
4041

4142
[InitializeOnLoad]
42-
public static class ApplePlugInEnvironment
43+
public class ApplePlugInEnvironment : AssetPostprocessor
4344
{
4445
/// <summary>
4546
/// Name of the folder that the Apple Unity Plug-Ins will use for storing permanent data assets and helper objects
@@ -96,6 +97,14 @@ public static class ApplePlugInEnvironment
9697
/// </summary>
9798
private static ListRequest _packageManagerListRequest;
9899

100+
/// <summary>
101+
/// Time, in seconds, that the package manager list request will wait before failing the list request.
102+
/// </summary>
103+
/// <remarks>
104+
/// The Apple Unity Plug-Ins infrastructure will fail if attempts to access the list of packages fails, so it may be necessary to investigate what would cause timeouts or even to extend the timeout duration.
105+
/// </remarks>
106+
private static int _packageManagerBatchModeListRequestTimeout => 5;
107+
99108
/// <summary>
100109
/// Collection of all known Apple Unity Plug-In packages
101110
/// </summary>
@@ -129,91 +138,145 @@ private enum UpdateState
129138
private static string _trackedApplePlatform;
130139

131140
/// <summary>
132-
/// Static constructor used by Unity for initialization of the ApplePlugInEnvironment.
141+
/// Static constructor used by Unity for early initialization of the <c>ApplePlugInEnvironment</c>.
133142
/// </summary>
134143
static ApplePlugInEnvironment()
135144
{
136-
// Ensure that the necessary Apple Unity Plug-In support folders exist and let user know if any have been created.
137-
string createFolderMessage = "[Apple Unity Plug-ins] Creating support folders:\n";
138-
bool foldersCreated = false;
145+
_appleUnityPackages = new Dictionary<string, AppleUnityPackage>();
146+
_packageManagerListRequest = Client.List();
147+
Events.registeringPackages += OnPackageManagerRegistrationUpdate;
139148

140-
if (!Directory.Exists(ApplePlugInSupportRootPath))
149+
_trackedAppleConfig = GetAppleBuildConfig();
150+
_trackedApplePlatform = GetApplePlatformID(EditorUserBuildSettings.activeBuildTarget);
151+
152+
_updateState = UpdateState.Initializing;
153+
154+
if (!Application.isBatchMode)
141155
{
142-
createFolderMessage += $" Created {ApplePlugInSupportRootPath}\n";
143-
foldersCreated = true;
144-
AssetDatabase.CreateFolder("Assets", ApplePlugInSupportFolderName);
156+
EditorApplication.update += OnEditorUpdate;
145157
}
158+
}
146159

147-
if (!Directory.Exists(ApplePlugInSupportEditorPath))
160+
/// <summary>
161+
/// Called when Package Manager List requests have succeded, this method updates internal data structures which track Apple Unity Plug-In packages and their associated native libraries.
162+
/// </summary>
163+
/// <param name="syncPlayModeLibraries">When true, macOS support libraries for each Apple Unity Plug-In will be copied to <c>ApplePlugInSupportPlayModeSupportPath</c></param>
164+
private static void OnPackageManagerListSuccess(bool syncPlayModeLibraries)
165+
{
166+
// Need to handle the special case of libraries being within this project, so postpone logging results.
167+
AddPackagesFromCollection(_packageManagerListRequest.Result, logPackagesAfterUpdate: false);
168+
169+
// If this is one of the development Apple plug-in Unity projects, it needs to be handled as a special case because
170+
// it isn't loaded/managed by the package manager; all of the assets are local under Assets/
171+
// All Apple plug-ins will have an AppleBuildStep implementation, so check for build steps that haven't been added already
172+
foreach (var buildStep in _defaultProfile.buildSteps.Values)
148173
{
149-
createFolderMessage += $" Created {ApplePlugInSupportEditorPath}\n";
150-
foldersCreated = true;
151-
AssetDatabase.CreateFolder(ApplePlugInSupportRootPath, "Editor");
174+
if (!_appleUnityPackages.ContainsKey(buildStep.DisplayName))
175+
{
176+
_appleUnityPackages[buildStep.DisplayName] = buildStep.IsNativePlugIn ? new AppleUnityPackage("Local Project", buildStep.DisplayName, Application.dataPath) : new AppleUnityPackage("Local Project", buildStep.DisplayName);
177+
}
152178
}
153179

154-
if (!Directory.Exists(ApplePlugInSupportPlayModeSupportPath))
180+
LogLibrarySummary();
181+
182+
if (syncPlayModeLibraries)
155183
{
156-
createFolderMessage += $" Created {ApplePlugInSupportPlayModeSupportPath}\n";
157-
foldersCreated = true;
158-
AssetDatabase.CreateFolder(ApplePlugInSupportEditorPath, "PlayModeSupport");
184+
SyncronizePlayModeSupportLibraries();
159185
}
186+
187+
ValidateLibraries();
188+
}
160189

161-
if (foldersCreated)
190+
/// <summary>
191+
/// Handles all initialization which requires asset operations.
192+
/// </summary>
193+
/// <param name="importedAssets">Array of paths to imported assets.</param>
194+
/// <param name="deletedAssets">Array of paths to deleted assets.</param>
195+
/// <param name="movedAssets">Array of paths to moved assets.</param>
196+
/// <param name="movedFromAssetPaths">Array of original paths for moved assets.</param>
197+
/// <param name="didDomainReload">Boolean set to true if there has been a domain reload.</param>
198+
static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths, bool didDomainReload)
199+
{
200+
if (!didDomainReload)
162201
{
163-
Debug.Log(createFolderMessage);
202+
return;
203+
}
204+
205+
// Apple Unity plug-ins settings are stored in a non-volatile asset, the default build profile, at ApplePlugInSupportEditorPath within the project
206+
if (!Directory.Exists(ApplePlugInSupportRootPath))
207+
{
208+
Debug.Log($"[Apple Unity Plug-ins] Creating support folder: {ApplePlugInSupportRootPath}");
209+
AssetDatabase.CreateFolder("Assets", ApplePlugInSupportFolderName);
210+
}
211+
212+
if (!Directory.Exists(ApplePlugInSupportEditorPath))
213+
{
214+
Debug.Log($"[Apple Unity Plug-ins] Creating support folder: {ApplePlugInSupportEditorPath}");
215+
AssetDatabase.CreateFolder(ApplePlugInSupportRootPath, "Editor");
164216
}
165217

166218
_defaultProfile = AppleBuildProfile.DefaultProfile();
167219
_defaultProfile.ResolveBuildSteps();
168220

169-
// Initialize collection of packages
170-
_appleUnityPackages = new Dictionary<string, AppleUnityPackage>();
171-
_packageManagerListRequest = Client.List();
221+
// When running in the Editor, initialization of the tracked Apple Unity plug-in packages is driven through interaction with the Package Manager scriting API,
222+
// which may take several update cycles to complete. Lacking a standard update loop, a while-loop will burn cycles until the list request has completed.
223+
if (Application.isBatchMode)
224+
{
225+
DateTime startTime = DateTime.Now;
172226

173-
// Initialize state tracking
174-
_updateState = UpdateState.Initializing;
175-
_trackedAppleConfig = GetAppleBuildConfig();
176-
_trackedApplePlatform = GetApplePlatformID(EditorUserBuildSettings.activeBuildTarget);
227+
// Unfortunately, Unity will throw an exception if this isn't called on the main thread. Stuck with blocking behavior for now.
228+
while (!_packageManagerListRequest.IsCompleted)
229+
{
230+
if ((DateTime.Now - startTime).Seconds > _packageManagerBatchModeListRequestTimeout)
231+
{
232+
string errorMessage = $"[Apple Unity Plug-Ins] Package manager list request exceeded {_packageManagerBatchModeListRequestTimeout} seconds.\n"
233+
+ " This may be the result of the size of the project, network latency, or issues with the package cache.\n"
234+
+ " Apple.Core build processing will not function correctly if this fails. Please investigate and retry.";
235+
236+
Debug.LogError(errorMessage);
237+
return;
238+
}
177239

178-
EditorApplication.update += OnEditorUpdate;
179-
Events.registeringPackages += OnPackageManagerRegistrationUpdate;
240+
// Prevent being blocked by tight loop.
241+
Thread.Sleep(100);
242+
}
243+
244+
if (_packageManagerListRequest.Status == StatusCode.Success)
245+
{
246+
// No need to sync play mode support libraries in batch mode. These are used just for Play Mode within the Editor.
247+
OnPackageManagerListSuccess(syncPlayModeLibraries: false);
248+
}
249+
else
250+
{
251+
Debug.LogError($"[Apple Unity Plug-Ins] Failed query to the package manager for list of packages with status: {_packageManagerListRequest.Status}");
252+
}
253+
}
254+
else
255+
{
256+
if (!Directory.Exists(ApplePlugInSupportPlayModeSupportPath))
257+
{
258+
Debug.Log($"[Apple Unity Plug-ins] Running in Editor, creating support folder: {ApplePlugInSupportPlayModeSupportPath}");
259+
AssetDatabase.CreateFolder(ApplePlugInSupportEditorPath, "PlayModeSupport");
260+
}
261+
}
180262
}
181263

182264
/// <summary>
183-
/// As soon as the Unity Editor creates the ApplePluginEnvironment, this event handler will be added to initialize the _appleUnityPackages dictionary.
265+
/// As soon as the Unity Editor creates the <c>ApplePluginEnvironment</c>, this event handler will be added to initialize the <c>_appleUnityPackages</c> dictionary.
184266
/// Once initialization has occured, this method will handle runtime validation for available libraries against selected Unity Editor settings.
185267
/// </summary>
268+
/// <remarks>
269+
/// Note that this method does not apply when Unity is run in batch mode.
270+
/// Please see the section under 'Batch mode arguments' at: https://docs.unity3d.com/Manual/EditorCommandLineArguments.html
271+
/// </remarks>
186272
private static void OnEditorUpdate()
187273
{
188274
switch (_updateState)
189275
{
190276
case UpdateState.Initializing:
191277
if (_packageManagerListRequest.IsCompleted && _packageManagerListRequest.Status == StatusCode.Success)
192278
{
193-
// Need to handle a the special case of libraries being within this project, so postpone logging results.
194-
AddPackagesFromCollection(_packageManagerListRequest.Result, false);
195-
196-
// If this is one of the development Apple plug-in Unity projects, it needs to be handled as a special case because
197-
// it isn't loaded/managed by the package manager; all of the assets are local under Assets/
198-
// All Apple plug-ins will have an AppleBuildStep implementation, so check for build steps that haven't been added already
199-
foreach (var buildStep in _defaultProfile.buildSteps.Values)
200-
{
201-
if (!_appleUnityPackages.ContainsKey(buildStep.DisplayName))
202-
{
203-
if (buildStep.IsNativePlugIn)
204-
{
205-
_appleUnityPackages[buildStep.DisplayName] = new AppleUnityPackage("Local Project", buildStep.DisplayName, Application.dataPath);
206-
}
207-
else
208-
{
209-
_appleUnityPackages[buildStep.DisplayName] = new AppleUnityPackage("Local Project", buildStep.DisplayName);
210-
}
211-
}
212-
}
213-
214-
LogLibrarySummary();
215-
SyncronizePlayModeSupportLibraries();
216-
ValidateLibraries();
279+
OnPackageManagerListSuccess(syncPlayModeLibraries: true);
217280

218281
_updateState = UpdateState.Updating;
219282
}
@@ -279,9 +342,9 @@ private static void ValidateLibraries()
279342
}
280343

281344
/// <summary>
282-
/// Gelper gets the current AppleConfigID based upon Editor development build settings.
345+
/// Helper gets the current <c>AppleConfigID</c> based upon Editor development build settings.
283346
/// </summary>
284-
/// <returns>A tuple which contains an AppleConfigID string representing the principal config as well as a fallback</returns>
347+
/// <returns>A tuple which contains an <c>AppleConfigID</c> string representing the principal config as well as a fallback</returns>
285348
public static (string Principal, string Fallback) GetAppleBuildConfig()
286349
{
287350
return IsDevelopmentBuild ? (AppleConfigID.Debug, AppleConfigID.Release) : (AppleConfigID.Release, AppleConfigID.Debug);
@@ -462,16 +525,26 @@ private static void SyncronizePlayModeSupportLibraries()
462525
AssetDatabase.DeleteAsset(path);
463526
}
464527

528+
string summary = $"[Apple Unity Plug-ins] Synchronizing plug-in libraries for Editor Play Mode support to <b>{ApplePlugInSupportPlayModeSupportPath}</b>\nLibraries copied:";
529+
bool librariesCopied = false;
530+
465531
// Copy current
466532
foreach (AppleUnityPackage applePackage in _appleUnityPackages.Values)
467533
{
468534
if (applePackage.PlayModeSupportLibrary.IsValid)
469535
{
536+
librariesCopied = true;
470537
AppleNativeLibrary pmsLibrary = applePackage.PlayModeSupportLibrary;
538+
summary += $"\n <b>{pmsLibrary.FileName}</b>";
471539
FileUtil.CopyFileOrDirectory(pmsLibrary.FullPath, $"{UnityProjectPath}/{ApplePlugInSupportPlayModeSupportPath}/{pmsLibrary.FileName}");
472540
}
473541
}
474542

543+
if (librariesCopied)
544+
{
545+
Debug.Log(summary);
546+
}
547+
475548
AssetDatabase.Refresh();
476549
}
477550

@@ -550,9 +623,9 @@ public static class AppleNativeLibraryUtility
550623
/// Gets the native library root folder for the generated Xcode project.
551624
/// </summary>
552625
/// <remarks>
553-
/// When building Xcode projects for macOS, Unity puts everything but the project under an additional folder "/{Application.productName}" - this script will respect this folder hierarchy.
554-
/// Output paths will of the following form:
555-
/// iOS/tvOS/visionOS: <c>[XCODE_PROJECT_DIR]/ApplePluginLibraries/[PLUGIN_NAME]/ApplePluginLibrary.suffix</c>
626+
/// When building Xcode projects for macOS, Unity puts everything but the project under an additional folder "/{Application.productName}" - this script will respect this folder hierarchy.<br/>
627+
/// Output paths will of the following form: <br/>
628+
/// iOS/tvOS/visionOS: <c>[XCODE_PROJECT_DIR]/ApplePluginLibraries/[PLUGIN_NAME]/ApplePluginLibrary.suffix</c><br/>
556629
/// macOS: <c>[XCODE_PROJECT_DIR]/[Application.productName]/ApplePluginLibraries/[PLUGIN_NAME]/ApplePluginLibrary.suffix</c>
557630
/// </remarks>
558631
/// <param name="unityBuildTarget">Current Unity BuildTarget</param>

plug-ins/Apple.Core/Apple.Core_Unity/Assets/Apple.Core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "com.apple.unityplugin.core",
33
"displayName": "Apple.Core",
44
"description": "Provides project settings, post-build automation tools, and other shared functionality for Apple Unity Plug-ins.",
5-
"version": "3.1.5",
5+
"version": "3.1.6",
66
"unity": "2022.3",
77
"keywords": [
88
"apple"

plug-ins/Apple.Core/Apple.Core_Unity/Packages/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"com.unity.2d.sprite": "1.0.0",
44
"com.unity.2d.tilemap": "1.0.0",
55
"com.unity.ai.navigation": "1.1.5",
6-
"com.unity.ide.rider": "3.0.31",
6+
"com.unity.ide.rider": "3.0.28",
77
"com.unity.ide.visualstudio": "2.0.22",
88
"com.unity.ide.vscode": "1.2.5",
99
"com.unity.test-framework": "1.4.5",

0 commit comments

Comments
 (0)