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(computeFn: () => T, cleanupFn?: (oldValue: T) => ()): Computed', 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', 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 (

Computed value updates are batched and processed asynchronously by the Chemical scheduler, typically at the end of the current frame or scheduler tick.

If a dependency changes multiple times within a single frame, the Computed will only re-calculate once using the most recent (final) values of its dependencies when the scheduler processes the batch.

Consequently, reading a Computed value (e.g., myComputed:get()) immediately after one of its dependencies is set will yield its old value. Effects and other Computeds depending on it will receive the updated value in the subsequent scheduler cycle.

); };