151 lines
6.7 KiB
TypeScript
151 lines
6.7 KiB
TypeScript
|
|
||
|
import React from 'react';
|
||
|
import { ContentPage } from '../../components/ContentPage';
|
||
|
import { InfoPanel } from '../../components/InfoPanel';
|
||
|
import { ApiSection } from '../../types';
|
||
|
|
||
|
const computedApi: ApiSection[] = [
|
||
|
{
|
||
|
title: 'Chemical.Computed',
|
||
|
description: 'Creates a reactive value that is derived from other reactive Values or Computeds. It automatically recalculates its value whenever any of its dependencies change.',
|
||
|
entries: [
|
||
|
{
|
||
|
signature: 'Chemical.Computed<T>(computeFn: () => T, cleanupFn?: (oldValue: T) => ()): Computed<T>',
|
||
|
description: 'Constructs a new Computed value.',
|
||
|
parameters: [
|
||
|
{ name: 'computeFn', type: '() => T', description: 'A function that calculates the value. Any reactive objects accessed via `:get()` inside this function become dependencies.' },
|
||
|
{ name: 'cleanupFn', type: '((oldValue: T) => ())?', description: "An optional function. When a dependency of the `Computed` changes, this `cleanupFn` is called with the `Computed`'s *previous* value right *before* the main `computeFn` is re-executed to calculate the new value. The `cleanupFn` is also called with the last computed value when the `Computed` object itself is destroyed." },
|
||
|
],
|
||
|
returns: { type: 'Computed<T>', description: 'A new Computed object.' },
|
||
|
example: `
|
||
|
local Chemical = require(game.ReplicatedStorage.Chemical)
|
||
|
local Value = Chemical.Value
|
||
|
local Computed = Chemical.Computed
|
||
|
local Effect = Chemical.Effect
|
||
|
|
||
|
local price = Value(100)
|
||
|
local discount = Value(0.1) -- 10% discount
|
||
|
|
||
|
local finalPrice = Computed(function()
|
||
|
print("Debug: Recalculating final price...")
|
||
|
return price:get() * (1 - discount:get())
|
||
|
end, function(oldFinalPrice)
|
||
|
-- This cleanupFn receives the value 'finalPrice' held *before* this re-computation.
|
||
|
print("Debug: Cleanup - Old final price was:", oldFinalPrice)
|
||
|
end)
|
||
|
|
||
|
-- The 'finalPrice' Computed has its 'computeFn' run once upon creation (or lazily on first :get()).
|
||
|
print("Initial final price (after first computation):", finalPrice:get())
|
||
|
-- Expected:
|
||
|
-- Debug: Recalculating final price...
|
||
|
-- Initial final price (after first computation): 90
|
||
|
|
||
|
price:set(200) -- Dependency changed
|
||
|
-- IMPORTANT: 'finalPrice:get()' here will return the *old* value (90)
|
||
|
-- because the 'finalPrice' Computed has not re-evaluated yet.
|
||
|
-- Re-evaluation is batched and occurs in the Chemical scheduler's next tick.
|
||
|
print("Final price immediately after price:set(200):", finalPrice:get())
|
||
|
-- Expected Output: Final price immediately after price:set(200): 90
|
||
|
|
||
|
-- An Effect depending on 'finalPrice' will see the updated value in a subsequent scheduler tick.
|
||
|
-- The scheduler will typically:
|
||
|
-- 1. Detect 'price' changed.
|
||
|
-- 2. Mark 'finalPrice' (Computed) as dirty.
|
||
|
-- 3. In the next update cycle, process 'finalPrice':
|
||
|
-- a. Call cleanupFn for 'finalPrice' with its old value (90).
|
||
|
-- b. Call computeFn for 'finalPrice' to get the new value (180).
|
||
|
-- 4. Mark 'priceEffect' (Effect) as dirty because 'finalPrice' changed.
|
||
|
-- 5. Process 'priceEffect', which will then :get() the new value of 'finalPrice' (180).
|
||
|
local priceEffect = Effect(function()
|
||
|
print("Effect: finalPrice is now:", finalPrice:get())
|
||
|
end)
|
||
|
|
||
|
-- Expected output sequence from scheduler after price:set(200):
|
||
|
-- Debug: Cleanup - Old final price was: 90
|
||
|
-- Debug: Recalculating final price...
|
||
|
-- Effect: finalPrice is now: 180
|
||
|
`,
|
||
|
},
|
||
|
],
|
||
|
},
|
||
|
{
|
||
|
title: 'Computed Methods',
|
||
|
entries: [
|
||
|
{
|
||
|
signature: 'computed:get(): T',
|
||
|
description: 'Retrieves the current (potentially cached) value of the Computed. If called within another Computed or Effect, it establishes a dependency. Does not trigger re-computation itself; re-computation is handled by the scheduler when dependencies change.',
|
||
|
returns: { type: 'T', description: 'The current computed value.' },
|
||
|
example: `
|
||
|
local Chemical = require(game.ReplicatedStorage.Chemical)
|
||
|
local Value = Chemical.Value
|
||
|
local Computed = Chemical.Computed
|
||
|
|
||
|
local x = Value(5)
|
||
|
local y = Value(10)
|
||
|
local sum = Computed(function() return x:get() + y:get() end)
|
||
|
|
||
|
local currentSum = sum:get() -- currentSum is 15
|
||
|
`,
|
||
|
},
|
||
|
{
|
||
|
signature: 'computed:destroy()',
|
||
|
description: 'Destroys the Computed object. If a `cleanupFn` was provided during construction, it will be called with the last computed value. This also removes its dependencies on other reactive objects.',
|
||
|
example: `
|
||
|
local Chemical = require(game.ReplicatedStorage.Chemical)
|
||
|
local Value = Chemical.Value
|
||
|
local Computed = Chemical.Computed
|
||
|
|
||
|
local name = Value("Test")
|
||
|
local upperName = Computed(function() return name:get():upper() end, function(oldUpper) print("Cleaning up:", oldUpper) end)
|
||
|
-- ...
|
||
|
upperName:destroy() -- "Cleaning up: TEST" will be printed if "TEST" was the last computed value.
|
||
|
`,
|
||
|
},
|
||
|
{
|
||
|
signature: 'computed:clean()',
|
||
|
description: 'Explicitly runs the `cleanupFn` if one was provided, using the current value of the Computed. This is typically managed internally but can be called manually if needed.',
|
||
|
example: `
|
||
|
local Chemical = require(game.ReplicatedStorage.Chemical)
|
||
|
local Value = Chemical.Value
|
||
|
local Computed = Chemical.Computed
|
||
|
|
||
|
local data = Value({ value = 1 })
|
||
|
local processed = Computed(function()
|
||
|
return data:get().value * 2
|
||
|
end, function(oldProcessedValue)
|
||
|
print("Cleaning processed value:", oldProcessedValue)
|
||
|
end)
|
||
|
|
||
|
processed:get() -- Ensure it's computed once, current value is 2
|
||
|
processed:clean() -- "Cleaning processed value: 2"
|
||
|
`,
|
||
|
notes: "This method is less commonly used directly. Destruction or dependency changes usually handle cleanup.",
|
||
|
},
|
||
|
],
|
||
|
},
|
||
|
];
|
||
|
|
||
|
export const ComputedPage: React.FC = () => {
|
||
|
return (
|
||
|
<ContentPage title="Chemical.Computed" sections={computedApi}>
|
||
|
<InfoPanel type="note" title="Execution Timing & Batching">
|
||
|
<p>
|
||
|
Computed value updates are batched and processed asynchronously by the Chemical scheduler,
|
||
|
typically at the end of the current frame or scheduler tick.
|
||
|
</p>
|
||
|
<p className="mt-2">
|
||
|
If a dependency changes multiple times within a single frame, the Computed will only
|
||
|
re-calculate <strong>once</strong> using the <strong>most recent (final) values</strong> of its
|
||
|
dependencies when the scheduler processes the batch.
|
||
|
</p>
|
||
|
<p className="mt-2">
|
||
|
Consequently, reading a Computed value (e.g., <code>myComputed:get()</code>) immediately
|
||
|
after one of its dependencies is set will yield its <strong>old value</strong>. Effects and
|
||
|
other Computeds depending on it will receive the updated value in the subsequent
|
||
|
scheduler cycle.
|
||
|
</p>
|
||
|
</InfoPanel>
|
||
|
</ContentPage>
|
||
|
);
|
||
|
};
|