Ownership
Overview
Every networked object in Fusion has exactly one owner at any time. The owner is the client that has write authority over the object's Words buffer -- only the owner's outbound state updates are accepted by the server. Ownership determines who can modify replicated properties, and the ownership model is selected per-object at creation time.
This describes Shared-Authority mode (SimulationMode::Shared), where each owner writes its own objects.
In Client-Server mode (SimulationMode::Authority) state authority belongs to the simulation server, and a PlayerPredicted object instead designates a predicting player that holds input authority — see Modes and Topologies and the Predicted Simulation section below.
Owner Modes
C++
enum class ObjectOwnerModes : uint8_t {
Transaction = 0,
PlayerAttached = 1,
Dynamic = 2,
MasterClient = 3,
GameGlobal = 4,
PlayerPredicted = 5,
};
| Mode | Behavior | Use Case |
|---|---|---|
Transaction |
Ownership requires explicit request/release. One owner at a time with cooldown. | Shared resources, vehicles, interactables |
PlayerAttached |
Transferable with the same transaction semantics as Transaction, but the server destroys the object when the owning player leaves the room. |
Player avatars, player-specific state |
Dynamic |
Any client can claim ownership instantly. Built-in cooldown prevents oscillation. | Physics objects, loose items |
MasterClient |
Always owned by the master client. Transfers automatically on master migration. | Game state, scoreboards, match logic |
GameGlobal |
Behaves like MasterClient on the client side — the master client holds write authority and it migrates with the master. Intended for game-global state. |
Session-wide state |
PlayerPredicted |
Server is the authority; a designated player feeds inputs and predicts state locally. Reconciliations surface via OnPredictionReset. Used in SimulationMode::Authority. |
Authoritative simulation, anti-cheat-sensitive gameplay |
SanitizeOwnerMode
C++
static ObjectOwnerModes Client::SanitizeOwnerMode(ObjectOwnerModes ownerMode);
Clamps an owner mode value into the valid range [Transaction, PlayerPredicted].
CreateObject and CreateMapObject apply it automatically; call it yourself if the mode comes from user input or configuration.
Querying Ownership
C++
// Returns the PlayerId of the current owner
PlayerId Client::GetOwner(const Object* obj);
// Returns true if the local client is the owner
bool Client::IsOwner(const Object* obj);
// Returns true if the local client can write to the object
bool Client::CanModify(const Object* obj);
// Returns true if the object has any owner (not unowned)
bool Client::HasOwner(const Object* obj) const;
IsOwner() and CanModify() both check local authority, but CanModify() is broader.
It also returns true while a Dynamic-mode claim is pending (intent set and cooldown elapsed), for a Dynamic-mode map object that has never received state, and on the predicting player of a PlayerPredicted object.
For modes MasterClient and above, GetOwner() returns the current master client's PlayerId and IsOwner() is true only on the master client.
Requesting Ownership
Expressing Intent
C++
void Client::SetWantOwner(Object* obj); // Signal desire to own
void Client::SetDontWantOwner(Object* obj); // Release ownership intent
These methods set the object's ObjectOwnerIntent:
C++
enum class ObjectOwnerIntent : uint8_t {
DontWantOwner = 0,
WantOwner = 1,
};
Transaction Mode
In Transaction mode, ownership transfer is a coordinated process:
- Client A calls
SetWantOwner(obj). - If the object is unowned, Client A announces itself as owner in its next state update and the server confirms it.
- If Client B currently owns the object, the request surfaces on Client B through
OnOwnershipRequest, and Client B answers it withRespondToOwnershipRequest(see Responding to Requests). - Client B can also release voluntarily with
SetDontWantOwner(obj).
PlayerAttached follows the same transaction semantics on the client.
The difference is server-enforced: when the owning player leaves the room, the server destroys a PlayerAttached object instead of leaving it ownerless.
Dynamic Mode
In Dynamic mode, any client can take ownership by calling SetWantOwner().
The claim is optimistic: once the cooldown has elapsed, the local client assigns itself as owner in its next outgoing update and fires OnObjectOwnerChanged immediately, without waiting for the server.
The server arbitrates concurrent claims.
If a later server update shows a different owner while the local intent is still WantOwner, the SDK clears the intent back to DontWantOwner (to avoid reclaim ping-pong) and fires OnObjectOwnerPredictionFailed.
A configurable cooldown prevents rapid ownership bouncing:
C++
client->SetDynamicOwnerCooldown(1.0 / 3); // default ~333ms
double cooldown = client->GetDynamicOwnerCooldown();
The older static constant Object::DynamicOwnerCooldownTime has been removed; configure the cooldown at runtime via Client::SetDynamicOwnerCooldown(seconds).
Clearing Cooldown
C++
void Client::ClearOwnerCooldown(Object* obj);
Resets the per-object ownership transfer cooldown.
Responding to Requests
C++
void Client::RespondToOwnershipRequest(Object* obj, PlayerId requester, bool granted);
When another player requests ownership of an object the local client owns, the request surfaces on the OnOwnershipRequest broadcaster (described below).
The owner answers it asynchronously by calling RespondToOwnershipRequest, passing the requester PlayerId from the callback and granted = true to transfer ownership or granted = false to deny it.
Passing the requester back explicitly means concurrent requests from different players are each answered to the correct target.
The response is validated and forwarded by the plugin and surfaces on the requester via OnOwnershipResponse.
The call is a no-op unless the local client owns the object, the requester is a valid player and the object's mode is Transaction, PlayerAttached or Dynamic.
The request and response callbacks likewise only fire for these three modes.
MasterClient Mode
Objects with MasterClient mode (and GameGlobal / PlayerPredicted, which share the check) are always owned by the master client:
C++
constexpr PlayerId MASTER_CLIENT_PLAYER_ID = 0xFFFF; // UINT16_MAX
When the master client disconnects, the Photon server assigns a new master.
Objects with MasterClient mode automatically transfer to the new master.
For these modes, GetOwner() always reports the current master client's PlayerId, and only the master client sends state updates for them.
Send Rate Control
Ownership and send rate work together for bandwidth optimization.
Per-Object Local Send Rate
C++
void Client::SetLocalSendRate(Object* obj, uint32_t sendRate);
Sets the local send rate divisor for an object.
A value of 1 (or 0, the value ResetLocalSendRate restores) means the object is considered on every send tick.
A value of 16 means it is considered every 16th tick.
This is a client-side optimization -- the object still exists on all clients but consumes less bandwidth when you are not the active authority.
Server-Side Send Rate
C++
void Client::SetRoomSendRate(const Object* obj, int32_t sendRate);
void Client::ResetRoomSendRate(const Object* obj);
SetRoomSendRate writes to the RoomSendRate field in the ObjectTail, which the server uses for bandwidth allocation. ResetRoomSendRate clears it back to the default. Both replace the older SetSendRate / ResetSendRate (which wrote to the now-renamed SendRate tail field). Both only work on root objects — called on a child, they log a warning and do nothing.
The local divisor counterpart is SetLocalSendRate(obj, rate) / ResetLocalSendRate(obj).
For session-wide tuning (rather than per-object), Client::SetAuthoritySendRate(rate) controls the send rate used while operating in SimulationMode::Authority.
Ownership + Send Rate Pattern
A common pattern when requesting/releasing ownership:
C++
// Requesting ownership: send every tick
client->SetLocalSendRate(obj, 1);
client->SetWantOwner(obj);
// Releasing ownership: minimize bandwidth
client->SetLocalSendRate(obj, 16);
client->SetDontWantOwner(obj);
This ensures that when you own an object, you send updates at full rate, and when you release it, you minimize bandwidth until someone else claims it.
Ownership Callbacks
OnObjectOwnerChanged
C++
Broadcaster<void(ObjectRoot*)> OnObjectOwnerChanged;
Fires when ownership of any object changes.
GetOwner() already returns the new owner's PlayerId inside the callback.
For a local Dynamic-mode claim it fires immediately when the client optimistically assigns itself; otherwise it fires when a received update carries a new owner.
Integration layers typically:
- Check if the local client is now the owner (
IsOwner(obj)). - Enable or disable write access to the object's synchronizer.
- Emit an engine-side signal so game logic can react.
OnObjectOwnerPredictionFailed
C++
Broadcaster<void(ObjectRoot*)> OnObjectOwnerPredictionFailed;
Fires when an optimistic Dynamic-mode ownership claim fails — the local client had assigned itself as owner (intent WantOwner), but a server update shows another client won the claim.
The SDK clears the local intent back to DontWantOwner before firing, so the object is not automatically re-claimed.
Use this to roll back any local-authority assumptions made after SetWantOwner.
OnPredictionReset
C++
Broadcaster<void(ObjectRoot*)> OnPredictionReset;
Fires on the predicting player of a PlayerPredicted object when authoritative state arrives and the local input queue must be replayed on top of it.
It fires inside ExecuteInputs, immediately before the queued inputs are re-applied from the new baseline — reset your engine state to the received authoritative state in the handler.
OnOwnershipRequest
C++
Broadcaster<void(ObjectRoot*, PlayerId requester)> OnOwnershipRequest;
Fires on the current owner when another player requests ownership (modes Transaction, PlayerAttached and Dynamic only).
The handler answers asynchronously by calling Client::RespondToOwnershipRequest(obj, requester, granted) — see Responding to Requests above.
The reply is forwarded by the plugin via RPC_INTERNAL_OWNERSHIP_RESPONSE and surfaces on the requester through OnOwnershipResponse.
OnOwnershipResponse
C++
Broadcaster<void(ObjectRoot*, bool granted)> OnOwnershipResponse;
Fires on the requester after the current owner replies to an ownership request.
Predicted Simulation
Setting Client::SetPredictingPlayer(root, player) on a PlayerPredicted-mode object designates which player is feeding inputs:
C++
// On the authority: designate which player feeds inputs.
client->SetPredictingPlayer(root, predictingPlayer);
uint32_t seq = client->GetInputSequence(root);
bool hasIa = client->HasInputAuthority(root);
bool amIp = client->IsPredictingPlayer(root);
// On the predicting player: queue an input frame each tick.
root->QueueInput(deltaSeconds, std::move(payload));
// On the authority: apply the queued inputs and write the resulting state.
root->ExecuteInputs(deltaSeconds);
QueueInput runs on the client that holds input authority; ExecuteInputs runs on the simulation authority, which applies the queued inputs and writes the authoritative state.
Queued inputs surface through the OnInput broadcaster on both sides.
QueueInput is a silent no-op unless the object's mode is PlayerPredicted and the local player is the designated predicting player.
The SDK clones the payload, so the caller keeps ownership of the passed Data and must still free it.
The queue is capped at 256 entries — when full, the oldest input is dropped.
Inputs travel as a dedicated unreliable event (each entry is sent up to three times); the authority discards duplicates and out-of-order sequences.
When authoritative state arrives on the predicting player, acknowledged inputs are dropped from the queue and OnPredictionReset fires on the next ExecuteInputs so the client can replay the remainder from the new baseline.
Special Player IDs
C++
constexpr PlayerId MASTER_CLIENT_PLAYER_ID = 0xFFFF; // Master client
constexpr PlayerId PLUGIN_PLAYER_ID = 0xFFFE; // Server plugin
constexpr PlayerId OBJECT_OWNED_PLAYER_ID = 0xFFFD; // "Send to object owner"
Note that PlayerId is now uint16_t (was uint32_t in older SDKs); the sentinel constants moved from the 0xFFFFFFFF family to the 0xFFFF family.
Sub-Object Authority
Sub-objects (ObjectChild) share their root object's authority.
There is no independent ownership for children -- whoever owns the root owns all of its sub-objects:
C++
ObjectRoot* ObjectChild::Root() override;
Ownership queries on a child should go through the root:
C++
ObjectRoot* root = client->GetRoot(childObj);
bool owned = client->IsOwner(root);
Common Mistakes
| Mistake | Symptom |
|---|---|
Expecting a PlayerAttached object to survive its owner leaving |
The server destroys it when the owning player leaves the room |
| Writing Words without ownership | Data overwritten by authority on next update |
| Not adjusting send rate when taking ownership | Wasted bandwidth at low send rate |
Ignoring OnObjectOwnerPredictionFailed / OnPredictionReset |
Stale local-authority assumptions; visual snapping when authority corrects state |
| Not handling master client migration | MasterClient-mode objects stop updating |
Related
- {VersionPath}/manual/object-creation -- ObjectOwnerModes passed at creation time
- {VersionPath}/manual/objects -- Object hierarchy and data model
- {VersionPath}/manual/interest-area -- Interest keys interact with ownership
- {VersionPath}/manual/time -- When ownership callbacks fire in the frame loop
- Overview
- Owner Modes
- Querying Ownership
- Requesting Ownership
- Expressing Intent
- Transaction Mode
- Dynamic Mode
- Clearing Cooldown
- Responding to Requests
- MasterClient Mode
- Send Rate Control
- Ownership Callbacks
- OnObjectOwnerChanged
- OnObjectOwnerPredictionFailed
- OnPredictionReset
- OnOwnershipRequest
- OnOwnershipResponse
- Predicted Simulation
- Special Player IDs
- Sub-Object Authority
- Common Mistakes
- Related