| 
10 | 10 | using UnityEditor.PackageManager;  | 
11 | 11 | using UnityEditor.PackageManager.Requests;  | 
12 | 12 | using UnityEngine;  | 
 | 13 | +using System.Threading;  | 
13 | 14 | 
 
  | 
14 | 15 | namespace Apple.Core  | 
15 | 16 | {  | 
@@ -39,7 +40,7 @@ static ApplePlatformID()  | 
39 | 40 |     }  | 
40 | 41 | 
 
  | 
41 | 42 |     [InitializeOnLoad]  | 
42 |  | -    public static class ApplePlugInEnvironment  | 
 | 43 | +    public class ApplePlugInEnvironment : AssetPostprocessor  | 
43 | 44 |     {  | 
44 | 45 |         /// <summary>  | 
45 | 46 |         /// 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  | 
96 | 97 |         /// </summary>  | 
97 | 98 |         private static ListRequest _packageManagerListRequest;  | 
98 | 99 | 
 
  | 
 | 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 | + | 
99 | 108 |         /// <summary>  | 
100 | 109 |         /// Collection of all known Apple Unity Plug-In packages  | 
101 | 110 |         /// </summary>  | 
@@ -129,91 +138,145 @@ private enum UpdateState  | 
129 | 138 |         private static string _trackedApplePlatform;  | 
130 | 139 | 
 
  | 
131 | 140 |         /// <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>.  | 
133 | 142 |         /// </summary>  | 
134 | 143 |         static ApplePlugInEnvironment()  | 
135 | 144 |         {  | 
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;  | 
139 | 148 | 
 
  | 
140 |  | -            if (!Directory.Exists(ApplePlugInSupportRootPath))  | 
 | 149 | +            _trackedAppleConfig = GetAppleBuildConfig();  | 
 | 150 | +            _trackedApplePlatform = GetApplePlatformID(EditorUserBuildSettings.activeBuildTarget);  | 
 | 151 | + | 
 | 152 | +            _updateState = UpdateState.Initializing;  | 
 | 153 | + | 
 | 154 | +            if (!Application.isBatchMode)  | 
141 | 155 |             {  | 
142 |  | -                createFolderMessage += $"  Created {ApplePlugInSupportRootPath}\n";  | 
143 |  | -                foldersCreated = true;  | 
144 |  | -                AssetDatabase.CreateFolder("Assets", ApplePlugInSupportFolderName);  | 
 | 156 | +                EditorApplication.update += OnEditorUpdate;  | 
145 | 157 |             }  | 
 | 158 | +        }  | 
146 | 159 | 
 
  | 
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)  | 
148 | 173 |             {  | 
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 | +                }  | 
152 | 178 |             }  | 
153 | 179 | 
 
  | 
154 |  | -            if (!Directory.Exists(ApplePlugInSupportPlayModeSupportPath))  | 
 | 180 | +            LogLibrarySummary();  | 
 | 181 | + | 
 | 182 | +            if (syncPlayModeLibraries)  | 
155 | 183 |             {  | 
156 |  | -                createFolderMessage += $"  Created {ApplePlugInSupportPlayModeSupportPath}\n";  | 
157 |  | -                foldersCreated = true;  | 
158 |  | -                AssetDatabase.CreateFolder(ApplePlugInSupportEditorPath, "PlayModeSupport");  | 
 | 184 | +                SyncronizePlayModeSupportLibraries();  | 
159 | 185 |             }  | 
 | 186 | +              | 
 | 187 | +            ValidateLibraries();  | 
 | 188 | +        }  | 
160 | 189 | 
 
  | 
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)  | 
162 | 201 |             {  | 
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");  | 
164 | 216 |             }  | 
165 | 217 | 
 
  | 
166 | 218 |             _defaultProfile = AppleBuildProfile.DefaultProfile();  | 
167 | 219 |             _defaultProfile.ResolveBuildSteps();  | 
168 | 220 | 
 
  | 
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;  | 
172 | 226 | 
 
  | 
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 | +                    }  | 
177 | 239 | 
 
  | 
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 | +            }  | 
180 | 262 |         }  | 
181 | 263 | 
 
  | 
182 | 264 |         /// <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.  | 
184 | 266 |         /// Once initialization has occured, this method will handle runtime validation for available libraries against selected Unity Editor settings.  | 
185 | 267 |         /// </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>  | 
186 | 272 |         private static void OnEditorUpdate()  | 
187 | 273 |         {  | 
188 | 274 |             switch (_updateState)  | 
189 | 275 |             {  | 
190 | 276 |                 case UpdateState.Initializing:  | 
191 | 277 |                     if (_packageManagerListRequest.IsCompleted && _packageManagerListRequest.Status == StatusCode.Success)  | 
192 | 278 |                     {  | 
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);  | 
217 | 280 | 
 
  | 
218 | 281 |                         _updateState = UpdateState.Updating;  | 
219 | 282 |                     }  | 
@@ -279,9 +342,9 @@ private static void ValidateLibraries()  | 
279 | 342 |         }  | 
280 | 343 | 
 
  | 
281 | 344 |         /// <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.  | 
283 | 346 |         /// </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>  | 
285 | 348 |         public static (string Principal, string Fallback) GetAppleBuildConfig()  | 
286 | 349 |         {  | 
287 | 350 |             return IsDevelopmentBuild ? (AppleConfigID.Debug, AppleConfigID.Release) : (AppleConfigID.Release, AppleConfigID.Debug);  | 
@@ -462,16 +525,26 @@ private static void SyncronizePlayModeSupportLibraries()  | 
462 | 525 |                 AssetDatabase.DeleteAsset(path);  | 
463 | 526 |             }  | 
464 | 527 | 
 
  | 
 | 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 | + | 
465 | 531 |             // Copy current  | 
466 | 532 |             foreach (AppleUnityPackage applePackage in _appleUnityPackages.Values)  | 
467 | 533 |             {  | 
468 | 534 |                 if (applePackage.PlayModeSupportLibrary.IsValid)  | 
469 | 535 |                 {  | 
 | 536 | +                    librariesCopied = true;  | 
470 | 537 |                     AppleNativeLibrary pmsLibrary = applePackage.PlayModeSupportLibrary;  | 
 | 538 | +                    summary += $"\n  <b>{pmsLibrary.FileName}</b>";  | 
471 | 539 |                     FileUtil.CopyFileOrDirectory(pmsLibrary.FullPath, $"{UnityProjectPath}/{ApplePlugInSupportPlayModeSupportPath}/{pmsLibrary.FileName}");  | 
472 | 540 |                 }  | 
473 | 541 |             }  | 
474 | 542 | 
 
  | 
 | 543 | +            if (librariesCopied)  | 
 | 544 | +            {  | 
 | 545 | +                Debug.Log(summary);  | 
 | 546 | +            }  | 
 | 547 | + | 
475 | 548 |             AssetDatabase.Refresh();  | 
476 | 549 |         }  | 
477 | 550 | 
 
  | 
@@ -550,9 +623,9 @@ public static class AppleNativeLibraryUtility  | 
550 | 623 |         /// Gets the native library root folder for the generated Xcode project.  | 
551 | 624 |         /// </summary>  | 
552 | 625 |         /// <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/>  | 
556 | 629 |         ///               macOS: <c>[XCODE_PROJECT_DIR]/[Application.productName]/ApplePluginLibraries/[PLUGIN_NAME]/ApplePluginLibrary.suffix</c>  | 
557 | 630 |         /// </remarks>  | 
558 | 631 |         /// <param name="unityBuildTarget">Current Unity BuildTarget</param>  | 
 | 
0 commit comments