GooPanel.cs
using System.Collections.Generic;
using Sandbox;
using Sandbox.UI;
namespace Goo;
public abstract class GooPanel<TRoot> : PanelComponent, IHotloadManaged where TRoot : struct, IBlob
{
Fiber? _fiber;
readonly BuildContext _ctx = new();
bool _needsBuild = true;
protected abstract TRoot Build();
public void Rebuild() => _needsBuild = true;
/// <summary>Override to drive per-frame state (animations, polled input).
/// Return true while a rebuild is still needed next frame (e.g. an animation is in motion).
/// Runs every frame, before the build gate.</summary>
protected virtual bool Tick(float dt) => false;
/// <summary>When true (default), a rebuild is requested automatically after any event
/// handler on this panel's tree fires. Set false to restore fully manual Rebuild() control.</summary>
protected virtual bool AutoRebuildOnEvents => true;
// Hotload hook. Engine creates a fresh instance per Component on hotload, then
// Cecil-migrates field values from the prior instance. We need to trigger a diff
// against the new Build() output so the panel reflects edited source.
void IHotloadManaged.Created(IReadOnlyDictionary<string, object> state) => _needsBuild = true;
protected override void OnEnabled()
{
// Engine destroys Panel on disable and creates a fresh empty one on enable.
// Drop our fiber so the next diff is a full mount against the new Panel.
_fiber = null;
_needsBuild = true;
}
protected override void OnUpdate()
{
if (Panel == null) return;
// Per-frame state advances every frame, independent of the build gate.
if (Tick(Time.Delta)) _needsBuild = true;
if (!_needsBuild) return;
var prev = BuildContext._current;
BuildContext._current = _ctx;
_ctx.RootRebuild = Rebuild;
_ctx.AutoRebuildOnEvents = AutoRebuildOnEvents;
try
{
TRoot next = Build();
var result = Reconciler.Diff(ref _fiber, in next);
foreach (var w in result.Warnings) Log.Warning(w);
Applier.Apply(Panel, result.Ops);
}
finally
{
_ctx.ReturnAll();
BuildContext._current = prev;
_needsBuild = false;
}
}
// Goo manages Panel.Children directly via Diff/Apply; skip Razor's render-tree build path.
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder)
{
}
}