Hylograph.Scene.Engine
- Package
- purescript-hylograph-simulation
- Repository
- afcondon/purescript-hylograph-simulation
Scene Engine
Tick-based scene orchestration with adapter pattern.
The engine manages transitions between scenes using a three-phase lifecycle:
- Initialize: Apply init rules to prepare starting state
- Transition: Interpolate positions from start to target
- Finalize: Apply final rules and enter stable mode
The engine is generic over the node type. Apps provide an adapter record with functions that interact with their specific simulation.
Example usage:
-- Create adapter for your simulation
adapter = mkAdapter mySimulation
-- Create engine
engineRef <- createEngine adapter
-- Transition to a scene
transitionTo treeFormScene engineRef
-- Call tick from your simulation's tick handler
Sim.onTick (\_ -> tick engineRef) mySimulation
#SceneEngine Source
type SceneEngine node = Ref (EngineState node)The scene engine, stored in a Ref for mutation across tick callbacks
#EngineState Source
type EngineState node = { adapter :: EngineAdapter node, currentScene :: Maybe (SceneConfig node), transition :: Maybe (TransitionState node), transitionDelta :: TickDelta }Internal engine state
#EngineAdapter Source
type EngineAdapter node = { applyRulesInPlace :: Array (NodeRule node) -> Effect Unit, capturePositions :: Array node -> PositionMap, getNodes :: Effect (Array node), interpolatePositions :: PositionMap -> PositionMap -> Number -> Effect Unit, reheat :: Effect Unit, reinitializeForces :: Effect Unit, updatePositions :: PositionMap -> Effect Unit }Adapter functions that connect the engine to your simulation.
The engine is generic - it doesn't know about Sim.Simulation directly.
Instead, you provide these adapter functions that do the actual work.
Example adapter for ce-website:
mkAdapter :: CESimulation -> EngineAdapter SimNode
mkAdapter sim =
{ getNodes: Sim.getNodes sim
, capturePositions: \nodes -> Object.fromFoldable $
map (\n -> Tuple (show n.id) { x: n.x, y: n.y }) nodes
, interpolatePositions: \start target progress ->
Sim.interpolatePositionsInPlace start target progress sim
, updatePositions: \positions ->
Sim.updatePositionsInPlace positions sim
, applyRulesInPlace: \rules ->
applyRulesInPlace_ rules sim.nodes
, reinitializeForces: reinitForcesFor sim
, reheat: Sim.reheat sim
}
#createEngine Source
createEngine :: forall node. EngineAdapter node -> Effect (SceneEngine node)Create a new scene engine.
The engine starts with no active scene. Call transitionTo to
begin your first scene transition.
Uses default transition duration of ~2 seconds.
#transitionTo Source
transitionTo :: forall node. SceneConfig node -> SceneEngine node -> Effect UnitStart a transition to a new scene.
If a transition is already in progress, this is ignored.
The transition lifecycle:
- Apply scene's
initRulesto prepare starting state - Capture current positions as start positions
- Calculate target positions via scene's
layoutfunction - Begin interpolation (handled by
tick)
#tick Source
tick :: forall node. SceneEngine node -> Effect BooleanAdvance the engine by one tick.
Call this from your simulation's tick handler.
Returns true if a transition is in progress, false if stable.
#getCurrentScene Source
getCurrentScene :: forall node. SceneEngine node -> Effect (Maybe (SceneConfig node))Get the current scene configuration (if any).
#isTransitioning Source
isTransitioning :: forall node. SceneEngine node -> Effect BooleanCheck if a transition is in progress.
#getTransitionProgress Source
getTransitionProgress :: forall node. SceneEngine node -> Effect (Maybe Number)Get the current transition progress (0.0 to 1.0).
Returns Nothing if no transition is in progress.
#setCurrentScene Source
setCurrentScene :: forall node. SceneConfig node -> SceneEngine node -> Effect UnitSet the current scene directly (without transition).
Use this when the visualization is already in the target state (e.g., after initial rendering with pre-computed positions).
#defaultTransitionDelta Source
defaultTransitionDelta :: TickDeltaDefault transition delta: ~2 seconds at 60fps
Re-exports from Hylograph.Scene.Types
#TransitionState Source
type TransitionState node = { progress :: Progress, startPositions :: PositionMap, targetPositions :: PositionMap, targetScene :: SceneConfig node }Runtime state during an active transition.
Tracks the target scene, start/end positions, and current progress. The interpolation engine updates progress each tick until complete. Uses tick-based progress (0.0 to 1.0) rather than time-based elapsed.
#SceneConfig Source
type SceneConfig node = { finalRules :: Array node -> Array (NodeRule node), initRules :: Array (NodeRule node), layout :: Array node -> PositionMap, name :: String, stableMode :: EngineMode }Scene configuration with three-phase lifecycle.
Phase 1: Initialize (initRules)
Applied before transition starts. Use this to set up starting positions,
e.g., moving tree nodes to the root for a "grow from root" animation.
Phase 2: Transition (layout)
The interpolation engine smoothly moves nodes from their current positions
to the target positions computed by the layout function.
Phase 3: Finalize (finalRules)
Applied after transition completes. Use this to set up the stable state,
e.g., unpinning nodes so forces can take over, or setting gridX/gridY.
Example:
treeFormScene :: SceneConfig MyNode
treeFormScene =
{ name: "TreeForm"
, initRules: [ moveToRootRule ]
, layout: \nodes -> computeTreePositions nodes
, finalRules: \_ -> [ pinAtTreePositionsRule ]
, stableMode: Static
}
#PositionMap Source
type PositionMap = Object PositionPosition map: node ID (as string) -> position Used for capturing current positions and specifying targets
#NodeRule Source
type NodeRule node = { apply :: node -> node, name :: String, select :: node -> Boolean }A rule that selects nodes and applies a transform.
Rules are applied with first-match-wins semantics (like CSS cascade). If multiple rules match a node, only the first one applies.
Example:
pinPackages :: NodeRule MyNode
pinPackages =
{ name: "pinPackages"
, select: \n -> n.nodeType == Package
, apply: \n -> n { fx = notNull n.x, fy = notNull n.y }
}
#EngineMode Source
data EngineModeEngine mode determines what happens after a transition completes.
Physics: D3 force simulation runs, nodes settle via forcesStatic: Nodes stay pinned at their final positions
Constructors
Instances
- Modules
- Hylograph.
Config. Apply - Hylograph.
Config. Force - Hylograph.
Config. Scene - Hylograph.
ForceEngine - Hylograph.
ForceEngine. Core - Hylograph.
ForceEngine. Demo - Hylograph.
ForceEngine. Events - Hylograph.
ForceEngine. Links - Hylograph.
ForceEngine. Registry - Hylograph.
ForceEngine. Render - Hylograph.
ForceEngine. Setup - Hylograph.
ForceEngine. Setup. WASM - Hylograph.
ForceEngine. Simulation - Hylograph.
ForceEngine. Types - Hylograph.
ForceEngine. WASM - Hylograph.
ForceEngine. WASMEngine - Hylograph.
Scene. Engine - Hylograph.
Scene. Handle - Hylograph.
Scene. Rules - Hylograph.
Scene. Types - Hylograph.
Simulation - Hylograph.
Simulation. Emitter - Hylograph.
Simulation. HATS - Hylograph.
Simulation. Scene - Hylograph.
Transition. Consumers - Hylograph.
Transition. Example