using Godot; using System; using System.Collections.Generic; using System.Threading; using System.Collections.Concurrent; /// /// Base class for eye tracking data producers /// public partial class EyeTrackingProducer : Node { private readonly List _observers = new List(); /// /// Register an observer to receive eye tracking data /// public void RegisterObserver(IEyeTrackingObserver observer) { if (!_observers.Contains(observer)) { _observers.Add(observer); GD.Print($"Registered observer: {observer.GetType().Name}"); } } /// /// Remove an observer /// public void RemoveObserver(IEyeTrackingObserver observer) { if (_observers.Contains(observer)) { _observers.Remove(observer); GD.Print($"Removed observer: {observer.GetType().Name}"); } } /// /// Notify all observers with new eye tracking data /// protected void NotifyObservers(EyeTrackingData data) { foreach (var observer in _observers) { observer.OnEyeDataUpdate(data); } } } /// /// Interface for eye tracking data consumers /// public interface IEyeTrackingObserver { /// /// Called when new eye tracking data is available /// void OnEyeDataUpdate(EyeTrackingData data); } /// /// Base class for third-party integrations that consume eye tracking data /// public abstract partial class EyeTrackingConsumer : Node, IEyeTrackingObserver { private bool _enabled = true; /// /// Gets or sets whether this consumer is enabled /// public bool Enabled { get => _enabled; set { if (_enabled != value) { if (value) { Enable(); } else { Disable(); } } } } /// /// Gets the last received eye tracking data /// protected EyeTrackingData LastData { get; private set; } /// /// Initializes a new instance of the EyeTrackingConsumer class /// /// The name of this consumer protected EyeTrackingConsumer(string name = null) { Name = name ?? GetType().Name; Enabled = true; } /// /// Process new eye tracking data /// public virtual void OnEyeDataUpdate(EyeTrackingData data) { if (Enabled) { LastData = data; ProcessData(data); } } /// /// Process the eye tracking data - to be implemented by subclasses /// protected abstract void ProcessData(EyeTrackingData data); /// /// Enable this consumer /// public virtual void Enable() { _enabled = true; GD.Print($"Enabled {Name}"); } /// /// Disable this consumer /// public virtual void Disable() { _enabled = false; GD.Print($"Disabled {Name}"); } } /// /// Base class for consumers that process data asynchronously /// public abstract partial class AsyncConsumer : EyeTrackingConsumer { private readonly BlockingCollection _queue; private readonly int _queueSize; private Thread _workerThread; private bool _running; /// /// Initializes a new instance of the AsyncConsumer class /// /// The name of this consumer /// The maximum size of the queue protected AsyncConsumer(string name = null, int queueSize = 100) : base(name) { _queueSize = queueSize; _queue = new BlockingCollection(queueSize); } /// /// Queue eye tracking data for async processing /// public override void OnEyeDataUpdate(EyeTrackingData data) { if (Enabled) { try { // Try to add to the queue without blocking if (!_queue.TryAdd(data)) { // Skip this update if queue is full GD.PrintErr($"{Name} queue is full - skipping update"); } } catch (Exception ex) { GD.PrintErr($"Error adding data to {Name} queue: {ex.Message}"); GD.PrintErr(ex.StackTrace); } } } /// /// Start the worker thread /// public void Start() { if (!_running) { _running = true; _workerThread = new Thread(WorkerLoop) { IsBackground = true, Name = $"{Name}Worker" }; _workerThread.Start(); GD.Print($"Started {Name} worker thread"); } } /// /// Stop the worker thread /// public virtual void Stop() { _running = false; if (_workerThread != null && _workerThread.IsAlive) { // Give the thread a chance to exit gracefully if (!_workerThread.Join(1000)) { // If it doesn't exit in time, we don't force it in this implementation // You might want to add more aggressive thread termination if needed } GD.Print($"Stopped {Name} worker thread"); } } /// /// Main worker loop - processes queued data /// private void WorkerLoop() { while (_running) { try { // Try to take from the queue with a timeout if (_queue.TryTake(out EyeTrackingData data, 100)) { ProcessData(data); } } catch (Exception ex) { GD.PrintErr($"Error in {Name} worker: {ex.Message}"); GD.PrintErr(ex.StackTrace); // In C#, we don't re-throw here as that would terminate the thread // Instead, we log the error and continue } } } }