oecs-sharp/OECS.PlayTest/ObservableCapture.cs

94 lines
3.0 KiB
C#

using OECS;
using R3;
namespace OECS.PlayTest;
/// <summary>
/// Captures all entity changes from the World's observable API and produces
/// a compact text log on demand. Call <see cref="FormatWith{T}"/> to enrich
/// component entries with human-readable values.
/// </summary>
public sealed class ObservableCapture : IDisposable
{
private readonly World _world;
private readonly IDisposable _subscription;
private readonly List<CapturedEvent> _events = new();
private readonly Dictionary<Type, Func<World, Entity, string>> _formatters = new();
public ObservableCapture(World world)
{
_world = world;
_subscription = world.ObserveEntityChanges().Subscribe(OnChange);
}
/// <summary>
/// Registers a formatter for component type <typeparamref name="T"/>.
/// When a change for this type is captured, the formatted value is
/// included in the log output.
/// </summary>
public void FormatWith<T>(Func<T, string> formatter) where T : struct
{
_formatters[typeof(T)] = (w, e) =>
{
var value = w.ReadComponent<T>(e);
return formatter(value);
};
}
/// <summary>
/// Returns the captured log as a single string with one line per change.
/// </summary>
public string GetLog() => string.Join(Environment.NewLine, GetLogLines());
/// <summary>
/// Returns the captured log as a list of lines, one per change.
/// </summary>
public List<string> GetLogLines()
{
var lines = new List<string>(_events.Count);
foreach (var evt in _events)
{
var change = evt.Change;
var kind = change.Kind.ToString();
if (change.ComponentType != null)
{
var typeName = change.ComponentType.Name;
if (evt.FormattedValue != null)
lines.Add($"{kind} {typeName} = {evt.FormattedValue} on {change.Entity}");
else
lines.Add($"{kind} {typeName} on {change.Entity}");
}
else
{
lines.Add($"{kind} on {change.Entity}");
}
}
return lines;
}
public void Dispose() => _subscription.Dispose();
private void OnChange(EntityChange change)
{
string? formattedValue = null;
if (change.ComponentType != null
&& change.Kind != ChangeKind.ComponentRemoved
&& change.Kind != ChangeKind.EntityRemoved
&& _formatters.TryGetValue(change.ComponentType, out var formatter))
{
formattedValue = formatter(_world, change.Entity);
}
_events.Add(new CapturedEvent(change, formattedValue));
}
private readonly struct CapturedEvent
{
public readonly EntityChange Change;
public readonly string? FormattedValue;
public CapturedEvent(EntityChange change, string? formattedValue)
{
Change = change;
FormattedValue = formattedValue;
}
}
}