Core/Cell.cs
using System;
namespace Goo;
// Non-generic base so the reconciler can hold heterogeneous cells without knowing TRoot.
public abstract class Cell
{
Action? _rebuild;
// Set by the reconciler during DiffCell, before ExpandInto runs.
internal void SetRebuildHook(Action? rebuild) => _rebuild = rebuild;
// Marks the owning root dirty. Call from event handlers after mutating state.
public void Rebuild() => _rebuild?.Invoke();
// Writes the cell's built Blob into frame; overridden in Cell<TRoot>, monomorphized so no boxing.
internal abstract void ExpandInto(ref Frame frame);
// Mount a cell into a parent's child list or as a root; users never name CellElement.
public static CellElement Mount<TCell>(string? key = null, Action<TCell>? configure = null)
where TCell : Cell, new()
{
Action<Cell>? boxed = configure is null
? null
: instance => configure((TCell)instance);
return new CellElement(typeof(TCell), static () => new TCell(), boxed, key);
}
}
public abstract class Cell<TRoot> : Cell where TRoot : struct, IBlob
{
protected abstract TRoot Build();
internal sealed override void ExpandInto(ref Frame frame)
{
TRoot root = Build();
root.WriteTo(ref frame); // direct, monomorphized; no boxing
}
}
// Internal element struct the reconciler diffs. Mount<TCell> returns it; users never spell it.
public readonly record struct CellElement : IBlob
{
public static BlobKind Kind => BlobKind.Cell;
public string? Key { get; }
readonly Type _cellType;
readonly Func<Cell> _factory;
readonly Action<Cell>? _configure;
internal CellElement(Type cellType, Func<Cell> factory, Action<Cell>? configure, string? key)
{
_cellType = cellType;
_factory = factory;
_configure = configure;
Key = key;
}
void IBlob.WriteTo(ref Frame frame)
{
frame = default;
frame.Kind = BlobKind.Cell;
frame.Key = Key;
frame.CellType = _cellType;
frame.CellFactory = _factory;
frame.Configure = _configure;
}
}