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.

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 Permanently owned by the creating player. Cannot be transferred. 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 Server/plugin-owned global object. No client can claim ownership. Server-authoritative state
PlayerPredicted Server is the authority; a designated player feeds inputs and predicts state locally. Mispredictions are corrected via OnPredictionOverride / OnPredictionReset. Used in SimulationMode::Authority. Authoritative simulation, anti-cheat-sensitive gameplay

SanitizeOwnerMode

C++

ObjectOwnerModes Client::SanitizeOwnerMode(ObjectOwnerModes ownerMode) const;

Validates and normalizes an owner mode value.
Call this if the mode comes from user input or configuration to ensure it maps to a valid enum value.

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() may account for additional conditions beyond basic ownership.

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:

  1. Client A calls SetWantOwner(obj).
  2. If the object is unowned, Client A becomes the owner immediately.
  3. If Client B currently owns the object, the request is queued until Client B calls SetDontWantOwner(obj).
  4. After transfer, a cooldown prevents immediate re-transfer.

Dynamic Mode

In Dynamic mode, any client can take ownership by calling SetWantOwner().
Unlike Transaction, the transfer happens without waiting for the current owner to release.
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.

Explicit Transfers and Requests

C++

void Client::SetGiveAwayOwner(Object* obj, PlayerId player);
void Client::RejectOwnershipRequest(Object* obj);

SetGiveAwayOwner initiates an explicit transfer of an owned object to another player. RejectOwnershipRequest denies an inbound ownership request received via the OnOwnershipRequest broadcaster (described below).

MasterClient Mode

Objects with MasterClient mode are always owned by the master client:

C++

constexpr PlayerId MasterClientPlayerId = 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.

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 means the object sends every tick (highest rate).
A value of 16 means it sends every 16th tick (lowest rate).
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).

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.
The ObjectRoot has its Owner field already updated to the new owner's PlayerId.
Integration layers typically:

  1. Check if the local client is now the owner (IsOwner(obj)).
  2. Enable or disable write access to the object's synchronizer.
  3. Emit an engine-side signal so game logic can react.

OnObjectOwnerAssigned

C++

Broadcaster<void(ObjectRoot*)> OnObjectOwnerAssigned;

Fires specifically on the client that received ownership. Replaces the older OnOwnerWasGiven.
This is more targeted than OnObjectOwnerChanged, which fires on all clients.
Use this to activate authority-specific behavior (e.g., enabling input processing, starting physics simulation).

OnPredictionOverride

C++

Broadcaster<void(ObjectRoot*)> OnPredictionOverride;

Fires when the authoritative state overrides local prediction (renamed from OnObjectPredictionOverride).
This happens with ObjectOwnerModes::PlayerPredicted objects when the server's authoritative state diverges from the predicted local state.
Use this for visual correction or interpolation reset.

OnPredictionReset

C++

Broadcaster<void(ObjectRoot*)> OnPredictionReset;

Fires when the server rejects a predicted input sequence — the prediction queue is reset and re-applied from the latest authoritative state. New in this release.

OnOwnershipRequest

C++

Broadcaster<bool(ObjectRoot*, PlayerId requester)> OnOwnershipRequest;

Fires on the current owner when another player requests ownership. Subscribers return true to grant or false to deny; the SDK aggregates the responses (any true grants) and replies via RPC_InternalOwnershipResponse.

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++

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 for replay.
root->QueueInput(deltaSeconds, std::move(payload));
root->ExecuteInputs(deltaSeconds);

Inputs travel as RPC_InternalInput messages. The server may reject sequences that arrive out of order; the SDK then fires OnPredictionReset so the client can replay from the new baseline.

Special Player IDs

C++

constexpr PlayerId MasterClientPlayerId  = 0xFFFF;       // Master client
constexpr PlayerId PluginPlayerId        = 0xFFFE;       // Server plugin
constexpr PlayerId ObjectOwnerPlayerId   = 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
Calling SetWantOwner on a PlayerAttached object No effect; ownership cannot transfer
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 OnPredictionOverride / OnPredictionReset Visual snapping when authority corrects state
Not handling master client migration MasterClient-mode objects stop updating
  • {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
Back to top