diff --git a/Assets/Extra/ManualRecieveExample.cs b/Assets/Extra/ManualReceive.cs similarity index 79% rename from Assets/Extra/ManualRecieveExample.cs rename to Assets/Extra/ManualReceive.cs index 7975424..7536110 100644 --- a/Assets/Extra/ManualRecieveExample.cs +++ b/Assets/Extra/ManualReceive.cs @@ -1,3 +1,4 @@ +using System; using System.Collections; using System.Threading.Tasks; using Speckle.ConnectorUnity; @@ -7,6 +8,7 @@ using Speckle.Core.Credentials; using Speckle.Core.Transports; using UnityEngine; +[AddComponentMenu("Speckle/Extras/Manual Receiver")] [RequireComponent(typeof(RecursiveConverter))] public class ManualReceive : MonoBehaviour { @@ -30,7 +32,8 @@ public class ManualReceive : MonoBehaviour if(Time.timeSinceLevelLoad > 20) yield return null; Receive(); } - + + [ContextMenu(nameof(Receive))] public void Receive() { var account = new Account() @@ -48,14 +51,17 @@ public class ManualReceive : MonoBehaviour objectId, remoteTransport: transport, localTransport: localTransport, - onErrorAction: (m, e)=> Debug.LogError(m + e), + onErrorAction: (m, e) => Debug.LogError(m + e), disposeTransports: true ); - + + if (@base == null) throw new Exception("received data was null!"); + Dispatcher.Instance().Enqueue(() => { var parentObject = new GameObject(name); - receiver.RecursivelyConvertToNative(@base, parentObject.transform); + + receiver.RecursivelyConvertToNative_Sync(@base, parentObject.transform); Debug.Log($"Receive {objectId} completed"); }); diff --git a/Assets/Extra/ManualRecieveExample.cs.meta b/Assets/Extra/ManualReceive.cs.meta similarity index 100% rename from Assets/Extra/ManualRecieveExample.cs.meta rename to Assets/Extra/ManualReceive.cs.meta diff --git a/Assets/Extra/ReceiveFromURL.cs b/Assets/Extra/ReceiveFromURL.cs new file mode 100644 index 0000000..4f42d45 --- /dev/null +++ b/Assets/Extra/ReceiveFromURL.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Speckle.ConnectorUnity.Components; +using Speckle.ConnectorUnity.Utils; +using Speckle.Core.Api; +using Speckle.Core.Credentials; +using Speckle.Core.Logging; +using Speckle.Core.Models; +using UnityEngine; + +[AddComponentMenu("Speckle/Extras/Receive From Url")] +[RequireComponent(typeof(RecursiveConverter)), ExecuteAlways] +public class ReceiveFromURL : MonoBehaviour +{ + [Tooltip("Url of your speckle object/commit/branch/stream")] + public string url; + + private RecursiveConverter _converter; + +#nullable enable + private CancellationTokenSource? _tokenSource; + void Awake() + { + _converter = GetComponent(); + } + + [ContextMenu(nameof(Receive))] + public void Receive() + { + StartCoroutine(Receive_Routine()); + } + + public IEnumerator Receive_Routine() + { + if (IsBusy()) throw new InvalidOperationException("A receive operation has already started"); + _tokenSource = new CancellationTokenSource(); + try + { + StreamWrapper sw = new(url); + + if (!sw.IsValid) + throw new InvalidOperationException("Speckle url input is not a valid speckle stream/branch/commit"); + + var accountTask = new Utils.WaitForTask(async () => await GetAccount(sw)); + yield return accountTask; + + _tokenSource.Token.ThrowIfCancellationRequested(); + using Client c = new(accountTask.Result); + + var objectIdTask = new Utils.WaitForTask<(string, Commit?)>(async () => await GetObjectID(sw, c)); + yield return objectIdTask; + (string objectId, Commit? commit) = objectIdTask.Result; + + Debug.Log($"Receiving from {sw.ServerUrl}..."); + + var receiveTask = new Utils.WaitForTask(async () => await SpeckleReceiver.ReceiveAsync( + c, + sw.StreamId, + objectId, + commit, + cancellationToken: _tokenSource.Token)); + yield return receiveTask; + + Debug.Log("Converting to native..."); + _converter.RecursivelyConvertToNative_Sync(receiveTask.Result, transform); + } + finally + { + _tokenSource.Dispose(); + _tokenSource = null; + } + } + + + private async Task<(string objectId, Commit? commit)> GetObjectID(StreamWrapper sw, Client client) + { + string objectId; + Commit? commit = null; + //OBJECT URL + if (!string.IsNullOrEmpty(sw.ObjectId)) + { + objectId = sw.ObjectId; + } + //COMMIT URL + else if (!string.IsNullOrEmpty(sw.CommitId)) + { + commit = await client.CommitGet(sw.StreamId, sw.CommitId).ConfigureAwait(false); + objectId = commit.referencedObject; + } + //BRANCH URL OR STREAM URL + else + { + var branchName = string.IsNullOrEmpty(sw.BranchName) ? "main" : sw.BranchName; + + var branch = await client.BranchGet(sw.StreamId, branchName, 1).ConfigureAwait(false); + if (!branch.commits.items.Any()) + throw new SpeckleException("The selected branch has no commits."); + + commit = branch.commits.items[0]; + objectId = branch.commits.items[0].referencedObject; + } + + return (objectId, commit); + } + [ContextMenu(nameof(Cancel))] + public void Cancel() + { + if (IsNotBusy()) throw new InvalidOperationException("There are no pending receive operations to cancel"); + _tokenSource!.Cancel(); + } + + [ContextMenu(nameof(Cancel), true)] + public bool IsBusy() + { + return _tokenSource is not null; + } + + [ContextMenu(nameof(Receive), true)] + internal bool IsNotBusy() => !IsBusy(); + + private void OnDisable() + { + _tokenSource?.Cancel(); + } + + + private async Task GetAccount(StreamWrapper sw) + { + Account account; + try + { + account = await sw.GetAccount().ConfigureAwait(false); + } + catch (SpeckleException e) + { + if (string.IsNullOrEmpty(sw.StreamId)) + throw e; + + //Fallback to a non authed account + account = new Account + { + token = "", + serverInfo = new ServerInfo { url = sw.ServerUrl }, + userInfo = new UserInfo() + }; + } + + return account; + } +} diff --git a/Assets/Tests/Editor/PerformanceTest.cs.meta b/Assets/Extra/ReceiveFromURL.cs.meta similarity index 83% rename from Assets/Tests/Editor/PerformanceTest.cs.meta rename to Assets/Extra/ReceiveFromURL.cs.meta index 0e99b1e..431ea44 100644 --- a/Assets/Tests/Editor/PerformanceTest.cs.meta +++ b/Assets/Extra/ReceiveFromURL.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 2dd598fed5008c44a815ba09e81a2d19 +guid: 942bf0cb27c5c5045bc4cbb7fc0fad71 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Extra/SendChildrenToSpeckle.cs b/Assets/Extra/SendChildrenToSpeckle.cs index 82d7fe6..47f17f1 100644 --- a/Assets/Extra/SendChildrenToSpeckle.cs +++ b/Assets/Extra/SendChildrenToSpeckle.cs @@ -9,6 +9,7 @@ using UnityEngine; using UnityEngine.Events; [RequireComponent(typeof(Sender)), ExecuteAlways] +[Obsolete] public class SendChildrenToSpeckle : MonoBehaviour { public LayerMask layerMask; diff --git a/Assets/Extra/Speckle.Extra.asmdef b/Assets/Extra/Speckle.Extra.asmdef index 8aca4ca..0f341a8 100644 --- a/Assets/Extra/Speckle.Extra.asmdef +++ b/Assets/Extra/Speckle.Extra.asmdef @@ -1,4 +1,4 @@ { "name": "Speckle.Extra", - "references":[ "GUID:eed1b8b83e2c0074d9e5de2348e3ff72", "GUID:e6adfdc4e436206479f48eafc82f32b5", "GUID:d274441ecc3eb3f43b093eec1503d681" ] + "references":[ "GUID:eed1b8b83e2c0074d9e5de2348e3ff72", "GUID:e6adfdc4e436206479f48eafc82f32b5", "GUID:d274441ecc3eb3f43b093eec1503d681", "GUID:50d889142fdf9de4b8501c6eaa4b3225" ] } diff --git a/Assets/InteractionLogic.cs b/Assets/InteractionLogic.cs index 986ad6c..5418810 100644 --- a/Assets/InteractionLogic.cs +++ b/Assets/InteractionLogic.cs @@ -10,7 +10,8 @@ using UnityEngine.UI; using Text = UnityEngine.UI.Text; namespace Speckle.ConnectorUnity -{ +{ + [Obsolete] public class InteractionLogic : MonoBehaviour { private Receiver receiver; @@ -216,4 +217,4 @@ namespace Speckle.ConnectorUnity }); } } -} \ No newline at end of file +} diff --git a/Assets/SpeckleExamples.cs b/Assets/SpeckleExamples.cs index e17fa36..6587b63 100644 --- a/Assets/SpeckleExamples.cs +++ b/Assets/SpeckleExamples.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using UnityEngine; using Speckle.Core.Credentials; @@ -8,6 +9,7 @@ using Stream = Speckle.Core.Api.Stream; namespace Speckle.ConnectorUnity { + [Obsolete] public class SpeckleExamples : MonoBehaviour { public Text SelectStreamText; @@ -129,4 +131,4 @@ namespace Speckle.ConnectorUnity } } } -} \ No newline at end of file +} diff --git a/Assets/SpecklePlayground.unity b/Assets/SpecklePlayground.unity index d8a460c..4cc1441 100644 --- a/Assets/SpecklePlayground.unity +++ b/Assets/SpecklePlayground.unity @@ -377,6 +377,165 @@ Rigidbody: m_Interpolate: 0 m_Constraints: 0 m_CollisionDetection: 0 +--- !u!1 &50753198 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 50753202} + - component: {fileID: 50753201} + - component: {fileID: 50753200} + - component: {fileID: 50753199} + m_Layer: 0 + m_Name: Speckle Connector + m_TagString: Untagged + m_Icon: {fileID: 2800000, guid: ee2ed9d8fff3a1d4db5590491978062e, type: 3} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &50753199 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 50753198} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: b95e704835cc48444b81e33c978f6f7f, type: 3} + m_Name: + m_EditorClassIdentifier: + k__BackingField: + rid: 3905486442366500889 + k__BackingField: + rid: 3905486442366500890 + k__BackingField: + rid: 3905486442366500891 + OnBranchSelectionChange: + m_PersistentCalls: + m_Calls: [] + OnErrorAction: + m_PersistentCalls: + m_Calls: [] + OnSendProgressAction: + m_PersistentCalls: + m_Calls: [] + references: + version: 2 + RefIds: + - rid: 3905486442366500889 + type: {class: AccountSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers} + data: + selectedIndex: 0 + - rid: 3905486442366500890 + type: {class: StreamSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers} + data: + selectedIndex: 0 + k__BackingField: 50 + k__BackingField: + rid: 3905486442366500889 + - rid: 3905486442366500891 + type: {class: BranchSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers} + data: + selectedIndex: 0 + k__BackingField: 100 + k__BackingField: 0 + k__BackingField: + rid: 3905486442366500890 +--- !u!114 &50753200 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 50753198} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0bc895f6cb37b674995dc13b79783c55, type: 3} + m_Name: + m_EditorClassIdentifier: + k__BackingField: + rid: 3905486442366500885 + k__BackingField: + rid: 3905486442366500886 + k__BackingField: + rid: 3905486442366500887 + k__BackingField: + rid: 3905486442366500888 + OnCommitSelectionChange: + m_PersistentCalls: + m_Calls: [] + OnReceiveProgressAction: + m_PersistentCalls: + m_Calls: [] + OnErrorAction: + m_PersistentCalls: + m_Calls: [] + OnTotalChildrenCountKnown: + m_PersistentCalls: + m_Calls: [] + OnComplete: + m_PersistentCalls: + m_Calls: [] + references: + version: 2 + RefIds: + - rid: 3905486442366500885 + type: {class: AccountSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers} + data: + selectedIndex: 0 + - rid: 3905486442366500886 + type: {class: StreamSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers} + data: + selectedIndex: 0 + k__BackingField: 50 + k__BackingField: + rid: 3905486442366500885 + - rid: 3905486442366500887 + type: {class: BranchSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers} + data: + selectedIndex: 0 + k__BackingField: 100 + k__BackingField: 25 + k__BackingField: + rid: 3905486442366500886 + - rid: 3905486442366500888 + type: {class: CommitSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers} + data: + selectedIndex: 0 + k__BackingField: + rid: 3905486442366500887 +--- !u!114 &50753201 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 50753198} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: ed6cbf9ce4dca0349997d163ec9bce7e, type: 3} + m_Name: + m_EditorClassIdentifier: + k__BackingField: {fileID: 1490514812} +--- !u!4 &50753202 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 50753198} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 9 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &73017256 GameObject: m_ObjectHideFlags: 0 @@ -928,165 +1087,6 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 194696812} m_CullTransparentMesh: 1 ---- !u!1 &218987857 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 218987861} - - component: {fileID: 218987860} - - component: {fileID: 218987859} - - component: {fileID: 218987858} - m_Layer: 0 - m_Name: Speckle Connector - m_TagString: Untagged - m_Icon: {fileID: 2800000, guid: ee2ed9d8fff3a1d4db5590491978062e, type: 3} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!114 &218987858 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 218987857} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: b95e704835cc48444b81e33c978f6f7f, type: 3} - m_Name: - m_EditorClassIdentifier: - k__BackingField: - rid: 5855987529328361546 - k__BackingField: - rid: 5855987529328361547 - k__BackingField: - rid: 5855987529328361548 - OnBranchSelectionChange: - m_PersistentCalls: - m_Calls: [] - OnErrorAction: - m_PersistentCalls: - m_Calls: [] - OnSendProgressAction: - m_PersistentCalls: - m_Calls: [] - references: - version: 2 - RefIds: - - rid: 5855987529328361546 - type: {class: AccountSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers} - data: - selectedIndex: 0 - - rid: 5855987529328361547 - type: {class: StreamSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers} - data: - selectedIndex: 0 - k__BackingField: 50 - k__BackingField: - rid: 5855987529328361546 - - rid: 5855987529328361548 - type: {class: BranchSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers} - data: - selectedIndex: 0 - k__BackingField: 30 - k__BackingField: 0 - k__BackingField: - rid: 5855987529328361547 ---- !u!114 &218987859 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 218987857} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 0bc895f6cb37b674995dc13b79783c55, type: 3} - m_Name: - m_EditorClassIdentifier: - k__BackingField: - rid: 5855987529328361542 - k__BackingField: - rid: 5855987529328361543 - k__BackingField: - rid: 5855987529328361544 - k__BackingField: - rid: 5855987529328361545 - OnCommitSelectionChange: - m_PersistentCalls: - m_Calls: [] - OnReceiveProgressAction: - m_PersistentCalls: - m_Calls: [] - OnErrorAction: - m_PersistentCalls: - m_Calls: [] - OnTotalChildrenCountKnown: - m_PersistentCalls: - m_Calls: [] - OnComplete: - m_PersistentCalls: - m_Calls: [] - references: - version: 2 - RefIds: - - rid: 5855987529328361542 - type: {class: AccountSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers} - data: - selectedIndex: 0 - - rid: 5855987529328361543 - type: {class: StreamSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers} - data: - selectedIndex: 0 - k__BackingField: 50 - k__BackingField: - rid: 5855987529328361542 - - rid: 5855987529328361544 - type: {class: BranchSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers} - data: - selectedIndex: 0 - k__BackingField: 30 - k__BackingField: 15 - k__BackingField: - rid: 5855987529328361543 - - rid: 5855987529328361545 - type: {class: CommitSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers} - data: - selectedIndex: 0 - k__BackingField: - rid: 5855987529328361544 ---- !u!114 &218987860 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 218987857} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: ed6cbf9ce4dca0349997d163ec9bce7e, type: 3} - m_Name: - m_EditorClassIdentifier: - k__BackingField: {fileID: 1710028308} ---- !u!4 &218987861 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 218987857} - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 0} - m_RootOrder: 9 - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &234733581 GameObject: m_ObjectHideFlags: 0 @@ -1135,6 +1135,19 @@ Transform: m_Father: {fileID: 0} m_RootOrder: 4 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &236313388 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2a4a29c776298714c88f406ad39c6095, type: 3} + m_Name: + m_EditorClassIdentifier: + matchByName: 1 --- !u!1 &310693430 GameObject: m_ObjectHideFlags: 0 @@ -1616,18 +1629,6 @@ Rigidbody: m_Interpolate: 0 m_Constraints: 0 m_CollisionDetection: 0 ---- !u!114 &540478226 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: b3354e8208862c341940152f5340d41a, type: 3} - m_Name: - m_EditorClassIdentifier: --- !u!1 &641375517 GameObject: m_ObjectHideFlags: 0 @@ -3176,6 +3177,21 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1465364520} m_CullTransparentMesh: 1 +--- !u!114 &1490514812 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 88d6b4f2f80eaa14f9f07505f7e44ec2, type: 3} + m_Name: + m_EditorClassIdentifier: + nativeCaches: + - {fileID: 236313388} + - {fileID: 1545552959} --- !u!1 &1506703863 GameObject: m_ObjectHideFlags: 0 @@ -3341,6 +3357,18 @@ CanvasGroup: m_Interactable: 1 m_BlocksRaycasts: 1 m_IgnoreParentGroups: 0 +--- !u!114 &1545552959 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: b3354e8208862c341940152f5340d41a, type: 3} + m_Name: + m_EditorClassIdentifier: --- !u!1 &1587881868 GameObject: m_ObjectHideFlags: 0 @@ -3599,21 +3627,6 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1707872729} m_CullTransparentMesh: 1 ---- !u!114 &1710028308 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 88d6b4f2f80eaa14f9f07505f7e44ec2, type: 3} - m_Name: - m_EditorClassIdentifier: - nativeCaches: - - {fileID: 1771830985} - - {fileID: 540478226} --- !u!1 &1729237655 GameObject: m_ObjectHideFlags: 0 @@ -3726,19 +3739,6 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1762991479} m_CullTransparentMesh: 1 ---- !u!114 &1771830985 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 2a4a29c776298714c88f406ad39c6095, type: 3} - m_Name: - m_EditorClassIdentifier: - matchByName: 1 --- !u!1 &1885647142 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/Tests/Editor/Editor.asmdef b/Assets/Tests/Editor/EditorTests.asmdef similarity index 87% rename from Assets/Tests/Editor/Editor.asmdef rename to Assets/Tests/Editor/EditorTests.asmdef index 6e33215..01770e4 100644 --- a/Assets/Tests/Editor/Editor.asmdef +++ b/Assets/Tests/Editor/EditorTests.asmdef @@ -1,6 +1,6 @@ { - "name": "Editor", - "rootNamespace": "", + "name": "EditorTests", + "rootNamespace": "Speckle.ConnectorUnity.Tests", "references": [ "UnityEngine.TestRunner", "UnityEditor.TestRunner", diff --git a/Assets/Tests/Editor/Editor.asmdef.meta b/Assets/Tests/Editor/EditorTests.asmdef.meta similarity index 100% rename from Assets/Tests/Editor/Editor.asmdef.meta rename to Assets/Tests/Editor/EditorTests.asmdef.meta diff --git a/Assets/Tests/Editor/PerformanceTest.cs b/Assets/Tests/Editor/PerformanceTest.cs deleted file mode 100644 index cc6a0a9..0000000 --- a/Assets/Tests/Editor/PerformanceTest.cs +++ /dev/null @@ -1,96 +0,0 @@ - -using System; -using System.Collections; -using System.Diagnostics; -using System.Threading.Tasks; -using NUnit.Framework; -using Objects.Utils; -using Speckle.Core.Api; -using Speckle.Core.Models; -using Speckle.Core.Models.Extensions; -using UnityEngine; -using UnityEngine.TestTools; - - -public class PerformanceTest -{ - private static readonly string[] dataSource = new[] - { - "https://latest.speckle.dev/streams/24c3741255/commits/0925840e09" - }; - - - //This method is much faster - [Test, TestCaseSource(nameof(dataSource))] - public void Receive_GetAwaiterResult(string stream) - { - var stopwatch = Stopwatch.StartNew(); - - Helpers.Receive(stream).GetAwaiter().GetResult(); - - stopwatch.Stop(); - Console.WriteLine(stopwatch.ElapsedMilliseconds); - Assert.That(stopwatch.ElapsedMilliseconds, Is.Zero); - } - - - //This method takes around 46 seconds to complete - [Test, TestCaseSource(nameof(dataSource))] - public void Receive_TaskRunAsync(string stream) - { - var stopwatch = Stopwatch.StartNew(); - - Task.Run(async () => - { - await Helpers.Receive(stream); - }).GetAwaiter().GetResult(); - - stopwatch.Stop(); - Console.WriteLine(stopwatch.ElapsedMilliseconds); - Assert.That(stopwatch.ElapsedMilliseconds, Is.Zero); - } - - // [UnityTest, TestCaseSource(nameof(dataSource))] - // public IEnumerable Receive_Coroutine(string stream) - // { - // var stopwatch = Stopwatch.StartNew(); - // - // Task t = Helpers.Receive(stream); - // t.Start(); - // - // yield return new WaitUntil(() => !t.IsCompleted || stopwatch.ElapsedMilliseconds >= 100000); - // - // stopwatch.Stop(); - // Console.WriteLine(stopwatch.ElapsedMilliseconds); - // Assert.That(stopwatch.ElapsedMilliseconds, Is.Zero); - // Assert.True(t.IsCompletedSuccessfully); - // } - - - - - //This method takes around 46 seconds to complete - [Test] - public void TestTriangulate() - { - - - Base b = Task.Run(async () => - { - return await Helpers.Receive("https://speckle.xyz/streams/4a8fd0c6b6/commits/067bf723b1"); - }).GetAwaiter().GetResult(); - - - foreach (Base child in b.Traverse(b => b is Objects.Geometry.Mesh)) - { - if(child is not Objects.Geometry.Mesh m) continue; - - var stopwatch = Stopwatch.StartNew(); - - m.TriangulateMesh(); - - Console.WriteLine($"took {stopwatch.ElapsedMilliseconds:ms} to triangulate {child.id}"); - } - } - -} diff --git a/Assets/Tests/PlayMode.meta b/Assets/Tests/PlayMode.meta new file mode 100644 index 0000000..f872051 --- /dev/null +++ b/Assets/Tests/PlayMode.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5a4f4baa829261d438b740c7d3028756 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/PlayMode/ComponentTest.cs b/Assets/Tests/PlayMode/ComponentTest.cs new file mode 100644 index 0000000..978f21c --- /dev/null +++ b/Assets/Tests/PlayMode/ComponentTest.cs @@ -0,0 +1,18 @@ +using NUnit.Framework; +using UnityEngine; + +namespace Speckle.ConnectorUnity.Tests +{ + public abstract class ComponentTest where T : Component + { + protected T sut; + + [SetUp] + public void Setup() + { + GameObject go = new(); + sut = go.AddComponent(); + Assert.That(sut, Is.Not.Null); + } + } +} diff --git a/Assets/Tests/PlayMode/ComponentTest.cs.meta b/Assets/Tests/PlayMode/ComponentTest.cs.meta new file mode 100644 index 0000000..70603d7 --- /dev/null +++ b/Assets/Tests/PlayMode/ComponentTest.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4e2fe277dd9c47ad998138dcdbb024ae +timeCreated: 1686757093 \ No newline at end of file diff --git a/Assets/Tests/PlayMode/ConvertToNativeTests.cs b/Assets/Tests/PlayMode/ConvertToNativeTests.cs new file mode 100644 index 0000000..0d5fc44 --- /dev/null +++ b/Assets/Tests/PlayMode/ConvertToNativeTests.cs @@ -0,0 +1,50 @@ + +using System.Collections; +using System.Collections.Generic; +using System.Threading.Tasks; +using NUnit.Framework; +using NUnit.Framework.Constraints; +using Speckle.ConnectorUnity.Components; +using Speckle.ConnectorUnity.Wrappers; +using Speckle.Core.Api; +using Speckle.Core.Models; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Speckle.ConnectorUnity.Tests +{ + [TestFixture, TestOf(typeof(RecursiveConverter))] + public class ConvertToNativeTests : ComponentTest + { + private static IEnumerable TestCases() + { + yield return @"https://latest.speckle.dev/streams/c1faab5c62/commits/704984e22d"; + } + + private static Base Receive(string stream) + { + return Task.Run(async () => await Helpers.Receive(stream)).Result; + } + + [Test, TestCaseSource(nameof(TestCases))] + public void ToNative_Passes(string stream) + { + Base testCase = Receive(stream); + var results = sut.RecursivelyConvertToNative_Sync(testCase, null); + Assert.That(results, Has.Count.GreaterThan(0)); + Assert.That(results, HasSomeComponent()); + Assert.That(results, HasSomeComponent()); + Assert.That(results, HasSomeComponent()); + } + + private static Constraint HasSomeComponent() where T : Component + { + return Has.Some.Matches( + x => + { + return x.WasSuccessful(out var success, out _) + && success.GetComponent(); + }); + } + } +} diff --git a/Assets/Tests/PlayMode/ConvertToNativeTests.cs.meta b/Assets/Tests/PlayMode/ConvertToNativeTests.cs.meta new file mode 100644 index 0000000..897febd --- /dev/null +++ b/Assets/Tests/PlayMode/ConvertToNativeTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3d9b0fc7baaf51a4a8e2bcefad8bd7b3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/PlayMode/PlayModeTests.asmdef b/Assets/Tests/PlayMode/PlayModeTests.asmdef new file mode 100644 index 0000000..45648d7 --- /dev/null +++ b/Assets/Tests/PlayMode/PlayModeTests.asmdef @@ -0,0 +1,28 @@ +{ + "name": "PlayModeTests", + "rootNamespace": "Speckle.ConnectorUnity.Tests", + "references": [ + "UnityEngine.TestRunner", + "UnityEditor.TestRunner", + "Speckle.ConnectorUnity.Components", + "Utils", + "Speckle.ConnectorUnity.Wrappers" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll", + "SpeckleCore2.dll", + "Objects.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/Tests/PlayMode/PlayModeTests.asmdef.meta b/Assets/Tests/PlayMode/PlayModeTests.asmdef.meta new file mode 100644 index 0000000..01bf67d --- /dev/null +++ b/Assets/Tests/PlayMode/PlayModeTests.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 79301723eb79d2745ab1e1a9360f6f2d +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/PlayMode/SpeckleReceiverTests.cs b/Assets/Tests/PlayMode/SpeckleReceiverTests.cs new file mode 100644 index 0000000..b1e68f5 --- /dev/null +++ b/Assets/Tests/PlayMode/SpeckleReceiverTests.cs @@ -0,0 +1,73 @@ +#nullable enable +using System; +using System.Collections; +using NUnit.Framework; +using Speckle.ConnectorUnity.Components; +using Speckle.Core.Models; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Speckle.ConnectorUnity.Tests +{ + [TestFixture, TestOf(typeof(SpeckleReceiver))] + public sealed class SpeckleReceiverTests : ComponentTest + { + + [UnityTest] + public IEnumerator ReceiveAsync_Succeeds() + { + yield return null; + + var task = new Utils.Utils.WaitForTask(async () => await sut.ReceiveAsync(default)); + yield return task; + Base myBase = task.Result; + Assert.That(myBase, Is.Not.Null); + } + + [UnityTest] + public IEnumerator ReceiveAndConvert_Async_Succeeds() + { + Transform expectedParent = new GameObject("parent").transform; + yield return null; + + bool wasSuccessful = false; + Transform? actualParent = null; + + sut.OnComplete.AddListener(t => + { + wasSuccessful = true; + actualParent = t; + }); + sut.OnErrorAction.AddListener((_, ex) => throw new Exception("Failed", ex)); + + sut.ReceiveAndConvert_Async(expectedParent); + + yield return new WaitUntil(() => wasSuccessful); + + Assert.That(actualParent, Is.EqualTo(expectedParent)); + } + + [UnityTest] + public IEnumerator ReceiveAndConvert_Routine_Succeeds() + { + Transform expectedParent = new GameObject("parent").transform; + yield return null; + + bool wasSuccessful = false; + Transform? actualParent = null; + + sut.OnComplete.AddListener(t => + { + wasSuccessful = true; + actualParent = t; + }); + sut.OnErrorAction.AddListener((_, ex) => throw new Exception("Failed", ex)); + + yield return sut.ReceiveAndConvert_Routine(expectedParent); + + yield return new WaitUntil(() => wasSuccessful); + + Assert.That(actualParent, Is.EqualTo(expectedParent)); + } + } +} diff --git a/Assets/Tests/PlayMode/SpeckleReceiverTests.cs.meta b/Assets/Tests/PlayMode/SpeckleReceiverTests.cs.meta new file mode 100644 index 0000000..5664628 --- /dev/null +++ b/Assets/Tests/PlayMode/SpeckleReceiverTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1756c50dd28a4e341a70866daa68a8d5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/systems.speckle.speckle-unity/Editor/Components/SpeckleReceiverEditor.cs b/Packages/systems.speckle.speckle-unity/Editor/Components/SpeckleReceiverEditor.cs index a354208..3234f39 100644 --- a/Packages/systems.speckle.speckle-unity/Editor/Components/SpeckleReceiverEditor.cs +++ b/Packages/systems.speckle.speckle-unity/Editor/Components/SpeckleReceiverEditor.cs @@ -1,9 +1,8 @@ using System; using System.Collections.Concurrent; -using System.Linq; using System.Threading.Tasks; -using Speckle.Core.Api; using Speckle.Core.Models; +using Speckle.Core.Models.GraphTraversal; using UnityEditor; using UnityEngine; @@ -18,6 +17,77 @@ namespace Speckle.ConnectorUnity.Components.Editor private bool foldOutStatus = true; private Texture2D? previewImage; + public override async void OnInspectorGUI() + { + var speckleReceiver = (SpeckleReceiver)target; + + DrawDefaultInspector(); + + //Preview image + { + foldOutStatus = EditorGUILayout.Foldout(foldOutStatus, "Preview Image"); + if (foldOutStatus) + { + Rect rect = GUILayoutUtility.GetAspectRect(7f / 4f); + if (previewImage != null) GUI.DrawTexture(rect, previewImage); + } + } + + //TODO: Draw events in a collapsed region + + //Receive settings + { + bool prev = GUI.enabled; + GUI.enabled = !speckleReceiver.IsReceiving; + //Receive button + bool userRequestedReceive = GUILayout.Button("Receive!"); + + bool selection = EditorGUILayout.ToggleLeft("Generate Assets", generateAssets); + if (generateAssets != selection) + { + generateAssets = selection; + UpdateGenerateAssets(); + } + GUI.enabled = prev; + + if (speckleReceiver.IsReceiving) + { + var value = Progress.globalProgress; //NOTE: this may include non-speckle items... + var percent = Math.Max(0, Mathf.Ceil(value * 100)); + Debug.Log(value); + var rect = EditorGUILayout.GetControlRect(false, EditorGUIUtility.singleLineHeight); + EditorGUI.ProgressBar(rect, value, $"{percent}%"); + } + else if (userRequestedReceive) + { + var id = Progress.Start( + "Receiving Speckle data", + "Fetching commit data", Progress.Options.Sticky); + Progress.ShowDetails(); + + try + { + await ReceiveSelection(id).ConfigureAwait(true); + Progress.Finish(id); + } + catch (OperationCanceledException ex) + { + Progress.Finish(id, Progress.Status.Canceled); + Debug.Log($"Receive operation cancelled\n{ex}", this); + } + catch (Exception ex) + { + Progress.Finish(id, Progress.Status.Failed); + Debug.LogError($"Receive operation failed {ex}", this); + } + finally + { + EditorUtility.ClearProgressBar(); + } + } + } + } + public void OnEnable() { Init(); @@ -28,6 +98,7 @@ namespace Speckle.ConnectorUnity.Components.Editor Init(); } + private void Init() { var speckleReceiver = (SpeckleReceiver) target; @@ -42,39 +113,92 @@ namespace Speckle.ConnectorUnity.Components.Editor ((SpeckleReceiver)target).GetPreviewImage(t => previewImage = t); } - public override async void OnInspectorGUI() + private async Task ReceiveSelection(int progressId) { - var speckleReceiver = (SpeckleReceiver) target; + var speckleReceiver = (SpeckleReceiver)target; - DrawDefaultInspector(); - - //Preview image - foldOutStatus = EditorGUILayout.Foldout(foldOutStatus, "Preview Image"); - if (foldOutStatus) + bool shouldCancel = false; + + Progress.RegisterCancelCallback(progressId, () => { - Rect rect = GUILayoutUtility.GetAspectRect(7f/4f); - if(previewImage != null) GUI.DrawTexture(rect, previewImage); + speckleReceiver.Cancel(); + shouldCancel = true; + return true; + }); + + Base commitObject; + try + { + var token = speckleReceiver.BeginOperation(); + commitObject = await Task.Run(async () => await ReceiveCommit(progressId).ConfigureAwait(false), + token + ) + .ConfigureAwait(true); + } + finally + { + speckleReceiver.FinishOperation(); } - - //Receive button - bool receive = GUILayout.Button("Receive!"); + int childrenConverted = 0; + int childrenFailed = 0; - bool selection = EditorGUILayout.ToggleLeft("Generate Assets", generateAssets); - if (generateAssets != selection) + int totalChildren = (int) Math.Min(commitObject.totalChildrenCount, int.MaxValue); + float totalChildrenFloat = commitObject.totalChildrenCount; + + var convertProgress = Progress.Start("Converting To Native", "Preparing...", Progress.Options.Indefinite | Progress.Options.Sticky, progressId); + + bool BeforeConvert(TraversalContext context) { - generateAssets = selection; - UpdateGenerateAssets(); - } - - - //TODO: Draw events in a collapsed region + Base b = context.current; - - if (receive) - { - await ReceiveAndConvert(speckleReceiver).ConfigureAwait(false);; + //NOTE: progress wont reach 100% because not all objects are convertable + float progress = (childrenConverted + childrenFailed) / totalChildrenFloat; + + if (shouldCancel) return false; + + shouldCancel = EditorUtility.DisplayCancelableProgressBar( + "Converting To Native...", + $"{b.speckle_type} - {b.id}", + progress); + + return !shouldCancel; } + + foreach (var conversionResult in speckleReceiver.Converter.RecursivelyConvertToNative_Enumerable( + commitObject, + speckleReceiver.transform, + BeforeConvert)) + { + Base speckleObject = conversionResult.SpeckleObject; + if (conversionResult.WasSuccessful(out _, out var ex)) + { + childrenConverted++; + } + else + { + childrenFailed++; + Debug.LogWarning( + $"Failed to convert Speckle object of type {speckleObject.speckle_type}\n{ex}", + this); + } + + Progress.Report(progressId, childrenConverted + childrenFailed, totalChildren, "Receiving objects"); + + if (shouldCancel) break; + } + + var resultString = $"{childrenConverted} {nameof(GameObject)}s created"; + if (childrenFailed != 0) resultString += $", {childrenFailed} objects failed to convert!"; + + Debug.Log(shouldCancel + ? $"Stopped converting to native: The operation has been cancelled - {resultString}\n " + : $"Finished converting to native.\n{resultString}", + speckleReceiver); + + Progress.Finish(convertProgress); + + if (shouldCancel) throw new OperationCanceledException("Conversion operation canceled through editor dialogue"); } private void UpdateGenerateAssets() @@ -83,114 +207,75 @@ namespace Speckle.ConnectorUnity.Components.Editor speckleReceiver.Converter.AssetCache.nativeCaches = NativeCacheFactory.GetDefaultNativeCacheSetup(generateAssets); } - public async Task ReceiveAndConvert(SpeckleReceiver speckleReceiver) + private async Task ReceiveCommit(int progressId) { - speckleReceiver.CancellationTokenSource?.Cancel(); - if (!speckleReceiver.GetSelection(out Client? client, out _, out Commit? commit, out string? error)) - { - Debug.LogWarning($"Not ready to receive: {error}", speckleReceiver); - return null; - } - - Base? commitObject = await ReceiveCommit(speckleReceiver, client.ServerUrl).ConfigureAwait(true);; + var speckleReceiver = (SpeckleReceiver)target; - if (commitObject == null) return null; + string serverLogName = speckleReceiver.Account.Client?.ServerUrl ?? "Speckle"; - var gameObject = Convert(speckleReceiver, commitObject, commit.id); - Debug.Log($"Successfully received and converted commit: {commit.id}", target); - return gameObject; - } - - private GameObject Convert(SpeckleReceiver receiver, Base commitObject, string name) - { - //Convert Speckle Objects - int childrenConverted = 0; - float totalChildren = commitObject.totalChildrenCount; - - void BeforeConvertCallback(Base b) - { - //TODO: this is an incorrect way of measuring progress, as totalChildren != total convertable children - float progress = childrenConverted++ / totalChildren; - - EditorUtility.DisplayProgressBar("Converting To Native...", - $"{b.speckle_type} - {b.id}", - progress); - } - - var go = receiver.ConvertToNativeWithCategories(commitObject, - name, BeforeConvertCallback); - go.transform.SetParent(receiver.transform); - return go; - } - - private async Task ReceiveCommit(SpeckleReceiver speckleReceiver, string serverLogName) - { - string message = $"Receiving data from {serverLogName}..."; - EditorUtility.DisplayProgressBar(message, "", 0); + int transport = Progress.Start($"Downloading data from {serverLogName}", "Waiting...", Progress.Options.Sticky, progressId); + int deserialize = Progress.Start("Deserializing data", "Waiting...", Progress.Options.Sticky, progressId); + Progress.SetPriority(transport, Progress.Priority.High); var totalObjectCount = 1; void OnTotalChildrenKnown(int count) { totalObjectCount = count; - }; + Progress.Report(progressId, 0, totalObjectCount, "Receiving objects"); + } void OnProgress(ConcurrentDictionary dict) { - var currentProgress = dict.Values.Average(); - var progress = (float) currentProgress / totalObjectCount; - EditorApplication.delayCall += () => + bool r = dict.TryGetValue("RemoteTransport", out int rtProgress); + bool l = dict.TryGetValue("SQLite", out int ltProgress); + if (r || l) { - bool shouldCancel = EditorUtility.DisplayCancelableProgressBar(message, - $"{currentProgress}/{totalObjectCount}", - progress); - - if (shouldCancel) - { - CancelReceive(); - } - }; - }; - - void OnError(string message, Exception e) - { - if (e is not OperationCanceledException) - { - Debug.LogError($"Receive failed: {message}\n{e}", speckleReceiver); + var fetched = (rtProgress + ltProgress); + Progress.Report(transport, fetched, totalObjectCount, $"{fetched}/{totalObjectCount}"); } - CancelReceive(); - }; - Base? commitObject = null; + if (dict.TryGetValue("DS", out int tsProgress)) + { + tsProgress--; //The root object isn't included, so we add an extra 1 + Progress.Report(deserialize,tsProgress, totalObjectCount, $"{tsProgress}/{totalObjectCount}"); + Progress.Report(progressId,tsProgress, totalObjectCount); + } + } + + Base commitObject; try { speckleReceiver.OnTotalChildrenCountKnown.AddListener(OnTotalChildrenKnown); speckleReceiver.OnReceiveProgressAction.AddListener(OnProgress); - speckleReceiver.OnErrorAction.AddListener(OnError); - commitObject = await speckleReceiver.ReceiveAsync().ConfigureAwait(false);; - if (commitObject == null) - { - Debug.LogWarning($"Receive warning: Receive operation returned null", speckleReceiver); - } + commitObject = await speckleReceiver.ReceiveAsync(speckleReceiver.CancellationToken) + .ConfigureAwait(false); + Progress.Finish(transport); + Progress.Finish(deserialize); + } + catch(OperationCanceledException) + { + Progress.Finish(transport, Progress.Status.Canceled); + Progress.Finish(deserialize, Progress.Status.Canceled); + throw; + } + catch(Exception) + { + Progress.Finish(transport, Progress.Status.Failed); + Progress.Finish(deserialize, Progress.Status.Failed); + throw; } finally { speckleReceiver.OnTotalChildrenCountKnown.RemoveListener(OnTotalChildrenKnown); speckleReceiver.OnReceiveProgressAction.RemoveListener(OnProgress); - speckleReceiver.OnErrorAction.RemoveListener(OnError); - EditorApplication.delayCall += EditorUtility.ClearProgressBar; } return commitObject; } - private void CancelReceive() - { - ((SpeckleReceiver)target).CancellationTokenSource?.Cancel(); - EditorApplication.delayCall += EditorUtility.ClearProgressBar; - } - [MenuItem("GameObject/Speckle/Speckle Connector", false, 10)] - static void CreateCustomGameObject(MenuCommand menuCommand) { + static void CreateCustomGameObject(MenuCommand menuCommand) + { // Create a custom game object GameObject go = new GameObject("Speckle Connector"); // Ensure it gets reparented if this was a context click (otherwise does nothing) @@ -208,5 +293,6 @@ namespace Speckle.ConnectorUnity.Components.Editor EditorGUIUtility.SetIconForObject(go, icon); #endif } + } } diff --git a/Packages/systems.speckle.speckle-unity/Editor/Components/SpeckleSendEditor.cs b/Packages/systems.speckle.speckle-unity/Editor/Components/SpeckleSendEditor.cs index 1fb2af0..cec8754 100644 --- a/Packages/systems.speckle.speckle-unity/Editor/Components/SpeckleSendEditor.cs +++ b/Packages/systems.speckle.speckle-unity/Editor/Components/SpeckleSendEditor.cs @@ -106,12 +106,6 @@ namespace Speckle.ConnectorUnity.Components.Editor SceneManager.GetActiveScene().GetRootGameObjects(), go => go.activeInHierarchy); } - - private void CancelSend() - { - ((SpeckleReceiver)target).CancellationTokenSource?.Cancel(); - EditorApplication.delayCall += EditorUtility.ClearProgressBar; - } - + } } diff --git a/Packages/systems.speckle.speckle-unity/Editor/Wrappers/SpecklePropertiesEditor.cs b/Packages/systems.speckle.speckle-unity/Editor/Wrappers/SpecklePropertiesEditor.cs index f908f2a..cc929ef 100644 --- a/Packages/systems.speckle.speckle-unity/Editor/Wrappers/SpecklePropertiesEditor.cs +++ b/Packages/systems.speckle.speckle-unity/Editor/Wrappers/SpecklePropertiesEditor.cs @@ -34,12 +34,16 @@ namespace Speckle.ConnectorUnity.Wrappers.Editor .Where(x => x.FullName != null) .Select(x => x.FullName!.Replace('.', '/')); + var manualTypes = new [] { typeof(Base), typeof(Collection)}; + var manualStrings = new []{ nameof(Base), nameof(Collection)}; + + //Manually Add `Base` + SpeckleTypeOptions = options.Concat(manualTypes).ToArray(); + SpeckleTypeOptionStrings = strings.Concat(manualStrings).ToArray(); - SpeckleTypeOptionStrings = strings.Append(nameof(Base)).ToArray(); - SpeckleTypeOptions = options.Append(typeof(Base)).ToArray(); Debug.Assert(SpeckleTypeOptions.Length == SpeckleTypeOptionStrings.Length); } - + private static GUILayoutOption[] propLayoutOptions = { GUILayout.ExpandWidth(true) }; public override void OnInspectorGUI() diff --git a/Packages/systems.speckle.speckle-unity/Runtime/Components/Deprecated/Receiver.cs b/Packages/systems.speckle.speckle-unity/Runtime/Components/Deprecated/Receiver.cs index 708c036..639e97a 100644 --- a/Packages/systems.speckle.speckle-unity/Runtime/Components/Deprecated/Receiver.cs +++ b/Packages/systems.speckle.speckle-unity/Runtime/Components/Deprecated/Receiver.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using JetBrains.Annotations; using Sentry; using Speckle.ConnectorUnity.Components; +using Speckle.ConnectorUnity.Utils; using Speckle.Core.Kits; using UnityEngine; @@ -20,7 +21,8 @@ namespace Speckle.ConnectorUnity /// A Speckle Receiver, it's a wrapper around a basic Speckle Client /// that handles conversions and subscriptions for you /// - [RequireComponent( typeof( RecursiveConverter ) )] + [RequireComponent(typeof(RecursiveConverter))] + [Obsolete] public class Receiver : MonoBehaviour { public string StreamId; diff --git a/Packages/systems.speckle.speckle-unity/Runtime/Components/Deprecated/Sender.cs b/Packages/systems.speckle.speckle-unity/Runtime/Components/Deprecated/Sender.cs index 5b25a84..7124f15 100644 --- a/Packages/systems.speckle.speckle-unity/Runtime/Components/Deprecated/Sender.cs +++ b/Packages/systems.speckle.speckle-unity/Runtime/Components/Deprecated/Sender.cs @@ -21,6 +21,7 @@ namespace Speckle.ConnectorUnity /// that handles conversions for you /// [RequireComponent(typeof(RecursiveConverter)), ExecuteAlways] + [Obsolete] public class Sender : MonoBehaviour { @@ -146,4 +147,4 @@ namespace Speckle.ConnectorUnity #endregion } -} \ No newline at end of file +} diff --git a/Packages/systems.speckle.speckle-unity/Runtime/Components/RecursiveConverter.ToNative.cs b/Packages/systems.speckle.speckle-unity/Runtime/Components/RecursiveConverter.ToNative.cs index d5f676c..3e6454e 100644 --- a/Packages/systems.speckle.speckle-unity/Runtime/Components/RecursiveConverter.ToNative.cs +++ b/Packages/systems.speckle.speckle-unity/Runtime/Components/RecursiveConverter.ToNative.cs @@ -2,62 +2,225 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; -using Speckle.ConnectorUnity.NativeCache; +using Speckle.ConnectorUnity.Utils; +using Speckle.Core.Logging; using Speckle.Core.Models; using Speckle.Core.Models.GraphTraversal; using UnityEngine; namespace Speckle.ConnectorUnity.Components { + + /// + /// Struct that encapsulates the result of a ToNative conversion of a single Speckle Object () + /// + public readonly struct ConversionResult + { + /// + /// The context that was converted ToNative + /// + public readonly TraversalContext traversalContext; + + /// + /// The result of conversion a successful conversion + /// + public readonly GameObject? converted; + + /// + /// The result of conversion a failed conversion + /// + public readonly Exception? exception; + + /// + /// Constructor used for Successful conversions + /// + /// The current traversal context + /// The resultant ToNative conversion of context object + /// + public ConversionResult(TraversalContext traversalContext, [NotNull] GameObject? converted) + : this(traversalContext, converted, null) + { + if (converted is null) throw new ArgumentNullException(nameof(converted)); + } + + /// + /// Constructor used for Failed conversions + /// + /// The current conversion + /// The operation halting exception that occured + /// Optional converted GameObject + /// + public ConversionResult(TraversalContext traversalContext, [NotNull] Exception? exception, + GameObject? converted = null) + : this(traversalContext, converted, exception) + { + if (exception is null) throw new ArgumentNullException(nameof(exception)); + } + + private ConversionResult(TraversalContext traversalContext, GameObject? converted, Exception? exception) + { + this.traversalContext = traversalContext; + this.converted = converted; + this.exception = exception; + } + + /// + /// + /// + /// The converted + /// The that occured during conversion + /// True if the conversion was successful + public bool WasSuccessful( + [NotNullWhen(true)] out GameObject? converted, + [NotNullWhen(false)] out Exception? exception) + { + converted = this.converted; + exception = this.exception; + return WasSuccessful(); + } + + public bool WasSuccessful() => this.exception == null; + + public Base SpeckleObject => traversalContext.current; + } + public partial class RecursiveConverter { - public IEnumerator ConvertToNative(Base rootObject, Transform? parent, IDictionary outCreatedObjects) + /// + /// Calling this function will perform the conversion process synchronously + /// The conversion result + public List RecursivelyConvertToNative_Sync( + Base rootObject, + Transform? parent, + Predicate? predicate = null + ) { - - InitializeAssetCache(); - var traversalFunc = DefaultTraversal.CreateBIMTraverseFunc(ConverterInstance); - - var convertableObjects = traversalFunc.Traverse(rootObject) - .Where(tc => ConverterInstance.CanConvertToNative(tc.current)); - - foreach (TraversalContext tc in convertableObjects) - { - Transform? nativeParent = parent; - if (tc.parent != null && outCreatedObjects.TryGetValue(tc.parent.current, out GameObject p)) - nativeParent = p.transform; - - GameObject? go = ConverterInstance.ConvertToNative(tc.current) as GameObject; - if (go == null) continue; - - go.transform.SetParent(parent, true); - outCreatedObjects.Add(tc.current, go); - - //Set some common for all created GameObjects - //TODO add support for more unity specific props - if(go.name == "New Game Object" || string.IsNullOrWhiteSpace(go.name)) - go.name = AssetHelpers.GenerateObjectName(tc.current); - //if (baseObject["tag"] is string t) go.tag = t; - if (tc.current["physicsLayer"] is string layerName) - { - int layer = LayerMask.NameToLayer(layerName); //TODO: check how this can be interoperable with Unreal and Blender - if (layer > -1) go.layer = layer; - } - //if (baseObject["isStatic"] is bool isStatic) go.isStatic = isStatic; - - yield return go; - } + return RecursivelyConvertToNative_Enumerable(rootObject, parent, predicate).ToList(); } + /// + /// Calling this function will start a coroutine to complete later on the coroutine loop + /// The started Coroutine + public Coroutine RecursivelyConvertToNative_Coroutine( + Base rootObject, + Transform? parent, + Predicate? predicate = null + ) + { + return StartCoroutine(RecursivelyConvertToNative_Enumerable(rootObject, parent, predicate).GetEnumerator()); + } + /// + /// Will recursively traverse the given and convert convertable child objects + /// where the given + /// + /// The Speckle object to traverse and convert all convertable children + /// Optional parent for the created root s + /// A filter function to allow for selectively excluding certain objects from being converted + /// An unevaluated of all created s + public IEnumerable RecursivelyConvertToNative_Enumerable( + Base rootObject, + Transform? parent, + Predicate? predicate = null) + { + var userPredicate = predicate ?? (_ => true); + + var traversalFunc = DefaultTraversal.CreateBIMTraverseFunc(ConverterInstance); + + var objectsToConvert = traversalFunc + .Traverse(rootObject) + .Where(x => ConverterInstance.CanConvertToNative(x.current)) + .Where(x => userPredicate(x)); + + Dictionary created = new(); + foreach (var conversionResult in ConvertTree(objectsToConvert, parent, created)) + { + if (!isActiveAndEnabled) throw new InvalidOperationException($"Cannot convert objects while {GetType()} is disabled"); + + yield return conversionResult; + } + } + + /// + /// Converts a objectTree (see ) to unevaluated enumerable. + /// As this enumerable is iterated through, each context object will be converted to (if successful) + /// or if not. + /// + /// + /// You may enumerate over multiple frames (e.g. coroutine) but you must ensure the output eventually gets fully enumerated (exactly once) + /// + /// + /// + /// + /// + protected IEnumerable ConvertTree(IEnumerable objectTree, Transform? parent, IDictionary outCreatedObjects) + { + InitializeAssetCache(); + AssetCache.BeginWrite(); + + foreach (TraversalContext tc in objectTree) + { + ConversionResult result; + try + { + Transform? currentParent = GetParent(tc, outCreatedObjects) ?? parent; + + var converted = ConvertToNative(tc.current, currentParent); + result = new ConversionResult(tc, converted); + outCreatedObjects.TryAdd(tc.current, result.converted); + } + catch (Exception ex) + { + result = new ConversionResult(tc, ex); + } + + yield return result; + } + + AssetCache.FinishWrite(); + } + protected static Transform? GetParent(TraversalContext? tc, IDictionary createdObjects) + { + if (tc == null) return null; //We've reached the root object, and still not found a converted parent + + if(createdObjects.TryGetValue(tc.current, out GameObject? p) && p != null) + return p.transform; + + //Go one level up, and repeat! + return GetParent(tc.parent, createdObjects); + } + protected GameObject ConvertToNative(Base speckleObject, Transform? parentTransform) + { + GameObject? go = ConverterInstance.ConvertToNative(speckleObject) as GameObject; + if (go == null) throw new SpeckleException("Conversion Returned Null"); + + go.transform.SetParent(parentTransform, true); + + //Set some common for all created GameObjects + //TODO add support for more unity specific props + if(go.name == "New Game Object" || string.IsNullOrWhiteSpace(go.name)) + go.name = CoreUtils.GenerateObjectName(speckleObject); + if (speckleObject["physicsLayer"] is string layerName) + { + int layer = LayerMask.NameToLayer(layerName); //TODO: check how this can be interoperable with Unreal and Blender + if (layer > -1) go.layer = layer; + } + //if (baseObject["tag"] is string t) go.tag = t; + //if (baseObject["isStatic"] is bool isStatic) go.isStatic = isStatic; + return go; + } - + #region deprecated conversion functions + [Obsolete("Use " + nameof(RecursivelyConvertToNative_Coroutine))] public IEnumerator ConvertCoroutine(Base rootObject, Transform? parent, List outCreatedObjects) => ConvertCoroutine(rootObject, parent, outCreatedObjects,b => ConverterInstance.CanConvertToNative(b)); + [Obsolete("Use " + nameof(RecursivelyConvertToNative_Coroutine))] public IEnumerator ConvertCoroutine(Base rootObject, Transform? parent, List outCreatedObjects, Func predicate) { foreach (string propertyName in GetPotentialChildren(rootObject)) @@ -74,11 +237,13 @@ namespace Speckle.ConnectorUnity.Components /// The object to convert ( or of) /// Optional parent transform for the created root s /// A list of all created s + [Obsolete("Use " + nameof(RecursivelyConvertToNative_Sync))] public virtual List RecursivelyConvertToNative(object? o, Transform? parent) => RecursivelyConvertToNative(o, parent, b => ConverterInstance.CanConvertToNative(b)); /// /// A function to determine if an object should be converted + [Obsolete("Use " + nameof(RecursivelyConvertToNative_Sync))] public virtual List RecursivelyConvertToNative(object? o, Transform? parent, Func predicate) { InitializeAssetCache(); @@ -111,6 +276,7 @@ namespace Speckle.ConnectorUnity.Components ConverterInstance.SetContextDocument(AssetCache); } + [Obsolete] public virtual void RecurseTreeToNative(Base baseObject, Transform? parent, Func predicate, IList outCreatedObjects) { object? converted = null; @@ -129,7 +295,7 @@ namespace Speckle.ConnectorUnity.Components //Set some common for all created GameObjects //TODO add support for more unity specific props if(go.name == "New Game Object" || string.IsNullOrWhiteSpace(go.name)) - go.name = AssetHelpers.GenerateObjectName(baseObject); + go.name = CoreUtils.GenerateObjectName(baseObject); //if (baseObject["tag"] is string t) go.tag = t; if (baseObject["physicsLayer"] is string layerName) { @@ -150,6 +316,7 @@ namespace Speckle.ConnectorUnity.Components } + [Obsolete] private IEnumerable GetPotentialChildren(Base baseObject) { return ConverterInstance.CanConvertToNative(baseObject) @@ -157,7 +324,7 @@ namespace Speckle.ConnectorUnity.Components : baseObject.GetMembers().Keys; } - + [Obsolete] protected virtual void ConvertChild(object? value, Transform? parent, Func predicate, IList outCreatedObjects) { foreach (Base b in GraphTraversal.TraverseMember(value)) @@ -165,14 +332,6 @@ namespace Speckle.ConnectorUnity.Components RecurseTreeToNative(b, parent, predicate, outCreatedObjects); } } - - [Obsolete("Use " + nameof(RecursivelyConvertToNative), true)] - public GameObject ConvertRecursivelyToNative(Base @base, string name) - { - var parentObject = new GameObject(name); - RecursivelyConvertToNative(@base, parentObject.transform); - return parentObject; - } - + #endregion } -} \ No newline at end of file +} diff --git a/Packages/systems.speckle.speckle-unity/Runtime/Components/RecursiveConverter.cs b/Packages/systems.speckle.speckle-unity/Runtime/Components/RecursiveConverter.cs index 176e801..72cd688 100644 --- a/Packages/systems.speckle.speckle-unity/Runtime/Components/RecursiveConverter.cs +++ b/Packages/systems.speckle.speckle-unity/Runtime/Components/RecursiveConverter.cs @@ -1,5 +1,6 @@ using Speckle.ConnectorUnity.Factories; using Speckle.ConnectorUnity.NativeCache; +using Speckle.ConnectorUnity.Utils; using Speckle.Core.Kits; using Speckle.Core.Logging; using UnityEngine; @@ -18,7 +19,12 @@ namespace Speckle.ConnectorUnity.Components [field: SerializeField] public AggregateNativeCache AssetCache { get; set; } - private void Awake() + protected void Awake() + { + Init(); + } + + protected void Init() { Setup.Init(HostApplications.Unity.GetVersion(CoreUtils.GetHostAppVersion()), HostApplications.Unity.Slug); @@ -30,4 +36,4 @@ namespace Speckle.ConnectorUnity.Components } } } -} \ No newline at end of file +} diff --git a/Packages/systems.speckle.speckle-unity/Runtime/Components/SpeckleReceiver.cs b/Packages/systems.speckle.speckle-unity/Runtime/Components/SpeckleReceiver.cs index 2fc51f9..a569e3f 100644 --- a/Packages/systems.speckle.speckle-unity/Runtime/Components/SpeckleReceiver.cs +++ b/Packages/systems.speckle.speckle-unity/Runtime/Components/SpeckleReceiver.cs @@ -6,12 +6,14 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; +using Speckle.ConnectorUnity.Utils; using Speckle.ConnectorUnity.Wrappers.Selection; using Speckle.Core.Api; using Speckle.Core.Credentials; using Speckle.Core.Kits; using Speckle.Core.Logging; using Speckle.Core.Models; +using Speckle.Core.Models.GraphTraversal; using Speckle.Core.Transports; using UnityEngine; using UnityEngine.Events; @@ -38,139 +40,257 @@ namespace Speckle.ConnectorUnity.Components public RecursiveConverter Converter { get; private set; } +#nullable enable [Header("Events")] [HideInInspector] - public CommitSelectionEvent OnCommitSelectionChange; + public CommitSelectionEvent OnCommitSelectionChange = new(); [HideInInspector] - public OperationProgressEvent OnReceiveProgressAction; + public OperationProgressEvent OnReceiveProgressAction = new(); [HideInInspector] - public ErrorActionEvent OnErrorAction; + public ErrorActionEvent OnErrorAction = new(); [HideInInspector] - public ChildrenCountHandler OnTotalChildrenCountKnown; + public ChildrenCountHandler OnTotalChildrenCountKnown = new(); [HideInInspector] - public ReceiveCompleteHandler OnComplete; + public ReceiveCompleteHandler OnComplete = new(); -#nullable enable - protected internal CancellationTokenSource? CancellationTokenSource { get; private set; } + protected CancellationTokenSource? CancellationTokenSource { get; private set; } + public CancellationToken CancellationToken => CancellationTokenSource?.Token ?? default; + public bool IsReceiving => CancellationTokenSource != null; - //TODO runtime receiving - public IEnumerator ReceiveAndConvertRoutine(SpeckleReceiver speckleReceiver, string rootObjectName, Action? beforeConvertCallback = null) + /// + /// Cancels any current receive operations + /// + /// + /// Note, this does not cancel any currently executing ConvertToNative, just the . + /// + /// if the cancellation request was made. if there was no pending operation to cancel (see ) + public bool Cancel() { - Task receiveOperation = Task.Run(ReceiveAsync); + if (CancellationTokenSource == null) return false; + CancellationTokenSource.Cancel(); + return true; + } + + /// + /// Receive the selected object, and converts ToNative as children of + /// + /// Optional parent for the created root s + /// A filter function to allow for selectively excluding certain objects from being converted + /// function does not throw, instead calls , and calls upon completion + /// + /// + public IEnumerator ReceiveAndConvert_Routine(Transform? parent, Predicate? predicate = null) + { + if (IsReceiving) + { + OnErrorAction.Invoke("Failed to receive", new InvalidOperationException("A pending receive operation has already started")); + yield break; + } + + CancellationTokenSource?.Dispose(); + CancellationTokenSource = new(); + + // ReSharper disable once MethodSupportsCancellation + Task receiveOperation = Task.Run(async () => + { + Base result = await ReceiveAsync(CancellationToken); + CancellationToken.ThrowIfCancellationRequested(); + return result; + }); yield return new WaitUntil(() => receiveOperation.IsCompleted); - Base? b = receiveOperation.Result; - if (b == null) yield break; + if (receiveOperation.IsFaulted) + { + OnErrorAction.Invoke("Failed to receive", receiveOperation.Exception); + FinishOperation(); + yield break; + } + + Base b = receiveOperation.Result; - //TODO make routine break for each catergory/object - GameObject go = ConvertToNativeWithCategories(b, rootObjectName, beforeConvertCallback); - OnComplete.Invoke(go); + foreach (var _ in Converter.RecursivelyConvertToNative_Enumerable(b, parent, predicate)) + { + yield return null; + } + + OnComplete.Invoke(parent); + FinishOperation(); } + /// + public async void ReceiveAndConvert_Async(Transform? parent, Predicate? predicate = null) + { + try + { + BeginOperation(); + Base commitObject = await ReceiveAsync(CancellationToken).ConfigureAwait(true); + Converter.RecursivelyConvertToNative_Sync(commitObject, parent, predicate); + OnComplete.Invoke(parent); + } + catch (Exception ex) + { + OnErrorAction.Invoke("Failed to receive", ex); + } + finally + { + FinishOperation(); + } + } + /// /// Receives the selected commit object using async Task /// /// Awaitable commit object + /// /// thrown when selection is incomplete - public async Task ReceiveAsync() + /// + /// This function is safe to call concurrently from any threads. + /// For this reason we use parameter, rather than use the property + ///
+ /// Additionally, and won't be called. + ///
+ public async Task ReceiveAsync(CancellationToken cancellationToken) { - CancellationTokenSource?.Cancel(); - CancellationTokenSource?.Dispose(); - CancellationTokenSource = new CancellationTokenSource(); - if(!GetSelection(out Client? client, out Stream? stream, out Commit? commit, out string? error)) - throw new SpeckleException(error); - - return await ReceiveAsync( - token: CancellationTokenSource.Token, - client: client, - streamId: stream.id, - objectId: commit.referencedObject, - commit: commit, - onProgressAction: dict => OnReceiveProgressAction.Invoke(dict), - onErrorAction: (m, e) => OnErrorAction.Invoke(m, e), - onTotalChildrenCountKnown: c => OnTotalChildrenCountKnown.Invoke(c) - ).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + + ValidateSelection(out Client? client, out Stream? stream, out Commit? commit); + + Base result = await ReceiveAsync( + client: client, + streamId: stream.id, + objectId: commit.referencedObject, + commit: commit, + onProgressAction: dict => OnReceiveProgressAction.Invoke(dict), + onTotalChildrenCountKnown: c => OnTotalChildrenCountKnown.Invoke(c), + cancellationToken: cancellationToken + ) + .ConfigureAwait(false); + + return result; + } + + public void ValidateSelection(out Client client, out Stream stream, out Commit commit) + { + Client? selectedClient = Account.Client; + client = selectedClient ?? throw new InvalidOperationException("Invalid account selection"); + + Stream? selectedStream = Stream.Selected; + stream = selectedStream ?? throw new InvalidOperationException("Invalid stream selection"); + + Commit? selectedCommit = Commit.Selected; + commit = selectedCommit ?? throw new InvalidOperationException("Invalid commit selection"); } + /// + /// Starts a new receive operation with a + /// + /// already receiving + protected internal CancellationToken BeginOperation() + { + if (IsReceiving) throw new InvalidOperationException("A pending receive operation has already started"); + + CancellationTokenSource?.Dispose(); + CancellationTokenSource = new(); + + return CancellationTokenSource.Token; + } + + protected internal void FinishOperation() + { + if (!IsReceiving) throw new InvalidOperationException("No pending operations to finish"); + + CancellationTokenSource!.Dispose(); + CancellationTokenSource = null; + } + /// /// Receives the requested using async Task /// - /// /// /// /// /// /// - /// /// + /// + /// Throws various types of exceptions to indicate faliure /// - public static async Task ReceiveAsync(CancellationToken token, + public static async Task ReceiveAsync( Client client, string streamId, string objectId, Commit? commit, Action>? onProgressAction = null, - Action? onErrorAction = null, - Action? onTotalChildrenCountKnown = null) + Action? onTotalChildrenCountKnown = null, + CancellationToken cancellationToken = default) { using var transport = new ServerTransportV2(client.Account, streamId); - transport.CancellationToken = token; - - Base? ret = null; + transport.CancellationToken = cancellationToken; + + cancellationToken.ThrowIfCancellationRequested(); + + Base? requestedObject = await Operations.Receive( + objectId: objectId, + cancellationToken: cancellationToken, + remoteTransport: transport, + onProgressAction: onProgressAction, + onErrorAction: (s, ex) => + { + //Don't wrap cancellation exceptions! + if (ex is OperationCanceledException) + throw ex; + + //HACK: Sometimes, the task was cancelled, and Operations.Receive doesn't fail in a reliable way. In this case, the exception is often simply a symptom of a cancel. + if (cancellationToken.IsCancellationRequested) + { + SpeckleLog.Logger.Warning(ex, "A task was cancelled, ignoring potentially symptomatic exception"); + cancellationToken.ThrowIfCancellationRequested(); + } + + //Treat all operation errors as fatal + throw new SpeckleException($"Failed to receive requested object {objectId} from server: {s}", ex); + }, + onTotalChildrenCountKnown: onTotalChildrenCountKnown, + disposeTransports: false + ).ConfigureAwait(false); + + Analytics.TrackEvent(client.Account, Analytics.Events.Receive, new Dictionary() + { + {"mode", nameof(SpeckleReceiver)}, + {"sourceHostApp", HostApplications.GetHostAppFromString(commit?.sourceApplication).Slug}, + {"sourceHostAppVersion", commit?.sourceApplication ?? ""}, + {"hostPlatform", Application.platform.ToString()}, + {"isMultiplayer", commit != null && commit.authorId != client.Account.userInfo.id}, + }); + + if (requestedObject == null) + throw new SpeckleException($"Operation {nameof(Operations.Receive)} returned null"); + + cancellationToken.ThrowIfCancellationRequested(); + + //Read receipt try { - - token.ThrowIfCancellationRequested(); - - ret = await Operations.Receive( - objectId: objectId, - cancellationToken: token, - remoteTransport: transport, - onProgressAction: onProgressAction, - onErrorAction: onErrorAction, - onTotalChildrenCountKnown: onTotalChildrenCountKnown, - disposeTransports: false - ).ConfigureAwait(false); - - Analytics.TrackEvent(client.Account, Analytics.Events.Receive, new Dictionary() + await client.CommitReceived(cancellationToken, new CommitReceivedInput { - {"mode", nameof(SpeckleReceiver)}, - {"sourceHostApp", HostApplications.GetHostAppFromString(commit?.sourceApplication).Slug}, - {"sourceHostAppVersion", commit?.sourceApplication ?? ""}, - {"hostPlatform", Application.platform.ToString()}, - {"isMultiplayer", commit != null && commit.authorId != client.Account.userInfo.id}, - }); - - token.ThrowIfCancellationRequested(); - - //Read receipt - try - { - await client.CommitReceived(token, new CommitReceivedInput - { - streamId = streamId, - commitId = commit?.id, - message = $"received commit from {Application.unityVersion}", - sourceApplication = HostApplications.Unity.GetVersion(CoreUtils.GetHostAppVersion()) - }).ConfigureAwait(false); - } - catch (Exception e) - { - // Do nothing! - Debug.LogWarning($"Failed to send read receipt\n{e}"); - } + streamId = streamId, + commitId = commit?.id, + message = $"received commit from {Application.unityVersion}", + sourceApplication = HostApplications.Unity.GetVersion(CoreUtils.GetHostAppVersion()) + }).ConfigureAwait(false); } catch (Exception e) { - onErrorAction?.Invoke(e.Message, e); + // Do nothing! + Debug.LogWarning($"Failed to send read receipt\n{e}"); } - return ret; + return requestedObject; } - + /// /// Helper method for using . /// Creates blank GameObjects for each property/category of the root object. @@ -179,6 +299,7 @@ namespace Speckle.ConnectorUnity.Components /// The name of the parent to create /// Callback for each object converted /// The created parent + [Obsolete("Use " + nameof(RecursiveConverter) + " Now we have implemented support for " + nameof(Collection) + "s, receiving any collection is now the default behaviour")] public GameObject ConvertToNativeWithCategories(Base @base, string rootObjectName, Action? beforeConvertCallback) { @@ -211,7 +332,7 @@ namespace Speckle.ConnectorUnity.Components return rootObject; } - + /// /// /// @@ -220,6 +341,7 @@ namespace Speckle.ConnectorUnity.Components /// /// error messages for /// true if selection is complete, as we are ready to receive + [Obsolete("Use " + nameof(ValidateSelection))] public bool GetSelection( [NotNullWhen(true)] out Client? client, [NotNullWhen(true)] out Stream? stream, @@ -258,22 +380,21 @@ namespace Speckle.ConnectorUnity.Components /// /// when , will fetch 360 degree preview image /// Callback function to be called when the web request completes - /// if , , or was - public bool GetPreviewImage(/*bool allAngles,*/ Action callback) + /// The executing or if , , or was + public Coroutine? GetPreviewImage(/*bool allAngles,*/ Action callback) { Account? account = Account.Selected; - if (account == null) return false; + if (account == null) return null; string? streamId = Stream.Selected?.id; - if (streamId == null) return false; + if (streamId == null) return null; string? commitId = Commit.Selected?.id; - if (commitId == null) return false; + if (commitId == null) return null; string angles = /*allAngles ? "all" :*/ ""; string url = $"{account.serverInfo.url}/preview/{streamId}/commits/{commitId}/{angles}"; string authToken = account.token; - StartCoroutine(Utils.Utils.GetImageRoutine(url, authToken, callback)); - return true; + return StartCoroutine(Utils.Utils.GetImageRoutine(url, authToken, callback)); } #if UNITY_EDITOR @@ -284,6 +405,8 @@ namespace Speckle.ConnectorUnity.Components Application.OpenURL(url); } #endif + + public string GetSelectedUrl() { string serverUrl = Account.Selected!.serverInfo.url; @@ -319,12 +442,16 @@ namespace Speckle.ConnectorUnity.Components Account.RefreshOptions(); } - public void OnDestroy() + public void OnDisable() { CancellationTokenSource?.Cancel(); + } + + public void OnDestroy() + { CancellationTokenSource?.Dispose(); } - + public void OnBeforeSerialize() { //pass @@ -333,12 +460,32 @@ namespace Speckle.ConnectorUnity.Components { Initialise(); } + + #region Deprecated members + + [Obsolete("use " + nameof(ReceiveAndConvertRoutine), true)] + public IEnumerator ReceiveAndConvertRoutine(SpeckleReceiver speckleReceiver, string rootObjectName, Action? beforeConvertCallback = null) + { + // ReSharper disable once MethodSupportsCancellation + Task receiveOperation = Task.Run(async () => await ReceiveAsync(CancellationToken)); + + yield return new WaitUntil(() => receiveOperation.IsCompleted); + + Base? b = receiveOperation.Result; + if (b == null) yield break; + + //NOTE: coroutine doesn't break for each catergory/object + GameObject go = ConvertToNativeWithCategories(b, rootObjectName, beforeConvertCallback); + + } + + #endregion } [Serializable] public sealed class CommitSelectionEvent : UnityEvent { } [Serializable] public sealed class BranchSelectionEvent : UnityEvent { } [Serializable] public sealed class ErrorActionEvent : UnityEvent { } [Serializable] public sealed class OperationProgressEvent : UnityEvent> { } - [Serializable] public sealed class ReceiveCompleteHandler : UnityEvent { } + [Serializable] public sealed class ReceiveCompleteHandler : UnityEvent { } [Serializable] public sealed class ChildrenCountHandler : UnityEvent { } } diff --git a/Packages/systems.speckle.speckle-unity/Runtime/Components/SpeckleSender.cs b/Packages/systems.speckle.speckle-unity/Runtime/Components/SpeckleSender.cs index 18b4be2..264e760 100644 --- a/Packages/systems.speckle.speckle-unity/Runtime/Components/SpeckleSender.cs +++ b/Packages/systems.speckle.speckle-unity/Runtime/Components/SpeckleSender.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; +using Speckle.ConnectorUnity.Utils; using Speckle.ConnectorUnity.Wrappers.Selection; using Speckle.Core.Api; using Speckle.Core.Credentials; diff --git a/Packages/systems.speckle.speckle-unity/Runtime/Converter/Unity/ConverterUnity.Geometry.cs b/Packages/systems.speckle.speckle-unity/Runtime/Converter/Unity/ConverterUnity.Geometry.cs index 19c4a9a..a885538 100644 --- a/Packages/systems.speckle.speckle-unity/Runtime/Converter/Unity/ConverterUnity.Geometry.cs +++ b/Packages/systems.speckle.speckle-unity/Runtime/Converter/Unity/ConverterUnity.Geometry.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using Objects.Other; +using Speckle.ConnectorUnity; using Speckle.ConnectorUnity.NativeCache; using Speckle.ConnectorUnity.Utils; using Speckle.ConnectorUnity.Wrappers; @@ -212,15 +213,11 @@ namespace Objects.Converter.Unity return sobject; } - public GameObject? InstanceToNative(Instance instance) + public GameObject InstanceToNative(Instance instance) { - if (instance.definition == null) - { - Debug.Log($"Skipping {typeof(BlockInstance)} {instance.id}, block definition was null"); - return null; - } + if (instance.definition == null) throw new ArgumentException("Definition was null", nameof(instance)); - var defName = instance.definition["name"] as string ?? ""; + var defName = CoreUtils.GenerateObjectName(instance.definition); // Check for existing converted object if(LoadedAssets.TryGetObject(instance.definition, out GameObject? existingGo)) { @@ -231,15 +228,19 @@ namespace Objects.Converter.Unity } // Convert the block definition - GameObject native = new GameObject(defName); + GameObject native = new(defName); List meshes = new(); List others = new(); var geometry = instance.definition is BlockDefinition b ? b.geometry - : GraphTraversal.TraverseMember(instance.definition["elements"]); - + : GraphTraversal.TraverseMember(new[] + { + instance.definition["elements"] ?? instance.definition["@elements"], + instance.definition["displayValue"] ?? instance.definition["@displayValue"], + }); + foreach (Base geo in geometry) { if (geo is SMesh m) meshes.Add(m); @@ -252,7 +253,7 @@ namespace Objects.Converter.Unity if(!TryGetMeshFromCache(instance.definition, meshes, out Mesh? nativeMesh, out _)) { MeshToNativeMesh(meshes, out nativeMesh); - string name = AssetHelpers.GenerateObjectName(instance.definition); + string name = CoreUtils.GenerateObjectName(instance.definition); nativeMesh.name = name; LoadedAssets.TrySaveObject(instance.definition, nativeMesh); } @@ -269,8 +270,14 @@ namespace Objects.Converter.Unity LoadedAssets.TrySaveObject(instance.definition, native); + TransformToNativeTransform(native.transform, instance.transform); - if (instance["name"] is string instanceName) native.name = instanceName; + + var instanceName = CoreUtils.GetFriendlyObjectName(instance) != null + ? CoreUtils.GenerateObjectName(instance) + : defName; + + native.name = instanceName; return native; } diff --git a/Packages/systems.speckle.speckle-unity/Runtime/Converter/Unity/ConverterUnity.Mesh.cs b/Packages/systems.speckle.speckle-unity/Runtime/Converter/Unity/ConverterUnity.Mesh.cs index 4c15d03..000f2d0 100644 --- a/Packages/systems.speckle.speckle-unity/Runtime/Converter/Unity/ConverterUnity.Mesh.cs +++ b/Packages/systems.speckle.speckle-unity/Runtime/Converter/Unity/ConverterUnity.Mesh.cs @@ -6,6 +6,7 @@ using Speckle.ConnectorUnity.Utils; using Objects.Other; using Objects.Utils; using Speckle.ConnectorUnity.NativeCache; +using Speckle.Core.Logging; using Speckle.Core.Models; using UnityEngine; using UnityEngine.Rendering; @@ -209,13 +210,9 @@ namespace Objects.Converter.Unity /// The object being converted /// Collection of es that shall be converted /// A with the converted , , and - public GameObject? MeshesToNative(Base element, IReadOnlyCollection meshes) + public GameObject MeshesToNative(Base element, IReadOnlyCollection meshes) { - if (!meshes.Any()) - { - Debug.Log($"Skipping {element.GetType()} {element.id}, zero {typeof(SMesh)} provided"); - return null; - } + if (!meshes.Any()) throw new ArgumentException("Expected at least one Mesh", nameof(meshes)); Material[] nativeMaterials = RenderMaterialsToNative(meshes); @@ -223,7 +220,7 @@ namespace Objects.Converter.Unity { //Convert a new one MeshToNativeMesh(meshes, out nativeMesh, out center); - string name = AssetHelpers.GenerateObjectName(element); + string name = CoreUtils.GenerateObjectName(element); nativeMesh.name = name; LoadedAssets.TrySaveObject(element, nativeMesh); } @@ -241,15 +238,12 @@ namespace Objects.Converter.Unity /// /// Mesh to convert /// - public GameObject? MeshToNative(SMesh speckleMesh) + public GameObject MeshToNative(SMesh speckleMesh) { if (speckleMesh.vertices.Count == 0 || speckleMesh.faces.Count == 0) - { - Debug.Log($"Skipping mesh {speckleMesh.id}, mesh data was empty"); - return null; - } - - GameObject? converted = MeshesToNative(speckleMesh, new[] {speckleMesh}); + throw new ArgumentException("mesh data was empty", nameof(speckleMesh)); + + GameObject converted = MeshesToNative(speckleMesh, new[] {speckleMesh}); // Raw meshes shouldn't have dynamic props to attach //if (converted != null) AttachSpeckleProperties(converted,speckleMesh.GetType(), GetProperties(speckleMesh, typeof(Mesh))); @@ -483,7 +477,7 @@ namespace Objects.Converter.Unity var c = renderMaterial.diffuse.ToUnityColor(); mat.color = new Color(c.r, c.g, c.b, (float) renderMaterial.opacity); - mat.name = AssetHelpers.GenerateObjectName(renderMaterial); + mat.name = CoreUtils.GenerateObjectName(renderMaterial); mat.SetFloat(Metallic, (float) renderMaterial.metalness); mat.SetFloat(Glossiness, 1 - (float) renderMaterial.roughness); if (renderMaterial.emissive != SColor.Black.ToArgb()) mat.EnableKeyword("_EMISSION"); diff --git a/Packages/systems.speckle.speckle-unity/Runtime/Converter/Unity/ConverterUnity.cs b/Packages/systems.speckle.speckle-unity/Runtime/Converter/Unity/ConverterUnity.cs index d773194..0cc8f38 100644 --- a/Packages/systems.speckle.speckle-unity/Runtime/Converter/Unity/ConverterUnity.cs +++ b/Packages/systems.speckle.speckle-unity/Runtime/Converter/Unity/ConverterUnity.cs @@ -5,6 +5,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; using Objects.BuiltElements; +using Objects.Organization; using Objects.Other; using Speckle.ConnectorUnity.Utils; using Speckle.ConnectorUnity.NativeCache; @@ -111,8 +112,10 @@ namespace Objects.Converter.Unity return View3DToNative(v); case Mesh o: return MeshToNative(o); - case BlockInstance o: + case Instance o: return InstanceToNative(o); + case Collection c: + return CollectionToNative(c); default: //Object is not a raw geometry, convert it as display value element @@ -183,6 +186,19 @@ namespace Objects.Converter.Unity } } + public GameObject CollectionToNative(Collection collection) + { + var name = collection.name ?? $"{collection.collectionType} -- {collection.applicationId ?? collection.id}"; + var go = new GameObject(name); + AttachSpeckleProperties(go, collection.GetType(), GetProperties(collection)); + if (name == "Rooms") + { + go.SetActive(false); + } + + return go; + } + public bool CanConvertToNative(Base @object) { switch (@object) @@ -195,13 +211,17 @@ namespace Objects.Converter.Unity // return true; // case Curve _: // return true; - // case View3D _: + // case View2D: + // return false; + // case View _: // return true; - case View2D _: - return false; - case Mesh _: + case Model: + return false; //This allows us to traverse older commits pre-collections + case Collection: return true; - case BlockInstance _: + case Mesh: + return true; + case Instance: return true; default: @@ -212,9 +232,9 @@ namespace Objects.Converter.Unity if (@object[alias] is IList) return true; } - + return false; } } } -} \ No newline at end of file +} diff --git a/Packages/systems.speckle.speckle-unity/Runtime/Dispatcher.cs b/Packages/systems.speckle.speckle-unity/Runtime/Dispatcher.cs index e3b1e94..d206e75 100644 --- a/Packages/systems.speckle.speckle-unity/Runtime/Dispatcher.cs +++ b/Packages/systems.speckle.speckle-unity/Runtime/Dispatcher.cs @@ -15,6 +15,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.Threading.Tasks; +using Speckle.ConnectorUnity.Utils; using Speckle.Core.Kits; using Speckle.Core.Logging; using UnityEngine; diff --git a/Packages/systems.speckle.speckle-unity/Runtime/NativeCache/AbstractNativeCache.cs b/Packages/systems.speckle.speckle-unity/Runtime/NativeCache/AbstractNativeCache.cs index 34f4d55..c7365b8 100644 --- a/Packages/systems.speckle.speckle-unity/Runtime/NativeCache/AbstractNativeCache.cs +++ b/Packages/systems.speckle.speckle-unity/Runtime/NativeCache/AbstractNativeCache.cs @@ -1,15 +1,16 @@ +#nullable enable using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; +using Speckle.ConnectorUnity.Utils; using Speckle.Core.Models; using UnityEngine; using Object = UnityEngine.Object; namespace Speckle.ConnectorUnity.NativeCache { - #nullable enable [ExecuteAlways] public abstract class AbstractNativeCache : ScriptableObject { @@ -69,38 +70,12 @@ namespace Speckle.ConnectorUnity.NativeCache public static string GetAssetName(Base speckleObject, Type nativeType) { string suffix = GetAssetSuffix(nativeType); - string name = GenerateObjectName(speckleObject); + string name = CoreUtils.GenerateObjectName(speckleObject); string sanitisedName = new(name.Where(x => !InvalidChars.Contains(x)).ToArray()); return $"{sanitisedName}{suffix}"; } - - public const string OBJECT_NAME_SEPERATOR = " -- "; - - /// The object to be named - /// A human-readable Object name unique to the given - public static string GenerateObjectName(Base speckleObject) - { - var prefix = GetFriendlyObjectName(speckleObject) ?? SimplifiedSpeckleType(speckleObject); - return $"{prefix}{OBJECT_NAME_SEPERATOR}{speckleObject.id}"; - } - - public static string? GetFriendlyObjectName(Base speckleObject) - { - return speckleObject["name"] as string - ?? speckleObject["Name"] as string - ?? speckleObject["family"] as string; - } - - /// - /// The most significant type in a given - public static string SimplifiedSpeckleType(Base speckleObject) - { - return speckleObject.speckle_type.Split(':')[^1]; - } - - public static string GetAssetSuffix(Type nativeType) { if (nativeType == typeof(Material)) return ".mat"; @@ -108,7 +83,7 @@ namespace Speckle.ConnectorUnity.NativeCache return ".asset"; } - [Obsolete("use " + nameof(GenerateObjectName))] + [Obsolete("use " + nameof(CoreUtils.GenerateObjectName))] public static string GetObjectName(Base speckleObject) { string objectName = speckleObject["name"] as string ?? speckleObject.speckle_type.Split(':').Last(); diff --git a/Packages/systems.speckle.speckle-unity/Runtime/NativeCache/Speckle.ConnectorUnity.NativeCaches.asmdef b/Packages/systems.speckle.speckle-unity/Runtime/NativeCache/Speckle.ConnectorUnity.NativeCaches.asmdef index 4cc6dc8..35ec5fd 100644 --- a/Packages/systems.speckle.speckle-unity/Runtime/NativeCache/Speckle.ConnectorUnity.NativeCaches.asmdef +++ b/Packages/systems.speckle.speckle-unity/Runtime/NativeCache/Speckle.ConnectorUnity.NativeCaches.asmdef @@ -1,7 +1,7 @@ { "name": "Speckle.ConnectorUnity.NativeCache", "rootNamespace": "Speckle.ConnectorUnity", - "references": [], + "references": ["GUID:50d889142fdf9de4b8501c6eaa4b3225"], "includePlatforms": [], "excludePlatforms": [], "allowUnsafeCode": false, diff --git a/Packages/systems.speckle.speckle-unity/Runtime/Utils/CoreUtils.cs b/Packages/systems.speckle.speckle-unity/Runtime/Utils/CoreUtils.cs index 8786c5b..3173016 100644 --- a/Packages/systems.speckle.speckle-unity/Runtime/Utils/CoreUtils.cs +++ b/Packages/systems.speckle.speckle-unity/Runtime/Utils/CoreUtils.cs @@ -1,14 +1,15 @@ +#nullable enable using Speckle.Core.Kits; using Speckle.Core.Logging; +using Speckle.Core.Models; -namespace Speckle.ConnectorUnity +namespace Speckle.ConnectorUnity.Utils { public static class CoreUtils { public static void SetupInit() { - SpeckleLog.Initialize(HostApplications.Unity.Slug, HostApplications.Unity.GetVersion(CoreUtils.GetHostAppVersion())); - Setup.Init(HostApplications.Unity.GetVersion(CoreUtils.GetHostAppVersion()), HostApplications.Unity.Slug); + Setup.Init(HostApplications.Unity.GetVersion(GetHostAppVersion()), HostApplications.Unity.Slug); } public static HostAppVersion GetHostAppVersion() @@ -31,6 +32,30 @@ namespace Speckle.ConnectorUnity return HostAppVersion.v; #endif } + + public const string ObjectNameSeparator = " -- "; + + /// The object to be named + /// A human-readable Object name unique to the given + public static string GenerateObjectName(Base speckleObject) + { + var prefix = GetFriendlyObjectName(speckleObject) ?? SimplifiedSpeckleType(speckleObject); + return $"{prefix}{ObjectNameSeparator}{speckleObject.id}"; + } + public static string? GetFriendlyObjectName(Base speckleObject) + { + return speckleObject["name"] as string + ?? speckleObject["Name"] as string + ?? speckleObject["family"] as string; + } + + /// + /// The most significant type in a given + public static string SimplifiedSpeckleType(Base speckleObject) + { + return speckleObject.speckle_type.Split(':')[^1]; + } + } } diff --git a/Packages/systems.speckle.speckle-unity/Runtime/Utils/Utils.cs b/Packages/systems.speckle.speckle-unity/Runtime/Utils/Utils.cs index 9027c7f..f587afb 100644 --- a/Packages/systems.speckle.speckle-unity/Runtime/Utils/Utils.cs +++ b/Packages/systems.speckle.speckle-unity/Runtime/Utils/Utils.cs @@ -1,9 +1,10 @@ -using System; +#nullable enable +using System; using System.Collections; +using System.Threading.Tasks; using UnityEngine; using UnityEngine.Networking; -#nullable enable namespace Speckle.ConnectorUnity.Utils { public static class Utils @@ -93,5 +94,33 @@ namespace Speckle.ConnectorUnity.Utils Texture2D? texture = DownloadHandlerTexture.GetContent(www); callback.Invoke(texture); } + + /// + /// Coroutine that starts and waits for an async + /// to complete. + /// + /// Useful for running async tasks from coroutines + public class WaitForTask : CustomYieldInstruction + { + public readonly Task Task; + public override bool keepWaiting => !Task.IsCompleted; + + public WaitForTask(Func function) + { + Task = Task.Run(function); + } + } + + /// + public sealed class WaitForTask : CustomYieldInstruction + { + public readonly Task Task; + public TResult Result => Task.Result; + public override bool keepWaiting => !Task.IsCompleted; + public WaitForTask(Func> function) + { + this.Task = System.Threading.Tasks.Task.Run(function); + } + } } } diff --git a/Packages/systems.speckle.speckle-unity/Runtime/Wrappers/Selection/BranchSelection.cs b/Packages/systems.speckle.speckle-unity/Runtime/Wrappers/Selection/BranchSelection.cs index 2b70aa2..1eabdfc 100644 --- a/Packages/systems.speckle.speckle-unity/Runtime/Wrappers/Selection/BranchSelection.cs +++ b/Packages/systems.speckle.speckle-unity/Runtime/Wrappers/Selection/BranchSelection.cs @@ -10,9 +10,9 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection public sealed class BranchSelection : OptionSelection { [field: SerializeField, Range(1,100), Tooltip("Number of branches to request")] - public int BranchesLimit { get; set; } = 30; + public int BranchesLimit { get; set; } = 100; [field: SerializeField, Range(1,100), Tooltip("Number of commits to request")] - public int CommitsLimit { get; set; } = 15; + public int CommitsLimit { get; set; } = 25; [field: SerializeReference] public StreamSelection StreamSelection { get; private set; } diff --git a/Packages/systems.speckle.speckle-unity/Runtime/Wrappers/Selection/OptionSelection.cs b/Packages/systems.speckle.speckle-unity/Runtime/Wrappers/Selection/OptionSelection.cs index 2e4e273..f0e67ba 100644 --- a/Packages/systems.speckle.speckle-unity/Runtime/Wrappers/Selection/OptionSelection.cs +++ b/Packages/systems.speckle.speckle-unity/Runtime/Wrappers/Selection/OptionSelection.cs @@ -34,7 +34,7 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection { get { - if (Options == null) return null; + if (Options is null) return null; if (SelectedIndex < 0 || SelectedIndex >= Options.Length) return null; return Options[SelectedIndex]; } @@ -52,7 +52,7 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection protected void GenerateOptions(IList source, Func isDefault) { - List optionsToAdd = new List(source.Count); + List optionsToAdd = new (source.Count); int defaultOption = -1; int index = 0; foreach (TOption? a in source) @@ -77,4 +77,4 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection OnSelectionChange?.Invoke(); } } -} \ No newline at end of file +} diff --git a/Packages/systems.speckle.speckle-unity/Runtime/Wrappers/SpeckleProperties.cs b/Packages/systems.speckle.speckle-unity/Runtime/Wrappers/SpeckleProperties.cs index 259ab85..11c0323 100644 --- a/Packages/systems.speckle.speckle-unity/Runtime/Wrappers/SpeckleProperties.cs +++ b/Packages/systems.speckle.speckle-unity/Runtime/Wrappers/SpeckleProperties.cs @@ -4,6 +4,8 @@ using System.Collections.Generic; using System.Collections.Specialized; using Speckle.Core.Api; using Speckle.Core.Models; +using Speckle.Core.Serialisation; +using Speckle.Newtonsoft.Json; using UnityEngine; namespace Speckle.ConnectorUnity.Wrappers @@ -18,8 +20,10 @@ namespace Speckle.ConnectorUnity.Wrappers [SerializeField, HideInInspector] private string _serializedData = ""; - + + [SerializeField, HideInInspector] private bool _hasChanged; + private ObservableConcurrentDictionary _data; public IDictionary Data @@ -40,10 +44,7 @@ namespace Speckle.ConnectorUnity.Wrappers private string _serializedSpeckleType; private Type _speckleType = typeof(Base); public Type SpeckleType { - get - { - return _speckleType ??= typeof(Base); - } + get => _speckleType ??= typeof(Base); set { @@ -80,7 +81,9 @@ namespace Speckle.ConnectorUnity.Wrappers public void OnAfterDeserialize() { - Base speckleData = Operations.Deserialize(_serializedData); + var deserializer = new BaseObjectDeserializerV2(); + Base speckleData = deserializer.Deserialize(_serializedData); + Data = speckleData.GetMembers(); _hasChanged = false; @@ -96,7 +99,7 @@ namespace Speckle.ConnectorUnity.Wrappers } [Serializable] - private class SpeckleData : Base + private sealed class SpeckleData : Base { public SpeckleData(IDictionary data) { @@ -107,4 +110,4 @@ namespace Speckle.ConnectorUnity.Wrappers } } } -} \ No newline at end of file +}