Code/ReactivePanelComponent.cs
#if SANDBOX
using System.Diagnostics;
using Sandbox.Reactivity.Internals;
using static Sandbox.Reactivity.Reactive;
#if JETBRAINS_ANNOTATIONS
using JetBrains.Annotations;
#endif
namespace Sandbox.Reactivity;
/// <summary>
/// The reactive counterpart to <see cref="PanelComponent" /> that allows usage of reactive properties.
/// </summary>
/// <remarks>
/// Make sure you set up an effect root using <see cref="PanelRoot" /> at the top of your razor markup:
/// <code>
/// @{ using var _ = PanelRoot(); }
/// </code>
/// Engine limitations prevent this from being done automatically.
/// </remarks>
#if JETBRAINS_ANNOTATIONS
[PublicAPI]
#endif
public class ReactivePanelComponent : PanelComponent, IReactivePropertyContainer, IReactivePanel
{
private Effect? _effectRoot;
private Effect? _renderEffectRoot;
private int _version;
Effect? IReactivePanel.RenderEffectRoot
{
get => _renderEffectRoot;
set => _renderEffectRoot = value;
}
int IReactivePanel.Version
{
get => _version;
set => _version = value;
}
Dictionary<int, IProducer> IReactivePropertyContainer.Producers { get; } = [];
/// <inheritdoc cref="GameObjectExtensions.SendDirect" />
public void SendDirect<T>(T eventData)
{
GameObject?.SendDirect(eventData);
}
/// <inheritdoc cref="GameObjectExtensions.SendUp" />
public void SendUp<T>(T eventData)
{
GameObject?.SendUp(eventData);
}
/// <inheritdoc cref="GameObjectExtensions.SendDown" />
public void SendDown<T>(T eventData)
{
GameObject?.SendDown(eventData);
}
/// <inheritdoc cref="ReactiveComponent.OnEvent" />
public void OnEvent<T>(Func<T, bool> callback)
{
if (GameObject is not { } go)
{
return;
}
var manager = GameObjectEventManager.GetOrCreate(go);
Effect(() =>
{
manager.Add(callback);
return () => { manager.Remove(callback); };
});
}
protected ReactivePanelScope PanelRoot()
{
return new ReactivePanelScope(this);
}
protected sealed override void OnEnabled()
{
// component lifetimes are managed by their owning game objects, it doesn't make sense to bind it to the
// lifetime of the currently executing effect
_effectRoot = new Effect([StackTraceHidden] [DebuggerStepThrough]() =>
{
OnActivate();
return null;
},
null,
false);
_effectRoot.SetDebugInfo(DisplayInfo.For(this).Name,
DisplayInfo.For(this).Icon,
new CallLocation(GetType(), nameof(OnActivate)),
this);
_effectRoot.Run();
}
protected sealed override void OnDisabled()
{
_renderEffectRoot?.Dispose();
_renderEffectRoot = null;
_effectRoot?.Dispose();
_effectRoot = null;
base.OnDisabled();
}
protected sealed override int BuildHash()
{
return _version;
}
/// <summary>
/// Called inside an effect root when this component is enabled, allowing for effects to be created. When this
/// component is disabled, the effect root (and all of its descendants) are disposed.
/// </summary>
protected virtual void OnActivate()
{
}
}
#endif