Try it out!
Click or scan the QR code above to launch a new CodePen instance. You’ll see several moving colored dots - one for each device currently connected to the session. Some dots may even belong to other Multisynq developers reading this documentation! Click or tap the screen to tell your dot where to go.The unsmoothed position of your dot is shown in gray. Notice how it jumps forward every time the model performs an update. The view uses this information to calculate each dot’s smoothed position.
What You’ll Learn
Global Constants
Define constants that contribute to session synchronization
Pure Functions
Share utility functions safely between model and view
Frame Optimization
Use
"oncePerFrame"
to limit view updates efficientlyAnimation Interpolation
Handle infrequent model updates with smooth animations
Global Constants
Constants used by the model should be included in the session hash to ensure synchronization. Changing these constants will create a new session, preventing desynchronization issues.Multisynq.Constants
contributes to the hash used to generate a session ID. Use a short alias like Q
to make your code more readable.Pure Functions
You can safely share utility functions between model and view as long as they are purely functional:Pure Function Requirements
No External Dependencies
No External Dependencies
Function only uses parameters passed to it
No Parameter Modification
No Parameter Modification
Function doesn’t modify input parameters
No State Persistence
No State Persistence
Function doesn’t save state outside its execution scope
Consistent Results
Consistent Results
Same inputs always produce same outputs
Pure function code isn’t included in the session ID hash. If you change these functions frequently, ensure all users have the same version to maintain synchronization.
Actor-Pawn Architecture
RootModel & RootView
The root classes handle spawning and managing Actor-Pawn pairs:- User joins:
RootModel
spawns anActor
, which tellsRootView
to spawn aPawn
- User exits:
RootModel
removes theActor
, which tellsRootView
to remove thePawn
View Initialization Pattern
During initialization, the view should never assume the model’s current state. Always read the model state and build accordingly, as the view might be joining a session in progress or restoring from a snapshot.
Actor Implementation
Movement Planning
goto
method calculates movement vectors:
- Check if already at destination (within
Q.CLOSE
distance) - If too close, pick a new random destination
- Otherwise, calculate velocity vector toward the goal
Arrival Detection
Animation Loop
- Move forward by velocity × tick duration
- Check if arrived and pick new destination if needed
- Notify view that actor has moved
- Schedule next tick
Pawn Implementation
Constructor with Frame Limiting
- Copy initial position from actor
- Subscribe to actor’s movement events
- Use
"oncePerFrame"
to optimize event handling
"oncePerFrame"
discards all but the last event of this type during each frame. This is crucial for high-frequency updates where only the latest position matters.Event Handling
Smooth Animation Update
- Extrapolate: Project actor’s last known position forward using velocity
- Interpolate: Blend current pawn position with extrapolated position
- Render: Draw the smoothed position
Understanding the Smoothing Parameter
TheQ.SMOOTH
value (0 < SMOOTH ≤ 1) controls interpolation behavior:
SMOOTH = 1.0
No interpolation - instant position updates (jerky)
SMOOTH = 0.5
Balanced smoothing - good responsiveness
SMOOTH = 0.1
Heavy smoothing - very smooth but less responsive
Rule of thumb: Tune
Q.SMOOTH
so the pawn spends about half its time behind the actor’s position and half ahead. This provides optimal balance between smoothness and responsiveness.Reflector Heartbeat Configuration
Setting Tick Rate
tps
(ticks per second) option controls reflector heartbeat frequency:
- Purpose: Keeps model running when no user input is received
- Default: 20 ticks per second
- Range: 1-60 ticks per second
- Best Practice: Match your model’s internal tick rate
In this tutorial,
Q.TICK_MS = 500
means the reflector sends heartbeat ticks twice per second maximum. Set heartbeat rate to match your model’s update frequency.Heartbeat vs Responsiveness
Increasing heartbeat tick rate will NOT make your app more responsive. User input events are sent immediately and processed as soon as received. Heartbeat ticks only affect model updates when no other events are received.
Performance Optimization Techniques
Frame-Rate Optimization
oncePerFrame
Discards redundant events within single frame
Selective Updates
Only update pawns that have actually moved
Extrapolation
Predict position between model updates
Interpolation
Smooth transitions prevent visual “popping”
Tuning Guidelines
Model Tick Rate
Model Tick Rate
Match heartbeat rate to your model’s natural update frequency
Smoothing Factor
Smoothing Factor
Balance between smoothness and responsiveness based on your use case
Movement Speed
Movement Speed
Adjust based on model tick rate and expected latency
Proximity Threshold
Proximity Threshold
Set arrival detection distance appropriate for your coordinate system
Advanced Concepts
Actor-Pawn Pattern Benefits
Separation of Concerns
Models handle logic, views handle presentation
Performance
Smooth 60fps animations from low-frequency model updates
Resilience
Gracefully handles network hiccups and connectivity issues
Scalability
Efficient event handling with frame-rate limiting
Common Pitfalls
Avoid These Mistakes:
- Setting
Q.SMOOTH
to 0 (causes no movement) - Making functions impure (breaks synchronization)
- Ignoring existing model state during view initialization
- Using wall-clock time instead of simulation time
Next Steps
3D Animation
Apply smoothing techniques to Three.js 3D scenes
Multiblaster Game
See smoothing in action in a complete game