A game about forced loneliness, made by TACStudios
at master 325 lines 13 kB view raw
1using System; 2using System.Collections.Generic; 3using UnityEditor; 4using UnityEditor.PackageManager; 5using UnityEditor.PackageManager.Requests; 6using UnityEngine; 7 8namespace Unity.Multiplayer.Center 9{ 10 internal static class PackageManagement 11 { 12 static PackageInstaller s_Installer; 13 14 /// <summary> 15 /// Opens the package manager window with selected package name and hides error 16 /// </summary> 17 public static void OpenPackageManager(string packageName) 18 { 19 try 20 { 21 UnityEditor.PackageManager.UI.Window.Open(packageName); 22 } 23 catch (Exception) 24 { 25 // Hide the error in the PackageManager API until the team fixes it 26 // Debug.Log("Error opening Package Manager: " + e.Message); 27 } 28 } 29 30 /// <summary> 31 /// Checks if the package is a direct dependency of the project 32 /// </summary> 33 /// <param name="packageId">The package name/id e.g. com.unity.netcode</param> 34 /// <returns>True if the package is a direct dependency</returns> 35 public static bool IsDirectDependency(string packageId) 36 { 37 var package = GetInstalledPackage(packageId); 38 return package != null && package.isDirectDependency; 39 } 40 41 /// <summary> 42 /// Checks if a package is installed. 43 /// </summary> 44 /// <param name="packageId">The package name, e.g. com.unity.netcode</param> 45 /// <returns>True if the package is installed, false otherwise</returns> 46 public static bool IsInstalled(string packageId) => GetInstalledPackage(packageId) != null; 47 48 /// <summary> 49 /// Checks if a package is embedded, linked locally, installed via Git or local Tarball. 50 /// </summary> 51 /// <param name="packageId">The package name, e.g. com.unity.netcode</param> 52 /// <returns>True if the package is linked locally, false otherwise</returns> 53 public static bool IsLinkedLocallyOrEmbeddedOrViaGit(string packageId) => 54 GetInstalledPackage(packageId) is { source: PackageSource.Embedded or PackageSource.Local or PackageSource.Git or PackageSource.LocalTarball }; 55 56 /// <summary> 57 /// Finds the installed package with the given packageId or returns null. 58 /// </summary> 59 /// <param name="packageId">The package name/id e.g. com.unity.netcode</param> 60 /// <returns>The package info</returns> 61 public static UnityEditor.PackageManager.PackageInfo GetInstalledPackage(string packageId) 62 { 63 return UnityEditor.PackageManager.PackageInfo.FindForPackageName(packageId); 64 } 65 66 /// <summary> 67 /// Filters out the packages that are already embedded, linked locally, installed via Git or local Tarball and returns this new list. 68 /// </summary> 69 /// <param name="installCandidates">A list of package IDs that are candidates for installation.</param> 70 /// <returns>A new filtered list of packages.</returns> 71 public static IEnumerable<string> RemoveLocallyLinkedOrEmbeddedOrViaGitPackagesFromList(IEnumerable<string> installCandidates) 72 { 73 var filteredList = new List<string>(); 74 75 foreach (var packageId in installCandidates) 76 { 77 if (!IsLinkedLocallyOrEmbeddedOrViaGit(packageId)) 78 { 79 filteredList.Add(packageId); 80 } 81 else 82 { 83 Debug.Log($"Removing {packageId} from install candidates.\n" + 84 "This package is already embedded, linked locally, installed via Git, or from a local tarball. " + 85 "Please check the Package Manager for more information or to upgrade manually."); 86 } 87 } 88 89 return filteredList; 90 } 91 92 /// <summary> 93 /// Returns true if any of the given packageIds is installed. 94 /// </summary> 95 /// <param name="packageIds">List of package is e.g com.unity.netcode</param> 96 /// <returns>True if any package is installed, false otherwise</returns> 97 public static bool IsAnyPackageInstalled(params string[] packageIds) 98 { 99 var installedPackages = UnityEditor.PackageManager.PackageInfo.GetAllRegisteredPackages(); 100 var hashset = new HashSet<string>(); 101 102 foreach (var package in installedPackages) 103 { 104 hashset.Add(package.name); 105 } 106 107 foreach (var packageId in packageIds) 108 { 109 if (hashset.Contains(packageId)) 110 { 111 return true; 112 } 113 } 114 115 return false; 116 } 117 118 /// <summary> 119 /// Installs a single package and invokes the callback when the package is installed/when the install failed. 120 /// </summary> 121 /// <param name="packageId">The package name/id e.g. com.unity.netcode</param> 122 /// <param name="onInstalled">The callback</param> 123 public static void InstallPackage(string packageId, Action<bool> onInstalled = null) 124 { 125 s_Installer = new PackageInstaller(packageId); 126 s_Installer.OnInstalled += onInstalled; 127 s_Installer.OnInstalled += _ => s_Installer = null; 128 } 129 130 /// <summary> 131 /// Register to an existing installation callback. This has no effect if no installation is ongoing (check 132 /// <see cref="IsInstallationFinished"/> to see if that is the case). 133 /// </summary> 134 /// <param name="onInstalled">The callback</param> 135 public static void RegisterToExistingInstaller(Action<bool> onInstalled) 136 { 137 if (s_Installer != null) 138 { 139 s_Installer.OnInstalled += onInstalled; 140 } 141 } 142 143 /// <summary> 144 /// Installs several packages and invokes the callback when all packages are installed/when the installation failed. 145 /// </summary> 146 /// <param name="packageIds">The package names/ids e.g. com.unity.netcode</param> 147 /// <param name="onAllInstalled">The callback</param> 148 /// <param name="packageIdsToRemove">Optional package name/ids to remove</param> 149 public static void InstallPackages(IEnumerable<string> packageIds, Action<bool> onAllInstalled = null, IEnumerable<string> packageIdsToRemove = null) 150 { 151 s_Installer = new PackageInstaller(RemoveLocallyLinkedOrEmbeddedOrViaGitPackagesFromList(packageIds), packageIdsToRemove); 152 s_Installer.OnInstalled += onAllInstalled; 153 s_Installer.OnInstalled += _ => s_Installer = null; 154 } 155 156 /// <summary> 157 /// Create a dictionary with package names as keys and versions as values 158 /// </summary> 159 /// <returns>The mapping (package id, installed version) </returns> 160 internal static Dictionary<string, string> InstalledPackageDictionary() 161 { 162 var installedPackages = UnityEditor.PackageManager.PackageInfo.GetAllRegisteredPackages(); 163 var installedPackageDictionary = new Dictionary<string, string>(); 164 165 foreach (var package in installedPackages) 166 { 167 var splitPackageId = package.packageId.Split('@'); 168 if (splitPackageId.Length == 2) 169 { 170 installedPackageDictionary[splitPackageId[0]] = splitPackageId[1]; 171 } 172 } 173 174 return installedPackageDictionary; 175 } 176 177 internal class VersionChecker 178 { 179 SearchRequest m_Request; 180 public VersionChecker(string packageID) 181 { 182 m_Request = Client.Search(packageID, false); 183 EditorApplication.update += Progress; 184 } 185 186 public event Action<UnityEditor.PackageManager.PackageInfo> OnVersionFound; 187 188 void Progress() 189 { 190 if (!m_Request.IsCompleted) return; 191 192 EditorApplication.update -= Progress; 193 var foundPackage = m_Request.Result; 194 foreach (var packageInfo in foundPackage) 195 { 196 OnVersionFound?.Invoke(packageInfo); 197 } 198 } 199 } 200 201 class PackageInstaller 202 { 203 Request m_Request; 204 string[] m_PackagesToAddIds; 205 public event Action<bool> OnInstalled; 206 207 public PackageInstaller(string packageId) 208 { 209 // Add a package to the project 210 m_Request = Client.Add(packageId); 211 m_PackagesToAddIds = new[] {packageId}; 212 EditorApplication.update += Progress; 213 } 214 215 public PackageInstaller(IEnumerable<string> packageIds, IEnumerable<string> packageIdsToRemove = null) 216 { 217 var packageIdsList = new List<string>(); 218 foreach (var id in packageIds) 219 { 220 packageIdsList.Add(id); 221 } 222 223 var packageIdsArray = packageIdsList.ToArray(); 224 225 string[] packageIdsToRemoveArray = null; 226 if (packageIdsToRemove != null) 227 { 228 var packageIdsToRemoveList = new List<string>(); 229 foreach (var id in packageIdsToRemove) 230 { 231 packageIdsToRemoveList.Add(id); 232 } 233 packageIdsToRemoveArray = packageIdsToRemoveList.ToArray(); 234 } 235 236 // Add a package to the project 237 m_Request = Client.AddAndRemove(packageIdsArray, packageIdsToRemoveArray); 238 m_PackagesToAddIds = packageIdsArray; 239 EditorApplication.update += Progress; 240 } 241 242 public bool IsCompleted() 243 { 244 return m_Request == null || m_Request.IsCompleted; 245 } 246 247 void Progress() 248 { 249 if (!m_Request.IsCompleted) return; 250 251 EditorApplication.update -= Progress; 252 if (m_Request.Status == StatusCode.Success) 253 { 254 Debug.Log("Installed: " + GetInstalledPackageId()); 255 } 256 else if (m_Request.Status >= StatusCode.Failure) 257 { 258 // if the request has more than one package, it will only prompt error message for one 259 // We should prompt all the failed packages 260 Debug.Log("Package installation request with selected packages: " + String.Join(", ", m_PackagesToAddIds) + 261 " failed. \n Reason: "+ m_Request.Error.message); 262 } 263 264 OnInstalled?.Invoke(m_Request.Status == StatusCode.Success); 265 } 266 267 string GetInstalledPackageId() 268 { 269 switch (m_Request) 270 { 271 case AddRequest addRequest: 272 return addRequest.Result.packageId; 273 case AddAndRemoveRequest addAndRemoveRequest: 274 var packageIds = new List<string>(); 275 foreach (var packageInfo in addAndRemoveRequest.Result) 276 { 277 packageIds.Add(packageInfo.packageId); 278 } 279 return string.Join(", ", packageIds); 280 default: 281 throw new InvalidOperationException("Unknown request type"); 282 } 283 } 284 } 285 286 /// <summary> 287 /// Detects if any multiplayer package is installed by checking for services and Netcode installed packages. 288 /// </summary> 289 /// <returns>True if any package was detected, False otherwise</returns> 290 public static bool IsAnyMultiplayerPackageInstalled() 291 { 292 var packagesToCheck = new [] 293 { 294 "com.unity.netcode", 295 "com.unity.netcode.gameobjects", 296 "com.unity.services.multiplayer", 297 "com.unity.transport", 298 "com.unity.dedicated-server", 299 "com.unity.services.cloudcode", 300 "com.unity.multiplayer.playmode", 301 "com.unity.services.vivox" 302 // Note about "com.unity.services.core": it used to be installed only with multiplayer packages, but it is also a dependency of the analytics, which is now always installed. 303 }; 304 305 foreach (var package in packagesToCheck) 306 { 307 if (IsInstalled(package)) 308 { 309 return true; 310 } 311 } 312 313 return false; 314 } 315 316 /// <summary> 317 /// Checks if the installation process has finished. 318 /// </summary> 319 /// <returns>True if there is no current installer instance or installation is finished on the installer</returns> 320 public static bool IsInstallationFinished() 321 { 322 return s_Installer == null || s_Installer.IsCompleted(); 323 } 324 } 325}