using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using OmiLAXR.Composers;
using OmiLAXR.Endpoints;
using OmiLAXR.Listeners;
using OmiLAXR.Filters;
using OmiLAXR.TrackingBehaviours;
using UnityEngine;
using Object = UnityEngine.Object;
namespace OmiLAXR
{
///
/// Pipeline System containing many stages.
///
[AddComponentMenu("OmiLAXR / Core / Pipeline")]
[DefaultExecutionOrder(0)]
public class Pipeline : MonoBehaviour
{
public bool IsRunning => gameObject.activeSelf;
public Actor actor;
public Instructor instructor;
public readonly List Listeners = new List();
public readonly List DataProviders = new List();
public readonly List TrackingBehaviours = new List();
public readonly List Filters = new List();
public readonly Dictionary> Actions = new Dictionary>();
public readonly Dictionary> Gestures = new Dictionary>();
public List Extensions = new List();
public ActorDataProvider[] ActorDataProviders { get; protected set; }
#if UNITY_2023_1_OR_NEWER
public static T GetPipeline() where T : Pipeline
=> FindFirstObjectByType();
#else
public static T GetPipeline() where T : Pipeline
=> FindObjectOfType();
#endif
#if UNITY_2023_1_OR_NEWER
public static Pipeline GetAll() => FindFirstObjectByType();
#else
public static Pipeline GetAll() => FindObjectOfType();
#endif
public T GetDataProvider() where T : DataProvider
=> DataProviders.OfType().Select(dp => dp as T).FirstOrDefault();
public T GetTrackingBehaviour() where T : PipelineComponent, ITrackingBehaviour
=> TrackingBehaviours.OfType().Select(dp => dp as T).FirstOrDefault();
public T GetFilters() where T : Filter
=> Filters.OfType().Select(dp => dp as T).FirstOrDefault();
public T GetListener() where T : Listener
=> Listeners.OfType().Select(listener => listener as T).FirstOrDefault();
public event Action AfterInit;
public event Action BeforeStartedPipeline;
public event Action AfterStartedPipeline;
public event Action OnCollect;
public event Action AfterFoundObjects;
public event Action AfterFilteredObjects;
public event ComposerAction AfterComposedStatement;
public event EndpointAction BeforeSendStatement;
public event EndpointAction AfterSendStatement;
public event Action BeforeStoppedPipeline;
public event Action AfterStoppedPipeline;
public readonly List trackingObjects = new List();
private bool _cleanupCalled = false;
private bool _startupCalled = false;
public void Add(PipelineComponent comp)
{
var type = comp.GetType();
if (type.IsSubclassOf(typeof(Listener)))
{
if (!Listeners.Contains(comp))
Listeners.Add(comp as Listener);
}
else if (type.IsSubclassOf(typeof(Filter)))
{
if (!Filters.Contains(comp))
Filters.Add(comp as Filter);
}
else if (type.IsSubclassOf(typeof(ITrackingBehaviour)))
{
var tb = comp as ITrackingBehaviour;
if (!TrackingBehaviours.Contains(tb))
TrackingBehaviours.Add(tb);
}
else if (type.IsSubclassOf(typeof(IPipelineExtension)))
{
var pc = comp as IPipelineComponent;
if (pc != null)
{
if (!Extensions.Contains(pc))
{
var ext = pc as IPipelineExtension;
ext?.Extend(this);
Extensions.Add(ext);
}
}
}
}
private Actor FindActor()
{
var actorGroup = GetComponent();
return actorGroup ?? GetComponent();
}
private void Init()
{
if (actor == null)
actor = FindActor();
ActorDataProviders = GetComponentsInChildren(false);
TrackingBehaviours.AddRange(GetComponentsInChildren(false));
// Find available listeners
Listeners.AddRange(GetComponentsInChildren(false));
// Find available data providers
Filters.AddRange(GetComponentsInChildren(false));
// Find available data providers
#if UNITY_2023_1_OR_NEWER
DataProviders.AddRange(FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None));
#else
DataProviders.AddRange(FindObjectsOfType(false));
#endif
// Bind after and before send events
foreach (var dp in DataProviders)
{
foreach (var c in dp.Composers)
{
c.AfterComposed += AfterComposed;
}
foreach (var ep in dp.Endpoints)
{
ep.OnSendingStatement += OnSendingStatement;
ep.OnSentStatement += OnSentStatement;
}
}
var composersCount = DataProviders.Aggregate(0, (i, provider) => i + provider.Composers.Count);
var hooksCount = DataProviders.Aggregate(0, (i, provider) => i + provider.Hooks.Count);
OnCollect?.Invoke(this);
Log($"Initialized with {Listeners.Count} listeners, {Filters.Count} filters, {TrackingBehaviours.Count} tracking behaviours, {composersCount} composers, {hooksCount} hooks and {DataProviders.Count} data providers" );
AfterInit?.Invoke(this);
}
private void AfterComposed(IComposer composer, IStatement statement)
=> AfterComposedStatement?.Invoke(composer, statement);
private void OnSendingStatement(Endpoint endpoint, IStatement statement)
=> BeforeSendStatement?.Invoke(endpoint, statement);
private void OnSentStatement(Endpoint endpoint, IStatement statement)
=> AfterSendStatement?.Invoke(endpoint, statement);
private void CollectGesturesAndActions()
{
var tbs = TrackingBehaviours.ToArray();
Actions.Clear();
Gestures.Clear();
foreach (var ts in tbs)
{
var properties = ts.GetTrackingBehaviourEvents();
foreach (var p in properties)
{
var ev = p.GetValue(ts) as ITrackingBehaviourEvent;
var actionAttrs = p.GetCustomAttributes();
var gestureAttrs = p.GetCustomAttributes();
foreach (var actionAttr in actionAttrs)
{
if (Actions.ContainsKey(actionAttr.Name))
Actions[actionAttr.Name].Add(ev);
else
Actions.Add(actionAttr.Name, new List() { ev });
}
foreach (var gestureAttr in gestureAttrs)
{
if (Gestures.ContainsKey(gestureAttr.Name))
Gestures[gestureAttr.Name].Add(ev);
else
Gestures.Add(gestureAttr.Name, new List() { ev });
}
}
}
}
protected void Log(string message, params object[] ps)
=> DebugLog.OmiLAXR.Print($"<{GetType().Name}> " + message);
private void OnEnable()
{
Startup();
}
private void Startup()
{
if (_startupCalled)
return;
Init();
_startupCalled = true;
CollectGesturesAndActions();
trackingObjects.Clear();
// 1) Start listening for events
foreach (var listener in Listeners)
{
listener.OnFoundObjects += FoundObjects;
if (listener.enabled)
listener.StartListening();
}
BeforeStartedPipeline?.Invoke(this);
Log($"Started Pipeline with {trackingObjects.Count} tracking target objects...");
AfterStartedPipeline?.Invoke(this);
_cleanupCalled = false;
}
private void OnDisable()
{
Cleanup();
}
private void Cleanup()
{
if (_cleanupCalled)
return;
_cleanupCalled = true;
BeforeStoppedPipeline?.Invoke(this);
trackingObjects.Clear();
// clear listenings
foreach (var listener in Listeners.Where(l => l.enabled))
{
if (listener == null)
continue;
listener.OnFoundObjects -= FoundObjects;
}
foreach (var endpoint in DataProviders.SelectMany(dp => dp.Endpoints))
{
if (endpoint == null || !endpoint.enabled)
continue;
endpoint.StopSending();
}
Log("Stopped Pipeline!");
AfterStoppedPipeline?.Invoke(this);
foreach (var dp in DataProviders)
{
foreach (var c in dp.Composers)
{
c.AfterComposed -= AfterComposed;
}
foreach (var ep in dp.Endpoints)
{
ep.OnSendingStatement -= OnSendingStatement;
ep.OnSentStatement -= OnSentStatement;
}
}
_startupCalled = false;
}
public void StartPipeline()
{
if (IsRunning)
return;
gameObject.SetActive(true);
Startup();
}
private void OnApplicationQuit()
{
Cleanup();
}
private void OnDestroy()
{
Cleanup();
}
public void StopPipeline()
{
if (!IsRunning)
return;
Cleanup();
_startupCalled = false;
gameObject.SetActive(false);
}
public void SetDisabledActions(bool disabled, IEnumerable names = null)
{
foreach (var pair in Actions.Where(p => names == null || names.Contains(p.Key)))
{
pair.Value.ForEach(v => v.IsDisabled = disabled);
}
}
public void SetDisabledGestures(bool disabled, IEnumerable names = null)
{
foreach (var pair in Gestures.Where(p => names == null || names.Contains(p.Key)))
{
pair.Value.ForEach(v => v.IsDisabled = disabled);
}
}
private void FoundObjects(Object[] objects)
{
AfterFoundObjects?.Invoke(objects);
// 2) apply all filters
objects = Filters.Where(f => f.enabled).Aggregate(objects, (gos, filter) => filter.Pass(gos));
AfterFilteredObjects?.Invoke(objects);
trackingObjects.AddRange(objects);
}
}
}