Core/BuildContext.cs
using System;
using System.Collections.Generic;
namespace Goo;
internal sealed class BuildContext
{
[ThreadStatic] internal static BuildContext? _current;
public static BuildContext Current => _current
?? throw new InvalidOperationException("No active BuildContext; struct intent constructed outside GooPanel.Tick?");
// Published by GooPanel.OnUpdate before Build(), read by the static Applier during Apply
// (BuildContext._current is this instance for the whole OnUpdate body). RootRebuild is the
// panel's Rebuild(); AutoRebuildOnEvents gates whether event handlers auto-request a rebuild.
internal Action? RootRebuild;
internal bool AutoRebuildOnEvents = true;
readonly Stack<FrameList> _frameListPool = new();
readonly Stack<StyleList> _styleListPool = new();
readonly Stack<List<int>> _pathPool = new();
readonly Stack<Children> _childrenPool = new();
readonly Stack<Dictionary<string, Fiber>> _keyedDictPool = new();
readonly Stack<HashSet<string>> _keySetPool = new();
readonly Stack<List<Op>> _opListPool = new();
readonly Stack<List<string>> _warnListPool = new();
// HostPath int[] pool keyed by length. Index 0 unused (length-0 paths use Array.Empty<int>()).
// _hostPathPool[n] holds reusable int[n] slabs.
readonly List<Stack<int[]>> _hostPathPool = new();
readonly List<FrameList> _rentedFrameLists = new();
readonly List<StyleList> _rentedStyleLists = new();
readonly List<Children> _rentedChildren = new();
// Diff result lists outlive ReturnAll; the previous Diff's lists are returned to the pool
// at the start of the NEXT Diff. This keeps DiffResult valid for callers that read it
// after a try/finally ReturnAll block.
List<Op>? _pendingOpReturn;
List<string>? _pendingWarnReturn;
// HostPath snapshots live inside emitted Ops and are read by callers after ReturnAll, so
// they obey the same delayed-return scheme: the previous Diff's snapshots get released to
// the pool at the START of the next Diff (via BeginDiff), not on ReturnAll.
List<int[]> _pendingHostPathReturn = new();
List<int[]> _activeHostPathRented = new();
// Bumped on every ReturnAll. Stamped onto each rented Children so user code that
// holds a Container reference past its build can be detected (see Children.EnsureValid).
internal int _currentBuildId;
public FrameList RentFrameList()
{
var list = _frameListPool.Count > 0 ? _frameListPool.Pop() : new FrameList();
_rentedFrameLists.Add(list);
return list;
}
public Children RentChildren()
{
var c = _childrenPool.Count > 0 ? _childrenPool.Pop() : new Children();
c._list = RentFrameList();
c._buildId = _currentBuildId;
_rentedChildren.Add(c);
return c;
}
public StyleList RentStyleList()
{
var list = _styleListPool.Count > 0 ? _styleListPool.Pop() : new StyleList();
_rentedStyleLists.Add(list);
return list;
}
// Path pool returned per-Diff (lifetime is one Reconciler.Diff call, not the whole frame).
public List<int> RentPath() => _pathPool.Count > 0 ? _pathPool.Pop() : new List<int>();
public void ReturnPath(List<int> p) { p.Clear(); _pathPool.Push(p); }
// Scratch dict/set rented and returned synchronously inside Reconciler.DiffKeyed.
public Dictionary<string, Fiber> RentKeyedDict() =>
_keyedDictPool.Count > 0 ? _keyedDictPool.Pop() : new Dictionary<string, Fiber>();
public void ReturnKeyedDict(Dictionary<string, Fiber> d) { d.Clear(); _keyedDictPool.Push(d); }
public HashSet<string> RentKeySet() =>
_keySetPool.Count > 0 ? _keySetPool.Pop() : new HashSet<string>();
public void ReturnKeySet(HashSet<string> s) { s.Clear(); _keySetPool.Push(s); }
// Recycles the previous Diff's list, then rents the next. Caller of the previous Diff
// must not read the result after the next Diff begins.
internal List<Op> RentOpList()
{
if (_pendingOpReturn != null) { _pendingOpReturn.Clear(); _opListPool.Push(_pendingOpReturn); }
var l = _opListPool.Count > 0 ? _opListPool.Pop() : new List<Op>();
_pendingOpReturn = l;
return l;
}
internal List<string> RentWarningList()
{
if (_pendingWarnReturn != null) { _pendingWarnReturn.Clear(); _warnListPool.Push(_pendingWarnReturn); }
var l = _warnListPool.Count > 0 ? _warnListPool.Pop() : new List<string>();
_pendingWarnReturn = l;
return l;
}
// Drains the previous Diff's HostPath snapshots back to the pool, then swaps the active
// and pending tracking lists. Called from Reconciler.Diff before any RentHostPath calls.
internal void BeginDiff()
{
for (int i = 0; i < _pendingHostPathReturn.Count; i++)
{
var arr = _pendingHostPathReturn[i];
_hostPathPool[arr.Length].Push(arr);
}
_pendingHostPathReturn.Clear();
(_pendingHostPathReturn, _activeHostPathRented) = (_activeHostPathRented, _pendingHostPathReturn);
}
internal int[] RentHostPath(List<int> path)
{
int len = path.Count;
if (len == 0) return Array.Empty<int>();
while (_hostPathPool.Count <= len) _hostPathPool.Add(new Stack<int[]>());
var stack = _hostPathPool[len];
var arr = stack.Count > 0 ? stack.Pop() : new int[len];
for (int i = 0; i < len; i++) arr[i] = path[i];
_activeHostPathRented.Add(arr);
return arr;
}
internal void ReturnAll()
{
foreach (var l in _rentedFrameLists) { l.Reset(); _frameListPool.Push(l); }
foreach (var l in _rentedStyleLists) { l.Reset(); _styleListPool.Push(l); }
foreach (var c in _rentedChildren) { c._list = null; _childrenPool.Push(c); }
_rentedFrameLists.Clear();
_rentedStyleLists.Clear();
_rentedChildren.Clear();
_currentBuildId++;
}
}