Cancellation fixes and editor progress

This commit is contained in:
Jedd Morgan 2023-06-14 17:08:51 +01:00
Родитель d9f7895b3f
Коммит b3c6b59721
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: BBD1F7EA4F833F16
18 изменённых файлов: 436 добавлений и 208 удалений

Просмотреть файл

@ -1,6 +1,6 @@
{
"name": "Editor",
"rootNamespace": "",
"name": "EditorTests",
"rootNamespace": "Speckle.ConnectorUnity.Tests",
"references": [
"UnityEngine.TestRunner",
"UnityEditor.TestRunner",

Просмотреть файл

@ -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}");
}
}
}

Просмотреть файл

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5a4f4baa829261d438b740c7d3028756
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Просмотреть файл

@ -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,55 @@
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using NUnit.Framework;
using Speckle.ConnectorUnity.Components;
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_Sync_Passes(string stream)
{
Base testCase = Receive(stream);
var resuts = sut.RecursivelyConvertToNative_Sync(testCase, null);
Assert.That(resuts, Has.Count.GreaterThan(0));
Assert.That(resuts, Has.Some.Matches<ConversionResult>(x => x.WasSuccessful())));
AssertChildren();
}
[UnityTest, TestCaseSource(nameof(TestCases))]
public IEnumerator ToNative_Coroutine_Passes(string stream)
{
Base testCase = Receive(stream);
GameObject parent = new("parent");
yield return sut.RecursivelyConvertToNative_Coroutine(testCase, parent);
AssertChildren(parent);
}
private void AssertChildren(IEnumerable<GameObject> children)
{
foreach (var res in children)
{
res
}
}
}
}

Просмотреть файл

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 2dd598fed5008c44a815ba09e81a2d19
guid: 3d9b0fc7baaf51a4a8e2bcefad8bd7b3
MonoImporter:
externalObjects: {}
serializedVersion: 2

Просмотреть файл

@ -0,0 +1,25 @@
{
"name": "PlayModeTests",
"rootNamespace": "Speckle.ConnectorUnity.Tests",
"references": [
"UnityEngine.TestRunner",
"UnityEditor.TestRunner",
"Speckle.ConnectorUnity.Components",
"Utils"
],
"includePlatforms": [],
"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,96 @@
#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));
}
[UnityTest]
public IEnumerator ReceiveAndConvert_Coroutine_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.StartCoroutine(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,11 +1,6 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Speckle.ConnectorUnity.Utils;
using Speckle.Core.Api;
using Speckle.Core.Logging;
using Speckle.Core.Models;
using Speckle.Core.Models.GraphTraversal;
using UnityEditor;
@ -21,51 +16,68 @@ namespace Speckle.ConnectorUnity.Components.Editor
private static bool generateAssets = false;
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);
foldOutStatus = EditorGUILayout.Foldout(foldOutStatus, "Preview Image");
if (foldOutStatus)
{
Rect rect = GUILayoutUtility.GetAspectRect(7f / 4f);
if (previewImage != null) GUI.DrawTexture(rect, previewImage);
}
}
//Receive button
bool receive = GUILayout.Button("Receive!");
bool selection = EditorGUILayout.ToggleLeft("Generate Assets", generateAssets);
if (generateAssets != selection)
{
generateAssets = selection;
UpdateGenerateAssets();
}
//TODO: Draw events in a collapsed region
if (receive)
//Receive settings
{
try
bool prev = GUI.enabled;
GUI.enabled = !speckleReceiver.IsReceiving;
//Receive button
bool receive = GUILayout.Button("Receive!");
bool selection = EditorGUILayout.ToggleLeft("Generate Assets", generateAssets);
if (generateAssets != selection)
{
await ReceiveSelection();
generateAssets = selection;
UpdateGenerateAssets();
}
catch (OperationCanceledException ex)
GUI.enabled = prev;
if (receive && !speckleReceiver.IsReceiving)
{
Debug.Log($"Receive operation cancelled\n{ex}", this);
}
catch (Exception ex)
{
Debug.LogError($"Failed to receive selection {ex}", this);
}
finally
{
EditorUtility.ClearProgressBar();
int id = Progress.Start(
"Receiving Speckle data",
"Fetching commit data",
Progress.Options.Indefinite | Progress.Options.Sticky);
Progress.SetPriority(id, Progress.Priority.High);
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();
}
}
}
}
@ -94,32 +106,57 @@ namespace Speckle.ConnectorUnity.Components.Editor
previewImage = null;
((SpeckleReceiver)target).GetPreviewImage(t => previewImage = t);
}
private async Task ReceiveSelection()
private async Task ReceiveSelection(int progressId)
{
var speckleReceiver = (SpeckleReceiver)target;
Base commitObject = await ReceiveCommit();
int childrenConverted = 0;
float totalChildren = commitObject.totalChildrenCount;
bool shouldCancel = false;
Progress.RegisterCancelCallback(progressId, () =>
{
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();
}
int childrenConverted = 0;
int childrenFailed = 0;
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)
{
Base b = context.current;
//NOTE: progress wont reach 100% because not all objects are convertable
float progress = childrenConverted / totalChildren;
float progress = (childrenConverted + childrenFailed) / totalChildrenFloat;
if (shouldCancel) return false;
shouldCancel = EditorUtility.DisplayCancelableProgressBar(
"Converting To Native...",
$"{b.speckle_type} - {b.id}",
progress);
return true;
return !shouldCancel;
}
foreach (var conversionResult in speckleReceiver.Converter.RecursivelyConvertToNative_Enumerable(
@ -134,19 +171,27 @@ namespace Speckle.ConnectorUnity.Components.Editor
}
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;
}
Debug.Log(
shouldCancel
? $"Stopped converting to native. Created {childrenConverted} {nameof(GameObject)}s: Responding to cancel through editor"
: $"Finished converting to native. Created {childrenConverted} {nameof(GameObject)}s ",
this);
var resultString = $"{childrenConverted}{nameof(GameObject)}s created, {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()
@ -155,65 +200,73 @@ namespace Speckle.ConnectorUnity.Components.Editor
speckleReceiver.Converter.AssetCache.nativeCaches = NativeCacheFactory.GetDefaultNativeCacheSetup(generateAssets);
}
private async Task<Base> ReceiveCommit()
private async Task<Base> ReceiveCommit(int progressId)
{
var speckleReceiver = (SpeckleReceiver)target;
speckleReceiver.BeginOperation();
string serverLogName = speckleReceiver.Account.Client?.ServerUrl ?? "Speckle";
string message = $"Receiving data from {serverLogName}...";
EditorUtility.DisplayProgressBar(message, "Fetching data", 0f);
string serverLogName = speckleReceiver.Account.Client?.ServerUrl ?? "Speckle";
int transport = Progress.Start($"Downloading data from {serverLogName}", "Waiting...", Progress.Options.Sticky, progressId);
int deserialize = Progress.Start("Deserializing data", "Waiting...", Progress.Options.Sticky, progressId);
var totalObjectCount = 1;
void OnTotalChildrenKnown(int count)
{
totalObjectCount = count;
EditorApplication.delayCall += () => EditorUtility.DisplayProgressBar(message, $"Fetching data {0}/{totalObjectCount}", 0f);
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,
$"Downloading data {currentProgress}/{totalObjectCount}",
progress);
if (shouldCancel)
{
speckleReceiver.CancellationTokenSource!.Cancel();
}
};
var fetched = (rtProgress + ltProgress);
Progress.Report(transport, fetched, totalObjectCount, $"{fetched}/{totalObjectCount}");
}
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}");
}
}
Base commitObject;
try
{
speckleReceiver.OnTotalChildrenCountKnown.AddListener(OnTotalChildrenKnown);
speckleReceiver.OnReceiveProgressAction.AddListener(OnProgress);
commitObject = await speckleReceiver.ReceiveAsync(speckleReceiver.CancellationToken).ConfigureAwait(false);
commitObject = await speckleReceiver.ReceiveAsync(speckleReceiver.CancellationToken)
.ConfigureAwait(false);
Progress.Finish(transport);
Progress.Finish(deserialize);
}
catch(Exception ex)
catch(OperationCanceledException)
{
throw new SpeckleException("Failed to receive commit", ex);
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);
EditorApplication.delayCall += EditorUtility.ClearProgressBar;
speckleReceiver.FinishOperation();
}
return commitObject;
}
[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)

