Frame Loop
Overview
Fusion requires a strict 3-step call sequence every frame to function correctly.
Missing a step or calling them out of order causes state corruption, connection drops or silent data loss.
The Mandatory Sequence
The three calls must happen every frame, on the same thread, in this exact order.
| Step | Call | Purpose |
|---|---|---|
| 1 | RealtimeClient::Service() |
Pump transport — send/receive packets, advance connection state |
| 2 | Client::UpdateFrameEnd() |
Package and send outgoing state (dirty words, RPCs, StringHeap) |
| 3 | Client::UpdateFrameBegin(dt) |
Process incoming state and fire callbacks |
Step 1: RealtimeClient::Service()
C++
void RealtimeClient::Service(bool dispatchIncomingCommands = true);
Pumps the Photon transport layer.
Sends outgoing UDP/TCP packets, receives incoming data, handles keepalives and advances the connection state machine.
The dispatchIncomingCommands parameter controls whether received events are dispatched to callbacks immediately.
Must be called even when not in a room.
Without Service(), the connection state machine stalls -- connect attempts never complete, keepalives stop and the server disconnects the client.
C++
realtimeClient.Service(true);
There is also ServiceBasic() which performs a minimal service without dispatching, and SendOutgoingCommands() / DispatchIncomingCommands() for fine-grained control.
Step 2: Client::UpdateFrameEnd()
C++
void Client::UpdateFrameEnd();
Packages and sends outgoing state.
The SDK:
- Iterates all objects the local client owns (or has authority over).
- Compares each object's Words buffer against its Shadow buffer to detect changes.
- Packs dirty words into a state packet.
- Writes any queued RPCs into the outgoing packet.
- Serializes StringHeap changes for objects with dirty strings.
- Sends the packet via the Notify channel.
- Updates ack tracking for delivery confirmation.
Call this after your outbound sync -- authority objects must have their Words buffers populated before UpdateFrameEnd() reads them.
Step 3: Client::UpdateFrameBegin(dt)
C++
void Client::UpdateFrameBegin(double dt);
Processes incoming state.
The SDK:
- Reads state packets received since the last frame.
- Updates Words buffers of remote objects with received data.
- Fires
OnObjectReadyfor newly ready remote objects. - Fires
OnSubObjectCreatedfor newly created sub-objects. - Fires
OnObjectOwnerChangedfor ownership transfers. - Fires
OnOwnerWasGivenwhen this client receives ownership. - Fires
OnObjectPredictionOverridewhen authority state overrides local prediction. - Fires
OnObjectDestroyed/OnSubObjectDestroyedfor remotely destroyed objects. - Fires
OnInterestEnter/OnInterestExitfor AOI transitions. - Fires
OnRpcfor received RPCs. - Fires
OnSceneChangefor scene change notifications. - Advances internal timers by
dt.
The dt parameter is the elapsed wall-clock time since the last call, in seconds.
This drives network time synchronization.
Call this before your inbound sync -- after UpdateFrameBegin() returns, remote objects' Words buffers contain the latest received state, ready to be read by the integration layer.
Complete Frame Sequence
Here is the recommended integration pattern for a single frame:
C++
void on_frame(double delta) {
// 1. Pump transport
realtimeClient->Service(true);
// 2. Write local state to Fusion buffers (authority objects only)
for (auto& [id, obj] : fusionClient->AllRootObjects()) {
if (fusionClient->IsOwner(obj)) {
write_to_words(obj); // Your outbound sync
}
}
// 3. Send outgoing state
fusionClient->UpdateFrameEnd();
// 4. Process incoming state and fire callbacks
fusionClient->UpdateFrameBegin(delta);
// 5. Read remote state from Fusion buffers (non-authority objects)
for (auto& [id, obj] : fusionClient->AllRootObjects()) {
read_from_words(obj); // Your inbound sync
}
// 6. Engine game logic runs (physics, scripts, AI)
}
Why End Before Begin?
The order UpdateFrameEnd() then UpdateFrameBegin() may seem counterintuitive.
The rationale is a single-frame pipeline.
| Phase | Action | Rationale |
|---|---|---|
| 1. Write Words | Authority writes outbound state | Words buffer populated with this frame's data |
| 2. UpdateFrameEnd (SEND) | SDK packages dirty words and transmits | Outgoing state sent immediately, minimizing latency |
| 3. UpdateFrameBegin (RECV) | SDK applies incoming state from remotes | Integration reads the freshest available data |
| 4. Read Words | Integration reads remote state | Complete send-receive cycle in one frame |
If you reversed the order (Begin then End), authority state written this frame would not be sent until the next frame's End call, adding one frame of latency.
Timing and Send Rate
The SDK internally tracks a send rate (default: 30 Hz) via _clientSendRate.
However, UpdateFrameEnd() handles rate limiting internally -- you should call it every frame and let the SDK decide when to actually transmit.
Calling it faster than the send rate is safe; the SDK skips transmission on frames where it is not yet time to send.
The dt parameter to UpdateFrameBegin() should be the actual elapsed time.
Do not pass a fixed timestep unless your frame rate is genuinely fixed.
Inaccurate delta values cause network time drift.
UpdateServiceOnly()
C++
void Client::UpdateServiceOnly();
A lightweight alternative that only pumps the socket layer without processing any Fusion state.
Use this during loading screens or scene transitions when you need to keep the connection alive but are not ready to process state updates.
UpdateServiceOnly() replaces the full 3-step sequence temporarily.
Resume the normal Service() -> UpdateFrameEnd() -> UpdateFrameBegin() sequence once loading completes.
C++
void on_loading_frame() {
// Keep connection alive during scene load
fusionClient->UpdateServiceOnly();
}
StateUpdatesPause / StateUpdatesResume
C++
void Client::StateUpdatesPause();
void Client::StateUpdatesResume();
Temporarily pauses outgoing state replication without disconnecting.
Objects stop sending updates while paused.
Use this during scene transitions to prevent sending stale state.
RPCs and connection management continue normally.
Call StateUpdatesResume() after the new scene is loaded and objects are re-bound.
C++
// Scene transition
fusionClient->StateUpdatesPause();
unload_old_scene();
load_new_scene();
register_scene_objects();
fusionClient->StateUpdatesResume();
Common Mistakes
| Mistake | Symptom |
|---|---|
Not calling Service() |
Connection timeout, stuck in connecting state |
Calling UpdateFrameBegin() before UpdateFrameEnd() |
One frame of extra latency on all state |
| Not calling any update when in a room | Objects never replicate, RPCs never arrive |
Passing 0 for dt |
Network time stops advancing, interpolation breaks |
Writing Words after UpdateFrameEnd() |
Changes not sent until next frame |
Calling UpdateFrameEnd() before Start() |
Internal assertion; _expectingEnd guard |
Not calling Service() before connection |
Connect() task never completes |
Related
- Connection -- Connection lifecycle and Service() requirements
- Objects -- Words/Shadow buffers and dirty detection