using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using Speckle.ConnectorUnity.Models;
using Speckle.Core.Kits;
using Speckle.Core.Models;
using UnityEngine;
using UnityEngine.Events;
namespace Speckle.ConnectorUnity.Converter
{
public abstract class ComponentConverter : ScriptableObject, IComponentConverter, IComponentConverterProcess
{
///
/// Simple data container of what the component supports
///
[SerializeField] protected ComponentInfo _info;
protected ConcurrentQueue _queue;
///
/// A container for managing the conversion settings
///
public ScriptableConverterSettings settings { get; set; }
///
/// Reports true if any items are in the queue
///
public bool HasWorkToDo => _queue.Valid();
///
/// Typical speckle conversion called from
/// This method is mainly used for setting up the gameobject and component data on the main thread
///
/// The object to convert
/// The scene object with necessary component info
public GameObject ToNative(Base @base)
{
if (TryConverterArgs(@base, out var args))
{
_queue.Enqueue(args);
onQueueSizeChanged?.Invoke(_queue.Count);
return args.unityObj.gameObject;
}
SpeckleUnity.Console.Warn("Object was not converted properly");
return null;
}
///
/// Simple check to see if is supported by the converter
///
///
/// Returns true if casted type is supported
public abstract bool CanConvertToNative(Base type);
///
/// Simple check to see if is supported by the converter
///
///
/// Returns true if casted type is supported
public abstract bool CanConvertToSpeckle(Component type);
///
/// Actual conversion logic for processing speckle data into the necessary unity component(s)
///
/// Speckle Object to convert
/// Referenced scene object with component to send data to
public abstract void ToNativeConversion(Base @base, ref Component obj);
///
/// Actual conversion logic for processing speckle data into the necessary unity component(s), but with the spice of async
///
/// Speckle Object to convert
/// Referenced scene object with component to send data to
public abstract UniTask ToNativeConversionAsync(Base @base, Component obj);
///
/// Conversion logic to parse the unity components into a Speckle Object
///
/// Component to convert
/// The processed
public abstract Base ToSpeckle(Component component);
///
/// The unity type targeted for conversion
///
public abstract Type unity_type { get; }
///
/// The speckle type targeted for conversion
///
public abstract string speckle_type { get; }
///
/// Event for notify queue updates during conversion
///
public event UnityAction onQueueSizeChanged;
public virtual bool Equals(ComponentConverter other)
{
return other != null
&& other.unity_type != null
&& other.unity_type == unity_type
&& other.speckle_type.Valid()
&& other.speckle_type.Equals(speckle_type);
}
///
/// Follow up method for handling all conversion data
/// Will process speckle data at specific rate set in
///
public async UniTask PostWorkAsync()
{
SpeckleUnity.Console.Log($"{nameof(PostWorkAsync)} for {name} with {_queue.Count}");
try
{
if (_queue.Valid())
{
var chunk = new List();
while (_queue.TryDequeue(out var args))
{
Debug.Log($"Deque called {args.speckleObj}");
chunk.Add(args);
if (_queue.Count <= 0 || chunk.Count >= settings.spawnSpeed)
{
await UniTask.WhenAll(chunk.Select(x => ToNativeConversionAsync(x.speckleObj, x.unityObj)));
chunk = new List();
onQueueSizeChanged?.Invoke(_queue.Count);
// call for making sure the ui thread doesnt get blocked
await UniTask.Yield();
}
}
}
}
catch (Exception e)
{
SpeckleUnity.Console.Warn(e.Message);
}
finally
{
SpeckleUnity.Console.Log("Post Work Completed");
onQueueSizeChanged?.Invoke(0);
}
}
///
/// Protected method for handling game object creation
///
/// Object to build off of
/// Output args to be added to the queue
/// Returns true if args are valid
protected abstract bool TryConverterArgs(Base @base, out ConverterArgs args);
protected virtual BaseBehaviour GetBaseType(GameObject obj)
{
if (obj == null) obj = new GameObject();
var bb = obj.GetComponent();
if (bb == null) bb = obj.AddComponent();
return bb;
}
[Serializable]
protected struct ComponentInfo
{
public string speckleTypeName;
public string unityTypeName;
public ComponentInfo(string unity, string speckle)
{
unityTypeName = unity;
speckleTypeName = speckle;
}
}
}
public abstract class ComponentConverter : ComponentConverter
where TComponent : Component
where TBase : Base
{
public override string speckle_type => _info.speckleTypeName;
///
/// targeted for conversion to
///
public override Type unity_type => typeof(TComponent);
protected bool ValidObjects(Base @base, Component component, out TBase tBase, out TComponent tComp)
{
tBase = null;
tComp = null;
if (@base is TBase b && component is TComponent c)
{
tBase = b;
tComp = c;
}
return tBase != null && tComp != null;
}
protected virtual void OnEnable()
{
_queue = new ConcurrentQueue();
_info = new ComponentInfo(typeof(TComponent).ToString(), Activator.CreateInstance().speckle_type);
}
public override bool CanConvertToNative(Base type) => type != null && type.GetType() == typeof(TBase);
public override bool CanConvertToSpeckle(Component type) => type != null && type.GetType() == typeof(TComponent);
protected abstract Base ConvertComponent(TComponent component);
protected abstract void ConvertBase(TBase @base, ref TComponent instance);
protected override BaseBehaviour GetBaseType(GameObject obj)
{
var comp = typeof(TComponent);
if (obj != null && comp.IsSubclassOf(typeof(BaseBehaviour)) || comp == typeof(BaseBehaviour))
return obj.GetComponent() as BaseBehaviour;
return base.GetBaseType(obj);
}
public override void ToNativeConversion(Base @base, ref Component component)
{
if (ValidObjects(@base, component, out var converterObj, out var converterComp))
ConvertBase(converterObj, ref converterComp);
}
public override async UniTask ToNativeConversionAsync(Base @base, Component component) => await UniTask.Create(() =>
{
ToNativeConversion(@base, ref component);
return UniTask.CompletedTask;
});
protected override bool TryConverterArgs(Base @base, out ConverterArgs args)
{
args = null;
if (CanConvertToNative(@base) && @base is TBase compBase)
{
var component = CreateComponentInstance();
GetBaseType(component.gameObject).Store(compBase);
args = new ConverterArgs(compBase, component);
}
else
{
SpeckleUnity.Console.Warn($"{@base.speckle_type} somehow ended up in the wrong converter!\n{this}");
}
return args != null;
}
public override Base ToSpeckle(Component component) => CanConvertToSpeckle(component) ? ConvertComponent((TComponent)component) : null;
public TComponent CreateComponentInstance(string n = null) => GetBaseType(
new GameObject(n.Valid() ? n : speckle_type).AddComponent().gameObject).GetComponent();
}
}