chemical_docs/pages/api/ComputedPage.tsx

151 lines
6.7 KiB
TypeScript
Raw Normal View History

2025-06-13 16:13:43 +00:00
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>
);
};