#nullable enable
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Speckle.ConnectorUnity.Utils;
using Speckle.Core.Logging;
using Speckle.Core.Models;
using Speckle.Core.Models.GraphTraversal;
using UnityEngine;
namespace Speckle.ConnectorUnity.Components
{
///
/// Struct that encapsulates the result of a ToNative conversion of a single Speckle Object ()
///
public readonly struct ConversionResult
{
///
/// The context that was converted ToNative
///
public readonly TraversalContext traversalContext;
///
/// The result of conversion a successful conversion
///
public readonly GameObject? converted;
///
/// The result of conversion a failed conversion
///
public readonly Exception? exception;
///
/// Constructor used for Successful conversions
///
/// The current traversal context
/// The resultant ToNative conversion of context object
///
public ConversionResult(TraversalContext traversalContext, [NotNull] GameObject? converted)
: this(traversalContext, converted, null)
{
if (converted is null)
throw new ArgumentNullException(nameof(converted));
}
///
/// Constructor used for Failed conversions
///
/// The current conversion
/// The operation halting exception that occured
/// Optional converted GameObject
///
public ConversionResult(
TraversalContext traversalContext,
[NotNull] Exception? exception,
GameObject? converted = null
)
: this(traversalContext, converted, exception)
{
if (exception is null)
throw new ArgumentNullException(nameof(exception));
}
private ConversionResult(
TraversalContext traversalContext,
GameObject? converted,
Exception? exception
)
{
this.traversalContext = traversalContext;
this.converted = converted;
this.exception = exception;
}
///
///
///
/// The converted
/// The that occured during conversion
/// True if the conversion was successful
public bool WasSuccessful(
[NotNullWhen(true)] out GameObject? converted,
[NotNullWhen(false)] out Exception? exception
)
{
converted = this.converted;
exception = this.exception;
return WasSuccessful();
}
public bool WasSuccessful() => this.exception == null;
public Base SpeckleObject => traversalContext.Current;
}
public partial class RecursiveConverter
{
///
/// Calling this function will perform the conversion process synchronously
/// The conversion result
public List RecursivelyConvertToNative_Sync(
Base rootObject,
Transform? parent,
Predicate? predicate = null
)
{
return RecursivelyConvertToNative_Enumerable(rootObject, parent, predicate).ToList();
}
///
/// Calling this function will start a coroutine to complete later on the coroutine loop
/// The started Coroutine
public Coroutine RecursivelyConvertToNative_Coroutine(
Base rootObject,
Transform? parent,
Predicate? predicate = null
)
{
return StartCoroutine(
RecursivelyConvertToNative_Enumerable(rootObject, parent, predicate).GetEnumerator()
);
}
///
/// Will recursively traverse the given and convert convertable child objects
/// where the given
///
/// The Speckle object to traverse and convert all convertable children
/// Optional parent for the created root s
/// A filter function to allow for selectively excluding certain objects from being converted
/// An unevaluated of all created s
public IEnumerable RecursivelyConvertToNative_Enumerable(
Base rootObject,
Transform? parent,
Predicate? predicate = null
)
{
var userPredicate = predicate ?? (_ => true);
var traversalFunc = DefaultTraversal.CreateBIMTraverseFunc(ConverterInstance);
var objectsToConvert = traversalFunc
.Traverse(rootObject)
.Where(x => ConverterInstance.CanConvertToNative(x.Current))
.Where(x => userPredicate(x));
Dictionary created = new();
foreach (var conversionResult in ConvertTree(objectsToConvert, parent, created))
{
if (!isActiveAndEnabled)
throw new InvalidOperationException(
$"Cannot convert objects while {GetType()} is disabled"
);
yield return conversionResult;
}
}
///
/// Converts a objectTree (see ) to unevaluated enumerable.
/// As this enumerable is iterated through, each context object will be converted to (if successful)
/// or if not.
///
///
/// You may enumerate over multiple frames (e.g. coroutine) but you must ensure the output eventually gets fully enumerated (exactly once)
///
///
///
///
///
protected IEnumerable ConvertTree(
IEnumerable objectTree,
Transform? parent,
IDictionary outCreatedObjects
)
{
InitializeAssetCache();
AssetCache.BeginWrite();
foreach (TraversalContext tc in objectTree)
{
ConversionResult result;
try
{
Transform? currentParent = GetParent(tc, outCreatedObjects) ?? parent;
var converted = ConvertToNative(tc.Current, currentParent);
result = new ConversionResult(tc, converted);
outCreatedObjects.TryAdd(tc.Current, result.converted);
}
catch (Exception ex)
{
result = new ConversionResult(tc, ex);
}
yield return result;
}
AssetCache.FinishWrite();
}
protected static Transform? GetParent(
TraversalContext? tc,
IDictionary createdObjects
)
{
if (tc == null)
return null; //We've reached the root object, and still not found a converted parent
if (createdObjects.TryGetValue(tc.Current, out GameObject? p) && p != null)
return p.transform;
//Go one level up, and repeat!
return GetParent(tc.Parent, createdObjects);
}
protected GameObject ConvertToNative(Base speckleObject, Transform? parentTransform)
{
GameObject? go = ConverterInstance.ConvertToNative(speckleObject) as GameObject;
if (go == null)
throw new SpeckleException("Conversion Returned Null");
go.transform.SetParent(parentTransform, true);
//Set some common for all created GameObjects
//TODO add support for more unity specific props
if (go.name == "New Game Object" || string.IsNullOrWhiteSpace(go.name))
go.name = CoreUtils.GenerateObjectName(speckleObject);
if (speckleObject["physicsLayer"] is string layerName)
{
int layer = LayerMask.NameToLayer(layerName); //TODO: check how this can be interoperable with Unreal and Blender
if (layer > -1)
go.layer = layer;
}
//if (baseObject["tag"] is string t) go.tag = t;
//if (baseObject["isStatic"] is bool isStatic) go.isStatic = isStatic;
return go;
}
#region deprecated conversion functions
[Obsolete("Use " + nameof(RecursivelyConvertToNative_Coroutine))]
public IEnumerator ConvertCoroutine(
Base rootObject,
Transform? parent,
List outCreatedObjects
) =>
ConvertCoroutine(
rootObject,
parent,
outCreatedObjects,
b => ConverterInstance.CanConvertToNative(b)
);
[Obsolete("Use " + nameof(RecursivelyConvertToNative_Coroutine))]
public IEnumerator ConvertCoroutine(
Base rootObject,
Transform? parent,
List outCreatedObjects,
Func predicate
)
{
foreach (string propertyName in GetPotentialChildren(rootObject))
{
ConvertChild(rootObject[propertyName], parent, predicate, outCreatedObjects);
yield return null;
}
}
///
/// Given ,
/// will recursively convert any objects in the tree
///
/// The object to convert ( or of)
/// Optional parent transform for the created root s
/// A list of all created s
[Obsolete("Use " + nameof(RecursivelyConvertToNative_Sync))]
public virtual List RecursivelyConvertToNative(object? o, Transform? parent) =>
RecursivelyConvertToNative(o, parent, b => ConverterInstance.CanConvertToNative(b));
///
/// A function to determine if an object should be converted
[Obsolete("Use " + nameof(RecursivelyConvertToNative_Sync))]
public virtual List RecursivelyConvertToNative(
object? o,
Transform? parent,
Func predicate
)
{
InitializeAssetCache();
var createdGameObjects = new List();
try
{
AssetCache.BeginWrite();
ConvertChild(o, parent, predicate, createdGameObjects);
}
finally
{
AssetCache.FinishWrite();
}
//TODO track event?
return createdGameObjects;
}
private void InitializeAssetCache()
{
//Ensure we have A native cache
if (AssetCache.nativeCaches.Any(x => x == null))
{
AssetCache.nativeCaches = NativeCacheFactory.GetStandaloneCacheSetup();
}
ConverterInstance.SetContextDocument(AssetCache);
}
[Obsolete]
public virtual void RecurseTreeToNative(
Base baseObject,
Transform? parent,
Func predicate,
IList outCreatedObjects
)
{
object? converted = null;
if (predicate(baseObject))
{
try
{
converted = ConverterInstance.ConvertToNative(baseObject);
}
catch (Exception ex) when (!ex.IsFatal())
{
Debug.LogWarning(
$"Failed to convert {baseObject.speckle_type} - {baseObject.id}\n{ex}"
);
}
}
// Handle new GameObjects
Transform? nextParent = parent;
if (converted is GameObject go)
{
outCreatedObjects.Add(go);
nextParent = go.transform;
go.transform.SetParent(parent, 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(baseObject);
//if (baseObject["tag"] is string t) go.tag = t;
if (baseObject["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;
}
// For geometry, only traverse `elements` prop, otherwise, try and convert everything
IEnumerable potentialChildren = GetPotentialChildren(baseObject);
// Convert Children
foreach (string propertyName in potentialChildren)
{
ConvertChild(baseObject[propertyName], nextParent, predicate, outCreatedObjects);
}
}
[Obsolete]
private IEnumerable GetPotentialChildren(Base baseObject)
{
return ConverterInstance.CanConvertToNative(baseObject)
? new[] { "elements" }
: baseObject.GetMembers().Keys;
}
[Obsolete]
protected virtual void ConvertChild(
object? value,
Transform? parent,
Func predicate,
IList outCreatedObjects
)
{
foreach (Base b in GraphTraversal.TraverseMember(value))
{
RecurseTreeToNative(b, parent, predicate, outCreatedObjects);
}
}
#endregion
}
}