Files
Imhotep/packages/imhotep-state/src/transitions.ts
T

128 lines
4.3 KiB
TypeScript

import type { GeometrySnapshot } from './snapshots.js'
import type { StateMaterializer } from './states.js'
// ---------------------------------------------------------------------------
// Transition Sampling Configuration
// ---------------------------------------------------------------------------
/** Mode that determines how a transition is sampled. */
export type TransitionMode = 'keyframes' | 'range' | 'transition'
/**
* Configuration for sampling a transition or animation.
*
* The materializer uses this to decide which time slices to capture.
*/
export interface TransitionSampleConfig {
/** Sampling strategy. */
mode: TransitionMode
/** Target element selector for the transition. */
selector: string
/** Explicit keyframe timestamps in ms (keyframes mode). */
keyframes?: number[]
/** Start of the sampled range in ms (range mode). */
from?: number
/** End of the sampled range in ms (range mode). */
to?: number
/** Total duration of the transition in ms (transition mode). */
duration?: number
/** Number of evenly-spaced samples (transition mode). */
sampleCount?: number
}
// ---------------------------------------------------------------------------
// Transition Sampler
// ---------------------------------------------------------------------------
/**
* Contract for sampling snapshots across a temporal range.
*
* The sampler receives a state materializer and a capture callback.
* It is responsible for scrubbing or waiting to each sample time and
* invoking the callback to produce a geometry snapshot.
*/
export interface TransitionSampler {
/**
* Sample the transition according to `config`.
*
* @param config - Sampling configuration.
* @param materializer - State materializer used to set up the initial state.
* @param capture - Callback that captures a single geometry snapshot.
* @returns Array of snapshots in chronological order.
*/
sample(
config: TransitionSampleConfig,
materializer: StateMaterializer,
capture: () => Promise<GeometrySnapshot>
): Promise<GeometrySnapshot[]>
}
// ---------------------------------------------------------------------------
// Sampling Helpers
// ---------------------------------------------------------------------------
/**
* Resolve the list of sample times (in ms) from a configuration.
*
* - keyframes: returns the provided keyframe array.
* - range: returns [from, to].
* - transition: returns evenly-spaced samples across duration.
*/
export function resolveSampleTimes(config: TransitionSampleConfig): number[] {
switch (config.mode) {
case 'keyframes': {
return config.keyframes?.length ? config.keyframes : [0]
}
case 'range': {
const from = config.from ?? 0
const to = config.to ?? from
return from === to ? [from] : [from, to]
}
case 'transition': {
const duration = config.duration ?? 300
const count = Math.max(2, config.sampleCount ?? 3)
const step = duration / (count - 1)
return Array.from({ length: count }, (_, i) => Math.round(i * step))
}
default: {
// Exhaustiveness fallback.
return [0]
}
}
}
// ---------------------------------------------------------------------------
// Default Transition Sampler
// ---------------------------------------------------------------------------
/**
* Creates a transition sampler that captures a snapshot at each sample time.
*
* This is a simplified implementation suitable for browser-backed
* measurement. A production sampler may use CDP animation scrubbing
* or requestAnimationFrame timing for higher precision.
*/
export function createTransitionSampler(): TransitionSampler {
return {
async sample(config, _materializer, capture) {
const times = resolveSampleTimes(config)
const snapshots: GeometrySnapshot[] = []
for (const time of times) {
// In a full implementation this would seek the animation to `time`
// before capturing. Here we capture sequentially and annotate the
// snapshot metadata with the intended sample time.
const snapshot = await capture()
if (typeof snapshot.metadata.durationMs === 'number') {
snapshot.metadata.durationMs = time
} else {
snapshot.metadata.durationMs = time
}
snapshots.push(snapshot)
}
return snapshots
},
}
}