static void FinalizePublish(Stack <RequestHandle> requests) { var subscriber = CreateSubscriber(); var resourceFolder = CompanionResourceUtils.GetCurrentResourceFolder(); var project = new CompanionProject(Application.cloudProjectId, Application.productName, true); requests.Push(subscriber.GetCloudResourceList(project, resourceFolder, (success, resourceList) => { foreach (var update in k_ResourceListUpdates) { var key = update.ResourceKey; resourceList.AddOrUpdateResource(key, update.Name, update.Type, update.FileSize, update.HasBundle); var thumbnail = update.Thumbnail; if (thumbnail != null) { subscriber.SaveThumbnailImage(project, resourceFolder, key, thumbnail, requests); } } requests.Push(subscriber.WriteCloudResourceList(project, resourceFolder, resourceList, writeSuccess => { if (success) { CompanionResourceUtils.OnCloudResourceListChanged(resourceList); } else { Debug.LogError("Failed to write resource list"); } })); })); }
public static CompanionProject AddNewProject(string name = null) { if (string.IsNullOrEmpty(name)) { name = k_UntitledProjectName; } var projectList = LoadProjects(); var projects = projectList.projects; var unlinkedCount = 0; foreach (var project in projects) { if (!project.linked) { unlinkedCount++; } } var id = string.Format(k_UnlinkedProjectIDFormat, unlinkedCount); var projectData = new CompanionProject(id, name, false); projectList.projects.Add(projectData); SaveProjects(projectList); return(projectData); }
public static void AddCloudResourceToLocalResourceList(CompanionProject project, string resourceFolder, CompanionResource resource) { var localResourceList = GetLocalResourceList(project, resourceFolder) ?? new ResourceList(); localResourceList.AddOrOverwriteExistingResource(resource); WriteLocalResourceList(project, resourceFolder, localResourceList); }
public static void DeleteProject(CompanionProject projectToDelete) { try { var projectFolder = projectToDelete.GetLocalPath(); if (Directory.Exists(projectFolder)) { Directory.Delete(projectToDelete.GetLocalPath(), true); } } catch (Exception e) { // TODO: Error feedback Debug.LogException(e); return; } var projectList = LoadProjects(); // TODO: Error if project not found var index = projectToDelete.index; foreach (var project in projectList.projects.ToList()) { if (project.index == index) { projectList.projects.Remove(project); break; } } SaveProjects(projectList); }
static bool MoveProjectFolder(string oldId, string newId, out string errorTitle, out string errorBody) { var localPath = CompanionProject.GetProjectPath(oldId); // If local project doesn't exist, do not prevent the link--this shouldn't ever happen, though if (!Directory.Exists(localPath)) { errorTitle = null; errorBody = null; return(true); } var linkedPath = CompanionProject.GetProjectPath(newId); if (Directory.Exists(linkedPath)) { errorTitle = "Cannot move project folder"; errorBody = "Sorry, it appears that another project folder already exists at the destination. This can happen if an error occurred when trying to link or delete a project with the same ID. Please back up and clear local data to resolve this issue."; return(false); } Directory.Move(localPath, linkedPath); errorTitle = null; errorBody = null; return(true); }
internal static RequestHandle GetScene(this IUsesCloudStorage storageUser, CompanionProject project, CompanionResource resource, Action <bool, string> callback) { if (callback == null) { Debug.LogWarning("Callback is null in GetScene"); return(default);
internal static RequestHandle SavePrefab(this IUsesCloudStorage storageUser, CompanionProject project, string resourceFolder, GameObject prefab, Action <bool, string> callback = null) { // Write to local storage in case cloud isn't reachable var prefabName = prefab.name; var path = AssetDatabase.GetAssetPath(prefab); if (string.IsNullOrEmpty(path)) { Debug.LogError("Cannot get path for " + prefab); return(default);
internal static void AddOrUpdateResource(this IUsesCloudStorage storageUser, CompanionProject project, string resourceFolder, string key, string name, ResourceType type, long fileSize, bool hasBundle, Stack <RequestHandle> requests, Action <bool, ResourceList> callback = null) { var localResourceList = GetLocalResourceList(project, resourceFolder) ?? new ResourceList(); var resource = localResourceList.AddOrUpdateResource(key, name, type, fileSize, hasBundle); WriteLocalResourceList(project, resourceFolder, localResourceList); void LocalOnlyUpdate(bool success, ResourceList resourceList) { resourceList = ResourceList.MergeResourceLists(localResourceList, resourceList); ResourceListChanged?.Invoke(false, resourceList); callback?.Invoke(success, resourceList); } if (!project.linked) { LocalOnlyUpdate(true, new ResourceList()); return; } // Get the latest list to make sure we don't lose scenes in case of stale data requests.Push(storageUser.GetCloudResourceList(project, resourceFolder, (getListSuccess, cloudResourceList) => { if (getListSuccess) { cloudResourceList.AddOrOverwriteExistingResource(resource); requests.Push(storageUser.WriteCloudResourceList(project, resourceFolder, cloudResourceList, writeListSuccess => { if (writeListSuccess) { #if UNITY_EDITOR CloudResourceListChanged?.Invoke(cloudResourceList); #endif WriteFallbackCloudResourceList(project, resourceFolder, cloudResourceList); cloudResourceList = ResourceList.MergeResourceLists(localResourceList, cloudResourceList); ResourceListChanged?.Invoke(true, cloudResourceList); callback?.Invoke(true, cloudResourceList); } else { LocalOnlyUpdate(false, cloudResourceList); } })); } else { LocalOnlyUpdate(false, cloudResourceList); } })); }
static RequestHandle SaveMarker(this IUsesCloudStorage storageUser, CompanionProject project, string resourceFolder, string guid, Marker marker, Action <bool, string> callback) { var jsonText = SceneSerialization.ToJson(marker); WriteLocalMarker(project, resourceFolder, guid, jsonText); var key = GetMarkerKey(resourceFolder, guid); if (!project.linked) { callback?.Invoke(true, key); return(default);
static RequestHandle SaveEnvironment(this IUsesCloudStorage storageUser, CompanionProject project, string resourceFolder, string guid, Environment environment, Action <bool, string, long> callback) { var jsonText = SceneSerialization.ToJson(environment); // Write to local storage in case cloud isn't reachable WriteLocalEnvironment(project, resourceFolder, guid, jsonText); var key = GetEnvironmentKey(resourceFolder, guid); if (!project.linked) { callback?.Invoke(true, key, jsonText.Length); return(default);
static RequestHandle SaveDataRecording(this IUsesCloudStorage storageUser, CompanionProject project, string resourceFolder, string guid, CompanionDataRecording recording, Action <bool, string, long> callback, ProgressCallback progress) { var jsonText = SceneSerialization.ToJson(recording); WriteLocalDataRecording(project, resourceFolder, guid, jsonText); var key = GetDataRecordingKey(resourceFolder, guid); if (!project.linked) { callback?.Invoke(true, key, jsonText.Length); return(default);
public static void GetPrefab(IUsesCloudStorage storageUser, CompanionProject project, string key, Action <GameObject> callback) { if (k_CachedPrefabs.TryGetValue(key, out var prefab)) { callback?.Invoke(prefab); } else { storageUser.GetPrefabAsset(project, key, (success, downloadedPrefab) => { SetPrefab(key, downloadedPrefab); callback?.Invoke(downloadedPrefab); }); } }
public static void UnlinkProject(string cloudId) { var projectList = LoadProjects(); var projects = projectList.projects; CompanionProject cloudProject = null; foreach (var project in projects) { if (project.index == cloudId) { cloudProject = project; } } if (cloudProject == null) { Debug.LogError($"Cloud project with ID {cloudId} now found"); return; } var newProjectId = GetNewProjectId(projects); try { if (!MoveProjectFolder(cloudId, newProjectId, out var errorTitle, out var errorBody)) { Debug.Log($"{errorTitle}\n{errorBody}"); return; } } catch (Exception e) { Debug.LogException(e); Debug.LogError("Failed to move project folder"); return; } cloudProject.Unlink(newProjectId); SaveProjects(projectList); }
public static void AddOrUpdateProject(CompanionProject currentProject) { var projectList = LoadProjects(); var found = false; foreach (var project in projectList.projects.ToList()) { if (project.index == currentProject.index) { found = true; project.linked = currentProject.linked; project.name = currentProject.name; } } if (!found) { projectList.AddProject(currentProject.index, currentProject.name, currentProject.linked); } SaveProjects(projectList); }
internal static RequestHandle GetCloudResourceList(this IUsesCloudStorage storageUser, CompanionProject project, string resourceFolder, Action <bool, ResourceList> callback) { if (callback == null) { Debug.LogWarning("Callback is null in GetCloudResourceList"); return(default);
internal static void ImportResource(this IUsesCloudStorage storageUser, CompanionProject project, CompanionResource resource, string platform, ResourceList resourceList, GameObject simulatedPlanePrefab, Material simulatedPlaneMaterial, Post postPrefab, MeshFilter meshPrefab, GameObject simulatedLightingPrefab, Stack <RequestHandle> requests, Action <UnityObject> callback) { string path; string cloudGuid; var resourceKey = resource.key; switch (resource.type) { case ResourceType.Scene: if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) { return; } void ImportSceneCallback(bool bundleSuccess, AssetPack assetPack) { storageUser.GetScene(project, resource, (success, jsonText) => { if (success) { CompanionSceneUtils.SplitSceneKey(resourceKey, out _, out cloudGuid); var sceneGuid = CompanionResourceSync.instance.GetAssetGuid(cloudGuid); var scenePath = AssetDatabase.GUIDToAssetPath(sceneGuid); // GUIDToAssetPath will return a path if the asset was recently deleted if (AssetDatabase.LoadAssetAtPath <SceneAsset>(scenePath) == null) { scenePath = null; } if (!string.IsNullOrEmpty(sceneGuid) && string.IsNullOrEmpty(scenePath)) { Debug.LogWarningFormat("Could not find scene with guid {0}. Falling back to new scene", sceneGuid); } var sceneName = CompanionSceneUtils.GetSceneName(resource.name); ImportScene(sceneName, jsonText, assetPack, scenePath); // Store cloud ID in temp scene object if original scene not found if (string.IsNullOrEmpty(scenePath)) { var cloudIdObject = new GameObject("__Temp"); cloudIdObject.hideFlags = HideFlags.HideInHierarchy | HideFlags.DontSaveInBuild; var cloudIdSaver = cloudIdObject.AddComponent <CloudGuidSaver>(); cloudIdSaver.CloudGuid = cloudGuid; } } }); } if (resourceList.TryGetAssetBundleKey(resource.key, platform, out var assetBundleKey)) { requests.Push(storageUser.GetAssetPack(project, assetBundleKey, ImportSceneCallback)); } else { ImportSceneCallback(true, null); } return; case ResourceType.Environment: requests.Push(storageUser.CloudLoadAsync(resourceKey, (success, responseCode, response) => { if (success) { var environmentName = resource.name; path = EditorUtility.SaveFilePanelInProject(k_SaveEnvironmentDialogTitle, environmentName, k_PrefabExtension, string.Empty); if (string.IsNullOrEmpty(path)) { return; } ImportEnvironment(environmentName, response, path, simulatedPlanePrefab, simulatedPlaneMaterial, postPrefab, meshPrefab, simulatedLightingPrefab, callback); var resourceSync = CompanionResourceSync.instance; CompanionEnvironmentUtils.SplitEnvironmentKey(resourceKey, out _, out cloudGuid); resourceSync.SetAssetGuid(AssetDatabase.AssetPathToGUID(path), cloudGuid); } })); return; case ResourceType.Recording: path = EditorUtility.SaveFilePanelInProject(k_SaveRecordingDialogTitle, resource.name, k_RecordingExtension, string.Empty); if (string.IsNullOrEmpty(path)) { return; } requests.Push(storageUser.CloudLoadAsync(resourceKey, (success, responseCode, response) => { if (success) { CoroutineUtils.StartCoroutine(storageUser.ImportDataRecording(path, resourceKey, resource.name, response, requests, callback)); } })); return; case ResourceType.Marker: path = EditorUtility.SaveFilePanelInProject(k_SaveImageDialogTitle, resource.name, k_ImageExtension, string.Empty); if (string.IsNullOrEmpty(path)) { return; } var markerKey = resourceKey; requests.Push(storageUser.CloudLoadAsync(markerKey, (success, responseCode, response) => { if (success) { requests.Push(storageUser.ImportMarker(path, markerKey, response, callback)); } })); return; case ResourceType.Prefab: requests.Push(storageUser.GetPrefabAsset(project, resourceKey, platform, (bundleSuccess, prefab) => { UnityObject.Instantiate(prefab); })); return; } }
static void Publish() { k_ResourceListUpdates.Clear(); s_UploadCount = 0; var subscriber = CreateSubscriber(); var resourceFolder = CompanionResourceUtils.GetCurrentResourceFolder(); var project = new CompanionProject(Application.cloudProjectId, Application.productName, true); foreach (var selected in Selection.objects) { if (selected is SceneAsset sceneAsset) { var path = AssetDatabase.GetAssetPath(sceneAsset); if (string.IsNullOrEmpty(path)) { Debug.LogError("Selected SceneAsset path is invalid"); continue; } var sceneAssetPack = CompanionSceneUtils.GetOrTryCreateAssetPackForSceneAsset(sceneAsset); sceneAssetPack.Clear(); sceneAssetPack.SceneAsset = sceneAsset; var removeScene = true; var unload = true; var sceneCount = SceneManager.sceneCount; for (var i = 0; i < sceneCount; i++) { var existingScene = SceneManager.GetSceneAt(i); if (existingScene.path == path) { if (existingScene.isLoaded) { unload = false; } removeScene = false; break; } } var scene = EditorSceneManager.OpenScene(path, OpenSceneMode.Additive); var assetGuid = AssetDatabase.AssetPathToGUID(path); if (string.IsNullOrEmpty(assetGuid)) { Debug.LogError("Could not get valid guid for asset at path " + path); continue; } // Hide the OnDestroyNotifier so it is not saved foreach (var root in scene.GetRootGameObjects()) { var notifier = root.GetComponentInChildren <OnDestroyNotifier>(); if (notifier != null) { notifier.gameObject.hideFlags = HideFlags.HideAndDontSave; } } // generate and save thumbnail image for the scene by running a one-shot simulation var thumbnail = SimulateSceneAndCaptureThumbnail(); var cloudGuid = CompanionResourceSync.instance.GetCloudGuid(assetGuid); s_UploadCount++; k_Requests.Clear(); k_Requests.Push(subscriber.SaveScene(project, resourceFolder, cloudGuid, scene, sceneAssetPack, (success, key, size) => { if (scene.IsValid()) { // Restore OnDestroyNotifier HideFlags so that it is discoverable foreach (var root in scene.GetRootGameObjects()) { var notifier = root.GetComponentInChildren <OnDestroyNotifier>(); if (notifier != null) { notifier.gameObject.hideFlags = HideFlags.HideInHierarchy; } } } var sceneName = sceneAsset.name; if (success) { k_ResourceListUpdates.Add(new ResourceListUpdate { ResourceKey = key, Name = sceneName, Type = ResourceType.Scene, FileSize = size, HasBundle = sceneAssetPack.AssetCount > 0, Thumbnail = thumbnail }); Debug.Log($"Published {sceneName}"); } else { Debug.LogError($"Failed to publish {sceneName}"); } if (--s_UploadCount == 0) { FinalizePublish(k_Requests); } })); // Scene serialization is synchronous, so we can close the scene while the upload completes if (unload) { EditorSceneManager.CloseScene(scene, removeScene); } continue; } if (selected is GameObject prefab) { var path = AssetDatabase.GetAssetPath(prefab); if (string.IsNullOrEmpty(path)) { Debug.LogError("SceneAsset for selected AssetPack path is invalid"); continue; } var guid = AssetDatabase.AssetPathToGUID(path); if (string.IsNullOrEmpty(guid)) { Debug.LogError("Could not get valid guid for asset at path " + path); continue; } // Check existing asset bundle name to avoid overwriting it var assetImporter = AssetImporter.GetAtPath(path); if (!string.IsNullOrEmpty(assetImporter.assetBundleName) && assetImporter.assetBundleName != guid) { Debug.LogError(prefab.name + " is already part of an AssetBundle, and cannot be published to the cloud without overwriting its AssetBundle name"); continue; } var bounds = BoundsUtils.GetBounds(prefab.transform); if (bounds.extents.MinComponent() < k_MinPrefabBound) { Debug.LogError(prefab.name + " is too small to be selectable. MARS prefabs must be selectable."); continue; } var thumbnail = ThumbnailUtility.GeneratePrefabThumbnail(prefab); if (thumbnail == null) { Debug.LogError(prefab.name + " is not a visible asset. MARS prefabs must be visible."); continue; } s_UploadCount++; k_Requests.Clear(); k_Requests.Push(subscriber.SavePrefab(project, resourceFolder, prefab, (success, key) => { if (success) { k_ResourceListUpdates.Add(new ResourceListUpdate { ResourceKey = key, Name = prefab.name, Type = ResourceType.Prefab, HasBundle = true, Thumbnail = thumbnail }); Debug.Log($"Published {prefab.name}"); } else { Debug.LogError($"Failed to publish {prefab.name}"); } if (--s_UploadCount == 0) { FinalizePublish(k_Requests); } })); } } }
static RequestHandle GetAssetBundleAsset(this IUsesCloudStorage storageUser, string group, CompanionProject project, string resourceFolder, string platform, string guid, Action <bool, UnityObject> callback) { const int timeout = 0; // Asset bundles may be quite large and take a long time to download // Write to local storage in case cloud isn't reachable var folder = CompanionResourceUtils.GetLocalResourceFolderPath(project, resourceFolder, group); var path = Path.Combine(folder, guid); if (!project.linked) { UnityObject asset = null; if (File.Exists(path)) { asset = GetLocalAssetBundleAsset(path); } callback(asset != null, asset); return(default);
public static bool TryLinkProject(string id, string name, string localIndex, out string errorTitle, out string errorBody) { if (string.IsNullOrEmpty(id)) { errorTitle = InvalidIDErrorTitle; errorBody = InvalidIDErrorBody; return(false); } if (string.IsNullOrEmpty(localIndex)) { errorTitle = k_InvalidLocalProjectErrorTitle; errorBody = k_InvalidLocalProjectErrorBody; return(false); } var projectList = LoadProjects(); CompanionProject localProject = null; foreach (var project in projectList.projects) { if (project.index == id) { errorTitle = k_AlreadyImportedErrorTitle; errorBody = string.Format(k_AlreadyImportedErrorBodyFormat, project.name); return(false); } if (project.index == localIndex) { localProject = project; } } if (localProject == null) { errorTitle = k_InvalidLocalProjectErrorTitle; errorBody = k_InvalidLocalProjectErrorBody; return(false); } try { if (!MoveProjectFolder(localIndex, id, out errorTitle, out errorBody)) { return(false); } } catch (Exception e) { Debug.LogException(e); errorTitle = "Failed to move project folder"; errorBody = $"Project save failed with the following message {e.Message}"; return(false); } localProject.Link(id, name); SaveProjects(projectList); errorTitle = null; return(true); }