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
}
}
}
}