Merge pull request #93 from specklesystems/jrm/traversal-refactor

implementation of traversal refactor
This commit is contained in:
Jedd Morgan
2023-06-26 22:14:52 +01:00
committed by GitHub
41 changed files with 1379 additions and 649 deletions
@@ -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");
});
+153
View File
@@ -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<RecursiveConverter>();
}
[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<Account>(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<Base>(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<Account> 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;
}
}
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 2dd598fed5008c44a815ba09e81a2d19
guid: 942bf0cb27c5c5045bc4cbb7fc0fad71
MonoImporter:
externalObjects: {}
serializedVersion: 2
+1
View File
@@ -9,6 +9,7 @@ using UnityEngine;
using UnityEngine.Events;
[RequireComponent(typeof(Sender)), ExecuteAlways]
[Obsolete]
public class SendChildrenToSpeckle : MonoBehaviour
{
public LayerMask layerMask;
+1 -1
View File
@@ -1,4 +1,4 @@
{
"name": "Speckle.Extra",
"references":[ "GUID:eed1b8b83e2c0074d9e5de2348e3ff72", "GUID:e6adfdc4e436206479f48eafc82f32b5", "GUID:d274441ecc3eb3f43b093eec1503d681" ]
"references":[ "GUID:eed1b8b83e2c0074d9e5de2348e3ff72", "GUID:e6adfdc4e436206479f48eafc82f32b5", "GUID:d274441ecc3eb3f43b093eec1503d681", "GUID:50d889142fdf9de4b8501c6eaa4b3225" ]
}
+3 -2
View File
@@ -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
});
}
}
}
}
+3 -1
View File
@@ -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
}
}
}
}
}
+199 -199
View File
@@ -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:
<Account>k__BackingField:
rid: 3905486442366500889
<Stream>k__BackingField:
rid: 3905486442366500890
<Branch>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
<StreamsLimit>k__BackingField: 50
<AccountSelection>k__BackingField:
rid: 3905486442366500889
- rid: 3905486442366500891
type: {class: BranchSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers}
data:
selectedIndex: 0
<BranchesLimit>k__BackingField: 100
<CommitsLimit>k__BackingField: 0
<StreamSelection>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:
<Account>k__BackingField:
rid: 3905486442366500885
<Stream>k__BackingField:
rid: 3905486442366500886
<Branch>k__BackingField:
rid: 3905486442366500887
<Commit>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
<StreamsLimit>k__BackingField: 50
<AccountSelection>k__BackingField:
rid: 3905486442366500885
- rid: 3905486442366500887
type: {class: BranchSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers}
data:
selectedIndex: 0
<BranchesLimit>k__BackingField: 100
<CommitsLimit>k__BackingField: 25
<StreamSelection>k__BackingField:
rid: 3905486442366500886
- rid: 3905486442366500888
type: {class: CommitSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers}
data:
selectedIndex: 0
<BranchSelection>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:
<AssetCache>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:
<Account>k__BackingField:
rid: 5855987529328361546
<Stream>k__BackingField:
rid: 5855987529328361547
<Branch>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
<StreamsLimit>k__BackingField: 50
<AccountSelection>k__BackingField:
rid: 5855987529328361546
- rid: 5855987529328361548
type: {class: BranchSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers}
data:
selectedIndex: 0
<BranchesLimit>k__BackingField: 30
<CommitsLimit>k__BackingField: 0
<StreamSelection>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:
<Account>k__BackingField:
rid: 5855987529328361542
<Stream>k__BackingField:
rid: 5855987529328361543
<Branch>k__BackingField:
rid: 5855987529328361544
<Commit>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
<StreamsLimit>k__BackingField: 50
<AccountSelection>k__BackingField:
rid: 5855987529328361542
- rid: 5855987529328361544
type: {class: BranchSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers}
data:
selectedIndex: 0
<BranchesLimit>k__BackingField: 30
<CommitsLimit>k__BackingField: 15
<StreamSelection>k__BackingField:
rid: 5855987529328361543
- rid: 5855987529328361545
type: {class: CommitSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers}
data:
selectedIndex: 0
<BranchSelection>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:
<AssetCache>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
@@ -1,6 +1,6 @@
{
"name": "Editor",
"rootNamespace": "",
"name": "EditorTests",
"rootNamespace": "Speckle.ConnectorUnity.Tests",
"references": [
"UnityEngine.TestRunner",
"UnityEditor.TestRunner",
-96
View File
@@ -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}");
}
}
}
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5a4f4baa829261d438b740c7d3028756
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+18
View File
@@ -0,0 +1,18 @@
using NUnit.Framework;
using UnityEngine;
namespace Speckle.ConnectorUnity.Tests
{
public abstract class ComponentTest<T> where T : Component
{
protected T sut;
[SetUp]
public void Setup()
{
GameObject go = new();
sut = go.AddComponent<T>();
Assert.That(sut, Is.Not.Null);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4e2fe277dd9c47ad998138dcdbb024ae
timeCreated: 1686757093
@@ -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<RecursiveConverter>
{
private static IEnumerable<string> 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<Transform>());
Assert.That(results, HasSomeComponent<MeshRenderer>());
Assert.That(results, HasSomeComponent<SpeckleProperties>());
}
private static Constraint HasSomeComponent<T>() where T : Component
{
return Has.Some.Matches<ConversionResult>(
x =>
{
return x.WasSuccessful(out var success, out _)
&& success.GetComponent<T>();
});
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3d9b0fc7baaf51a4a8e2bcefad8bd7b3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 79301723eb79d2745ab1e1a9360f6f2d
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -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<SpeckleReceiver>
{
[UnityTest]
public IEnumerator ReceiveAsync_Succeeds()
{
yield return null;
var task = new Utils.Utils.WaitForTask<Base>(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));
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1756c50dd28a4e341a70866daa68a8d5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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<GameObject?> ReceiveAndConvert(SpeckleReceiver speckleReceiver)
private async Task<Base> 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<Base?> 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<string, int> 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
}
}
}
@@ -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;
}
}
}
@@ -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()
@@ -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
/// </summary>
[RequireComponent( typeof( RecursiveConverter ) )]
[RequireComponent(typeof(RecursiveConverter))]
[Obsolete]
public class Receiver : MonoBehaviour
{
public string StreamId;
@@ -21,6 +21,7 @@ namespace Speckle.ConnectorUnity
/// that handles conversions for you
/// </summary>
[RequireComponent(typeof(RecursiveConverter)), ExecuteAlways]
[Obsolete]
public class Sender : MonoBehaviour
{
@@ -146,4 +147,4 @@ namespace Speckle.ConnectorUnity
#endregion
}
}
}
@@ -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
{
/// <summary>
/// Struct that encapsulates the result of a <see cref="RecursiveConverter"/> ToNative conversion of a single Speckle Object (<see cref="Base"/>)
/// </summary>
public readonly struct ConversionResult
{
/// <summary>
/// The context that was converted ToNative
/// </summary>
public readonly TraversalContext traversalContext;
/// <summary>
/// The result of conversion a successful conversion
/// </summary>
public readonly GameObject? converted;
/// <summary>
/// The result of conversion a failed conversion
/// </summary>
public readonly Exception? exception;
/// <summary>
/// Constructor used for Successful conversions
/// </summary>
/// <param name="traversalContext">The current traversal context</param>
/// <param name="converted">The resultant ToNative conversion of <see cref="TraversalContext.current"/> context object</param>
/// <exception cref="ArgumentNullException"/>
public ConversionResult(TraversalContext traversalContext, [NotNull] GameObject? converted)
: this(traversalContext, converted, null)
{
if (converted is null) throw new ArgumentNullException(nameof(converted));
}
/// <summary>
/// Constructor used for Failed conversions
/// </summary>
/// <param name="traversalContext">The current conversion</param>
/// <param name="exception">The operation halting exception that occured</param>
/// <param name="converted">Optional converted GameObject</param>
/// <exception cref="ArgumentNullException"/>
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;
}
/// <summary>
///
/// </summary>
/// <param name="converted">The converted <see cref="GameObject"/></param>
/// <param name="exception">The <see cref="exception"/> that occured during conversion</param>
/// <returns>True if the conversion was successful</returns>
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<Base, GameObject> outCreatedObjects)
/// <inheritdoc cref="RecursivelyConvertToNative_Enumerable"/>
/// <remarks>Calling this function will perform the conversion process synchronously</remarks>
/// <returns>The conversion result</returns>
public List<ConversionResult> RecursivelyConvertToNative_Sync(
Base rootObject,
Transform? parent,
Predicate<TraversalContext>? 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();
}
/// <inheritdoc cref="RecursivelyConvertToNative_Enumerable"/>
/// <remarks>Calling this function will start a coroutine to complete later on the coroutine loop</remarks>
/// <returns>The started Coroutine</returns>
public Coroutine RecursivelyConvertToNative_Coroutine(
Base rootObject,
Transform? parent,
Predicate<TraversalContext>? predicate = null
)
{
return StartCoroutine(RecursivelyConvertToNative_Enumerable(rootObject, parent, predicate).GetEnumerator());
}
/// <summary>
/// Will recursively traverse the given <paramref name="rootObject"/> and convert convertable child objects
/// where the given <see cref="predicate"/>
/// </summary>
/// <param name="rootObject">The Speckle object to traverse and convert all convertable children</param>
/// <param name="parent">Optional parent <see cref="Transform"/> for the created root <see cref="GameObject"/>s</param>
/// <param name="predicate">A filter function to allow for selectively excluding certain objects from being converted</param>
/// <returns>An unevaluated <see cref="IEnumerable"/> of all created <see cref="GameObject"/>s</returns>
public IEnumerable<ConversionResult> RecursivelyConvertToNative_Enumerable(
Base rootObject,
Transform? parent,
Predicate<TraversalContext>? 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<Base, GameObject?> 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;
}
}
/// <summary>
/// Converts a objectTree (see <see cref="GraphTraversal"/>) to unevaluated enumerable.
/// As this enumerable is iterated through, each context <see cref="Base"/> object will be converted to <see cref="GameObject"/> (if successful)
/// or <see langword="null"/> if not.
/// </summary>
/// <remarks>
/// You may enumerate over multiple frames (e.g. coroutine) but you must ensure the output eventually gets fully enumerated (exactly once)
/// </remarks>
/// <param name="objectTree"></param>
/// <param name="parent"></param>
/// <param name="outCreatedObjects"></param>
/// <returns></returns>
protected IEnumerable<ConversionResult> ConvertTree(IEnumerable<TraversalContext> objectTree, Transform? parent, IDictionary<Base, GameObject?> 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<Base, GameObject?> 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<GameObject> outCreatedObjects)
=> ConvertCoroutine(rootObject, parent, outCreatedObjects,b => ConverterInstance.CanConvertToNative(b));
[Obsolete("Use " + nameof(RecursivelyConvertToNative_Coroutine))]
public IEnumerator ConvertCoroutine(Base rootObject, Transform? parent, List<GameObject> outCreatedObjects, Func<Base, bool> predicate)
{
foreach (string propertyName in GetPotentialChildren(rootObject))
@@ -74,11 +237,13 @@ namespace Speckle.ConnectorUnity.Components
/// <param name="o">The object to convert (<see cref="Base"/> or <see cref="List{T}"/> of)</param>
/// <param name="parent">Optional parent transform for the created root <see cref="GameObject"/>s</param>
/// <returns> A list of all created <see cref="GameObject"/>s</returns>
[Obsolete("Use " + nameof(RecursivelyConvertToNative_Sync))]
public virtual List<GameObject> RecursivelyConvertToNative(object? o, Transform? parent)
=> RecursivelyConvertToNative(o, parent, b => ConverterInstance.CanConvertToNative(b));
/// <inheritdoc cref="RecursivelyConvertToNative(object, Transform)"/>
/// <param name="predicate">A function to determine if an object should be converted</param>
[Obsolete("Use " + nameof(RecursivelyConvertToNative_Sync))]
public virtual List<GameObject> RecursivelyConvertToNative(object? o, Transform? parent, Func<Base, bool> predicate)
{
InitializeAssetCache();
@@ -111,6 +276,7 @@ namespace Speckle.ConnectorUnity.Components
ConverterInstance.SetContextDocument(AssetCache);
}
[Obsolete]
public virtual void RecurseTreeToNative(Base baseObject, Transform? parent, Func<Base, bool> predicate, IList<GameObject> 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<string> 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<Base, bool> predicate, IList<GameObject> 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
}
}
}
@@ -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
}
}
}
}
}
@@ -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<Base>? beforeConvertCallback = null)
/// <summary>
/// Cancels any current receive operations
/// </summary>
/// <remarks>
/// Note, this does not cancel any currently executing ConvertToNative, just the <see cref="Operations.Receive"/>.
/// </remarks>
/// <returns><see langword="true"/> if the cancellation request was made. <see langword="false"/> if there was no pending operation to cancel (see <see cref="IsReceiving"/>)</returns>
public bool Cancel()
{
Task<Base?> receiveOperation = Task.Run(ReceiveAsync);
if (CancellationTokenSource == null) return false;
CancellationTokenSource.Cancel();
return true;
}
/// <summary>
/// Receive the selected <see cref="Commit"/> object, and converts ToNative as children of <paramref name="parent"/>
/// </summary>
/// <param name="parent">Optional parent <see cref="Transform"/> for the created root <see cref="GameObject"/>s</param>
/// <param name="predicate">A filter function to allow for selectively excluding certain objects from being converted</param>
/// <remarks>function does not throw, instead calls <see cref="OnErrorAction"/>, and calls <see cref="OnComplete"/> upon completion</remarks>
/// <seealso cref="ReceiveAsync(System.Threading.CancellationToken)"/>
/// <seealso cref="RecursiveConverter.RecursivelyConvertToNative_Enumerable"/>
public IEnumerator ReceiveAndConvert_Routine(Transform? parent, Predicate<TraversalContext>? 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<Base> 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();
}
/// <inheritdoc cref="ReceiveAndConvert_Routine"/>
public async void ReceiveAndConvert_Async(Transform? parent, Predicate<TraversalContext>? 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();
}
}
/// <summary>
/// Receives the selected commit object using async Task
/// </summary>
/// <returns>Awaitable commit object</returns>
/// <param name="cancellationToken"></param>
/// <exception cref="SpeckleException">thrown when selection is incomplete</exception>
public async Task<Base?> ReceiveAsync()
/// <remarks>
/// This function is safe to call concurrently from any threads.
/// For this reason we use <paramref name="cancellationToken"/> parameter, rather than use the <see cref="CancellationToken"/> property
/// <br/>
/// Additionally, <see cref="OnComplete"/> and <see cref="OnErrorAction"/> won't be called.
/// </remarks>
public async Task<Base> 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");
}
/// <summary>
/// Starts a new receive operation with a <see cref="CancellationToken"/>
/// </summary>
/// <exception cref="InvalidOperationException">already receiving</exception>
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;
}
/// <summary>
/// Receives the requested <see cref="objectId"/> using async Task
/// </summary>
/// <param name="token"></param>
/// <param name="client"></param>
/// <param name="streamId"></param>
/// <param name="objectId"></param>
/// <param name="commit"></param>
/// <param name="onProgressAction"></param>
/// <param name="onErrorAction"></param>
/// <param name="onTotalChildrenCountKnown"></param>
/// <param name="cancellationToken"></param>
/// <exception cref="Exception">Throws various types of exceptions to indicate faliure</exception>
/// <returns></returns>
public static async Task<Base?> ReceiveAsync(CancellationToken token,
public static async Task<Base> ReceiveAsync(
Client client,
string streamId,
string objectId,
Commit? commit,
Action<ConcurrentDictionary<string, int>>? onProgressAction = null,
Action<string, Exception>? onErrorAction = null,
Action<int>? onTotalChildrenCountKnown = null)
Action<int>? 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<string, object>()
{
{"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<string, object>()
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;
}
/// <summary>
/// Helper method for using <see cref="RecursiveConverter"/>.
/// Creates blank GameObjects for each property/category of the root object.
@@ -179,6 +299,7 @@ namespace Speckle.ConnectorUnity.Components
/// <param name="rootObjectName">The name of the parent <see cref="GameObject"/> to create</param>
/// <param name="beforeConvertCallback">Callback for each object converted</param>
/// <returns>The created parent <see cref="GameObject"/></returns>
[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<Base>? beforeConvertCallback)
{
@@ -211,7 +332,7 @@ namespace Speckle.ConnectorUnity.Components
return rootObject;
}
/// <summary>
///
/// </summary>
@@ -220,6 +341,7 @@ namespace Speckle.ConnectorUnity.Components
/// <param name="commit"></param>
/// <param name="error">error messages for </param>
/// <returns>true if selection is complete, as we are ready to receive</returns>
[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
/// </summary>
/// <param name="allAngles">when <see langword="true"/>, will fetch 360 degree preview image</param>
/// <param name="callback">Callback function to be called when the web request completes</param>
/// <returns><see langword="false"/> if <see cref="Account"/>, <see cref="Stream"/>, or <see cref="Commit"/> was <see langword="null"/></returns>
public bool GetPreviewImage(/*bool allAngles,*/ Action<Texture2D?> callback)
/// <returns>The executing <see cref="Coroutine"/> or <see langword="null"/> if <see cref="Account"/>, <see cref="Stream"/>, or <see cref="Commit"/> was <see langword="null"/></returns>
public Coroutine? GetPreviewImage(/*bool allAngles,*/ Action<Texture2D?> 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<Base>? beforeConvertCallback = null)
{
// ReSharper disable once MethodSupportsCancellation
Task<Base> 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<Commit?> { }
[Serializable] public sealed class BranchSelectionEvent : UnityEvent<Branch?> { }
[Serializable] public sealed class ErrorActionEvent : UnityEvent<string, Exception> { }
[Serializable] public sealed class OperationProgressEvent : UnityEvent<ConcurrentDictionary<string, int>> { }
[Serializable] public sealed class ReceiveCompleteHandler : UnityEvent<GameObject> { }
[Serializable] public sealed class ReceiveCompleteHandler : UnityEvent<Transform?> { }
[Serializable] public sealed class ChildrenCountHandler : UnityEvent<int> { }
}
@@ -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;
@@ -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<SMesh> meshes = new();
List<Base> 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;
}
@@ -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
/// <param name="element">The <see cref="Base"/> object being converted</param>
/// <param name="meshes">Collection of <see cref="Objects.Geometry.Mesh"/>es that shall be converted</param>
/// <returns>A <see cref="GameObject"/> with the converted <see cref="UnityEngine.Mesh"/>, <see cref="MeshFilter"/>, and <see cref="MeshRenderer"/></returns>
public GameObject? MeshesToNative(Base element, IReadOnlyCollection<SMesh> meshes)
public GameObject MeshesToNative(Base element, IReadOnlyCollection<SMesh> 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
/// </summary>
/// <param name="speckleMesh">Mesh to convert</param>
/// <returns></returns>
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");
@@ -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;
}
}
}
}
}
@@ -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;
@@ -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 = " -- ";
/// <param name="speckleObject">The object to be named</param>
/// <returns>A human-readable Object name unique to the given <paramref name="speckleObject"/></returns>
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;
}
/// <param name="speckleObject"></param>
/// <returns>The most significant type in a given <see cref="Base.speckle_type"/></returns>
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();
@@ -1,7 +1,7 @@
{
"name": "Speckle.ConnectorUnity.NativeCache",
"rootNamespace": "Speckle.ConnectorUnity",
"references": [],
"references": ["GUID:50d889142fdf9de4b8501c6eaa4b3225"],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
@@ -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 = " -- ";
/// <param name="speckleObject">The object to be named</param>
/// <returns>A human-readable Object name unique to the given <paramref name="speckleObject"/></returns>
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;
}
/// <param name="speckleObject"></param>
/// <returns>The most significant type in a given <see cref="Base.speckle_type"/></returns>
public static string SimplifiedSpeckleType(Base speckleObject)
{
return speckleObject.speckle_type.Split(':')[^1];
}
}
}
@@ -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);
}
/// <summary>
/// Coroutine <see cref="CustomYieldInstruction"/> that starts and waits for an async <see cref="System.Threading.Tasks.Task"/>
/// to complete.
/// </summary>
/// <remarks>Useful for running async tasks from coroutines</remarks>
public class WaitForTask : CustomYieldInstruction
{
public readonly Task Task;
public override bool keepWaiting => !Task.IsCompleted;
public WaitForTask(Func<Task> function)
{
Task = Task.Run(function);
}
}
/// <inheritdoc cref="WaitForTask"/>
public sealed class WaitForTask<TResult> : CustomYieldInstruction
{
public readonly Task<TResult> Task;
public TResult Result => Task.Result;
public override bool keepWaiting => !Task.IsCompleted;
public WaitForTask(Func<Task<TResult>> function)
{
this.Task = System.Threading.Tasks.Task.Run(function);
}
}
}
}
@@ -10,9 +10,9 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection
public sealed class BranchSelection : OptionSelection<Branch>
{
[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; }
@@ -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<TOption> source, Func<TOption, int, bool> isDefault)
{
List<TOption> optionsToAdd = new List<TOption>(source.Count);
List<TOption> 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();
}
}
}
}
@@ -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<string, object> _data;
public IDictionary<string, object> 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<string, object> data)
{
@@ -107,4 +110,4 @@ namespace Speckle.ConnectorUnity.Wrappers
}
}
}
}
}