Просмотреть файл

@ -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;
}
}
}

Просмотреть файл

@ -78,9 +78,11 @@ namespace Speckle.ConnectorUnity.Components
{
converted = this.converted;
exception = this.exception;
return this.exception == null;
return WasSuccessful();
}
public bool WasSuccessful() => this.exception == null;
public Base SpeckleObject => traversalContext.current;
}
@ -136,10 +138,10 @@ namespace Speckle.ConnectorUnity.Components
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;
}
Debug.Log($"Finished converting {rootObject.id} to native. Created {created.Count} {nameof(GameObject)}s ",this);
}
/// <summary>

Просмотреть файл

@ -40,29 +40,45 @@ 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 internal CancellationToken CancellationToken => CancellationTokenSource?.Token ?? default;
protected CancellationTokenSource? CancellationTokenSource { get; private set; }
public CancellationToken CancellationToken => CancellationTokenSource?.Token ?? default;
public bool IsReceiving => CancellationTokenSource != 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()
{
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"></param>
/// <param name="predicate"></param>
/// <remarks>function does not throw, instead calls <see cref="OnErrorAction"/>, and calls <see cref="OnComplete"/> on complteion</remarks>
/// <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)
@ -172,18 +188,20 @@ namespace Speckle.ConnectorUnity.Components
/// Starts a new receive operation with a <see cref="CancellationToken"/>
/// </summary>
/// <exception cref="InvalidOperationException">already receiving</exception>
protected internal void BeginOperation()
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;
}
@ -388,6 +406,8 @@ namespace Speckle.ConnectorUnity.Components
Application.OpenURL(url);
}
#endif
public string GetSelectedUrl()
{
string serverUrl = Account.Selected!.serverInfo.url;
@ -423,12 +443,16 @@ namespace Speckle.ConnectorUnity.Components
Account.RefreshOptions();
}
public void OnDestroy()
public void OnDisable()
{
CancellationTokenSource?.Cancel();
}
public void OnDestroy()
{
CancellationTokenSource?.Dispose();
}
public void OnBeforeSerialize()
{
//pass

Просмотреть файл

@ -1,7 +1,7 @@
#nullable enable
using System;
using System.Collections;
using Speckle.Core.Models;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;
@ -94,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);
}
}
}
}

Просмотреть файл

@ -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();
}
}
}
}