Reactive Variables
Learn about Functional Reactive Programming and composable transformation pipelines.
Functional Reactive Programming (FRP)
ClosureBT embraces Functional Reactive Programming principles through reactive variables and transformation pipelines. Variables are signal-based data streams that automatically propagate changes through a series of transformations.
What is FRP?
FRP treats variables as streams of values over time. When a source variable changes, that change flows through a pipeline of transformations automatically, creating a declarative data flow.
Think of it like Excel formulas: when a cell changes, all dependent cells recalculate automatically.
UsePipe: Composing Transformations
UsePipe chains multiple transformation operations on a reactive variable, creating a functional pipeline
where data flows from left to right through each stage:
var mousePos = UseEveryTick(MousePlaneCastPosition);
// Create a pipeline: throttle → buffer → transform
var mousePosAverage = UsePipe(mousePos,
v => UseThrottle(0.025f, v), // Stage 1: Sample every 25ms
v => UseRollingBuffer(15, false, v), // Stage 2: Keep last 15 values
v => UseSelect(v, buffer => // Stage 3: Calculate average
{
var avg = Vector3.zero;
foreach (var pos in buffer)
avg += pos;
return avg / buffer.Count;
}));
// Result: mousePosAverage automatically updates with smoothed mouse position var mousePos = UseEveryTick(MousePlaneCastPosition);
// Create a pipeline: throttle → buffer → transform
var mousePosAverage = UsePipe(mousePos,
v => UseThrottle(0.025f, v), // Stage 1: Sample every 25ms
v => UseRollingBuffer(15, false, v), // Stage 2: Keep last 15 values
v => UseSelect(v, buffer => // Stage 3: Calculate average
{
var avg = Vector3.zero;
foreach (var pos in buffer)
avg += pos;
return avg / buffer.Count;
}));
// Result: mousePosAverage automatically updates with smoothed mouse position The visualization below shows how data flows through a pipeline:
UsePipe Data Flow
Each stage in the pipeline transforms the incoming data stream:
- Source: The origin of data (e.g., mouse position, sensor input)
- Throttle: Limits update frequency to prevent excessive computations
- Buffer: Collects multiple values for aggregate operations
- Select/Transform: Maps values to new forms (like LINQ's Select)
Use Methods: Reactive Operators
ClosureBT provides a library of Use* methods for building reactive pipelines.
These are similar to RxJS operators or LINQ methods, designed for composing reactive data flows:
UseEveryTick
Samples a function or variable every tick, creating a reactive stream.
var mousePos = UseEveryTick(MouseWorldPosition);
var health = UseEveryTick(() => player.health); var mousePos = UseEveryTick(MouseWorldPosition);
var health = UseEveryTick(() => player.health); UseThrottle
Limits signal propagation to once per time period (in seconds).
var mousePos = UseEveryTick(MousePosition);
var throttled = UseThrottle(0.1f, mousePos); // Max once per 100ms var mousePos = UseEveryTick(MousePosition);
var throttled = UseThrottle(0.1f, mousePos); // Max once per 100ms UseRollingBuffer
Maintains a sliding window of the last N values.
var positions = UseRollingBuffer(10, false, mousePos);
// positions.Value is a List<Vector3> with last 10 positions var positions = UseRollingBuffer(10, false, mousePos);
// positions.Value is a List<Vector3> with last 10 positions UseSelect
Transforms each value through a projection function (like LINQ Select).
var health = UseEveryTick(() => player.health);
var healthPercent = UseSelect(health, h => h / 100f); var health = UseEveryTick(() => player.health);
var healthPercent = UseSelect(health, h => h / 100f); UseValueDidChange
Returns true for one tick when the source variable signals a change.
var target = UseEveryTick(() => currentTarget);
var targetChanged = UseValueDidChange(target);
OnTick(() => {
if (targetChanged.Value)
Debug.Log("New target acquired!");
}); var target = UseEveryTick(() => currentTarget);
var targetChanged = UseValueDidChange(target);
OnTick(() => {
if (targetChanged.Value)
Debug.Log("New target acquired!");
}); UseTimePredicateElapsed
Tracks how long (in seconds) a condition has been true.
var isMoving = UseEveryTick(() => velocity.magnitude > 0.1f);
var movingDuration = UseTimePredicateElapsed(isMoving, _ => isMoving.Value);
// movingDuration.Value = seconds the entity has been moving var isMoving = UseEveryTick(() => velocity.magnitude > 0.1f);
var movingDuration = UseTimePredicateElapsed(isMoving, _ => isMoving.Value);
// movingDuration.Value = seconds the entity has been moving Complete Use Methods Reference
All available reactive operators in ClosureBT:
UseEveryTick UseThrottle UseDebounce UseRollingBuffer UseSelect UseWhere UseScan UseValueDidChange UseDistinctUntilChanged UseCountChanged UseTimePredicateElapsed UseTicksElapsed UseElapsed UseWindowTime UsePipe