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