RPCs

Overview

RPCs in Fusion are fire-and-forget messages sent between clients.
Unlike state synchronization, which continuously replicates property values, RPCs deliver discrete events: damage notifications, chat messages, ability activations and other one-shot actions.

Rpc Structure

C++

class Rpc {
public:
    uint64_t  Id;                // RPC identifier
    uint64_t  Sequence;          // Per-RPC monotonic sequence (assigned by SendUserRpc)
    RpcFlags  Flags;             // Strong-typed delivery / error flags
    uint32_t  DeliveryAttempts;  // Number of attempts so far
    PlayerId  OriginPlayer;      // Sender (uint16_t)
    PlayerId  TargetPlayer;      // Recipient (0 = all)
    ObjectId  TargetObject;      // Target object ((0,0,0) = broadcast)
    uint64_t  EventHash;         // Method/event hash for dispatch
    Data      Bytes;             // Serialized payload

    bool IsInternal() const;     // True if Id is in [1, 1023]
};

Key Fields

Field Purpose
Id Unique RPC identifier. IDs 1-1023 are reserved for SDK internal RPCs. User RPCs use IDs >= 1024.
Sequence Monotonic per-sender sequence number. Assigned in place by Client::SendUserRpc; do not set it manually.
Flags RpcFlags bitset (delivery hints + error indicators). See RpcFlags.
DeliveryAttempts Count of delivery attempts. When the SDK exhausts its retry budget, RpcFlags::MaxDeliveryAttemptsReached is set and the RPC surfaces via OnRpcError.
TargetPlayer Specific player to receive the RPC. 0 for all players, or a PlayerId for targeted delivery.
TargetObject The networked object this RPC is addressed to. ObjectId(0,0,0) indicates a broadcast RPC.
EventHash Hash identifying the event/method. Combined with Id, this routes the RPC to the correct handler. Replaces the older paired DescriptorTypeHash+EventHash scheme — there is now a single hash on the wire.
Bytes Opaque payload containing serialized arguments.

The older Rpc::TargetComponent (uint16_t) and Rpc::DescriptorTypeHash (uint64_t) fields have been removed; routing now uses the (Id, EventHash) pair, and per-component sub-targeting is handled at the integration layer instead of in the wire format.

Creating and Sending RPCs

CreateUserRpc

C++

Rpc Client::CreateUserRpc(
    uint64_t    id,            // RPC ID (must be >= 1024)
    PlayerId    targetPlayer,  // 0 = all, specific PlayerId = targeted
    ObjectId    targetObject,  // Object target or (0,0,0) for broadcast
    uint64_t    EventHash,     // Method hash for dispatch
    const char* data,          // Serialized payload bytes
    size_t      dataLength     // Payload length
);

This constructs an Rpc struct with the local player as OriginPlayer. The data parameter should contain pre-serialized arguments. Older SDKs took a separate DescriptorTypeHash argument before EventHash — that parameter has been removed.

SendUserRpc

C++

bool Client::SendUserRpc(Rpc& rpc);

Queues the RPC for delivery. Takes a non-const reference because the SDK assigns Rpc::Sequence in place. RPCs are batched and sent during the next UpdateFrameEnd() cycle. Returns true if the RPC was accepted.

Complete Example

C++

// 1. Serialize payload
FusionCore::WriteBuffer payload;
payload.Int(damageAmount);
payload.UIntVar(targetEntityId);
FusionCore::Data payloadData = payload.Take();

// 2. Create the RPC
auto rpc = client->CreateUserRpc(
    1024,                                                      // User RPC ID
    0,                                                         // All players
    targetObjectId,                                            // Object-targeted
    FusionCore::CRC64(u8"OnDamage", std::strlen("OnDamage")),  // Event hash
    reinterpret_cast<const char*>(payloadData.Ptr),
    payloadData.Length
);

// 3. Send (the SDK fills in Sequence in place)
client->SendUserRpc(rpc);

// 4. Clean up payload
payloadData.Free();

Receiving RPCs

Successful RPCs arrive through the OnRpc broadcaster. Failed deliveries (the RpcFlags::ErrorFlags mask) surface separately via OnRpcError:

C++

PhotonCommon::Broadcaster<void(Rpc&)> OnRpc;
PhotonCommon::Broadcaster<void(Rpc&)> OnRpcError;

Subscribe to receive RPCs:

C++

subs += client->OnRpc.Subscribe([](FusionCore::Rpc& rpc) {
    if (rpc.IsInternal()) return;  // Skip SDK-internal RPCs

    if (rpc.TargetObject.IsSome()) {
        dispatch_to_object(rpc);
    } else {
        dispatch_broadcast(rpc);
    }
});

subs += client->OnRpcError.Subscribe([](FusionCore::Rpc& rpc) {
    using namespace FusionCore;
    if (HasFlag(rpc.Flags, RpcFlags::PlayerMissing)) {
        // Target player left before the RPC could be delivered.
    }
    if (HasFlag(rpc.Flags, RpcFlags::ObjectMissing)) {
        // Target object was destroyed.
    }
    if (HasFlag(rpc.Flags, RpcFlags::MapIncorrect)) {
        // Target object's map differs from sender's expectation.
    }
    if (HasFlag(rpc.Flags, RpcFlags::MaxDeliveryAttemptsReached)) {
        // Retry budget exhausted.
    }
});

Routing by Hash

The combination of (Id, EventHash) identifies the handler:

  • Id is the user-visible RPC identifier (must be >= 1024).
  • EventHash is typically CRC64(method_name) and lets a single Id cover multiple events without collisions.

The integration layer computes hashes at registration time and matches them against incoming RPCs.

Reading the Payload

C++

void handle_damage_rpc(FusionCore::Rpc& rpc) {
    FusionCore::ReadBuffer reader(rpc.Bytes);
    int32_t  damage   = reader.Int();
    uint32_t entityId = reader.UIntVar();
    // Apply damage...
}

ID Ranges

Range Owner Purpose
1 - 1023 SDK internal Map changes, ownership requests, prediction inputs, etc.
1024+ User Application-defined RPCs

Internal RPC Constants

C++

constexpr uint64_t RPC_InternalMinId               = 1;
constexpr uint64_t RPC_InternalMaxId               = 1023;
constexpr uint64_t RPC_InternalMapChange           = 1;   // Replaces older RPC_InternalSceneChange
constexpr uint64_t RPC_InternalObjectPriority      = 2;
constexpr uint64_t RPC_InternalMapAdd              = 3;
constexpr uint64_t RPC_InternalMapRemove           = 4;
constexpr uint64_t RPC_InternalOwnershipRequest    = 5;
constexpr uint64_t RPC_InternalRejectSubObject     = 6;
constexpr uint64_t RPC_InternalDestroyedMapActors  = 7;
constexpr uint64_t RPC_InternalForceDestroyObject  = 8;
constexpr uint64_t RPC_InternalForceAliveObject    = 9;
constexpr uint64_t RPC_InternalInput               = 10;
constexpr uint64_t RPC_InternalPlayerInterest      = 11;
constexpr uint64_t RPC_InternalOwnershipResponse   = 12;

Internal RPCs are processed by the SDK before reaching OnRpc.
The IsInternal() method identifies them.
User code should never send RPCs with IDs in the internal range.

Target Filtering

Player Targeting

TargetPlayer Value Behavior
0 Delivered to all players
Specific PlayerId Delivered only to that player
MasterClientPlayerId (0xFFFF) Delivered to the current master client
ObjectOwnerPlayerId (0xFFFD) Delivered to the owner of TargetObject

Object Targeting

TargetObject Value Behavior
ObjectId(0, 0, 0) (.IsNone()) Broadcast RPC -- not object-targeted
Valid ObjectId (.IsSome()) Delivered to the specific networked object

RpcFlags

C++

enum class RpcFlags : uint32_t {
    None                       = 0,
    ReturnResultOnFailure      = 1 << 0,
    IncorrectTargetForward     = 1 << 1,
    DontReplyWithResult        = 1 << 2,
    PlayerMissing              = 1 << 5,
    ObjectMissing              = 1 << 6,
    MapIncorrect               = 1 << 7,
    MaxDeliveryAttemptsReached = 1 << 8,
    ErrorFlags                 = PlayerMissing | ObjectMissing
                               | MapIncorrect | MaxDeliveryAttemptsReached,
};

bool HasFlag(RpcFlags flags, RpcFlags flag);

RpcFlags is now an enum class (was a wrapper struct in older SDKs). Compose with | and inspect with HasFlag. The ErrorFlags mask groups all delivery-failure indicators surfaced via OnRpcError.

Wire Format

The Rpc struct has built-in serialization for the SDK's packet protocol:

C++

static Rpc  Rpc::Read(ReadBuffer& reader);
static void Rpc::Write(WriteBuffer& writer, const Rpc& rpc);

These are used internally by the SDK for packet construction and parsing.
User code does not need to call them directly.

Delivery Guarantees

RPCs are sent through Fusion's Notify protocol, which provides ordered, reliable delivery with acknowledgment tracking.
Lost packets are detected and retransmitted up to the SDK's retry budget; if the budget is exhausted (or the target object/player goes missing), the RPC's Flags are stamped with the appropriate RpcFlags::ErrorFlags bit and surfaced via OnRpcError instead of OnRpc.

This means:

  • Successful RPCs are guaranteed to arrive in order from the same sender.
  • Failed RPCs are reported (rather than silently lost) — subscribe to OnRpcError to react.
  • Duplicate delivery does not occur.

Common Mistakes

Mistake Symptom
Using RPC ID < 1024 Conflicts with SDK internal RPCs
Not checking IsInternal() Processing SDK-internal RPCs as user RPCs
Sending RPCs before IsRunning() RPCs silently dropped
Not freeing payload Data after Take() Memory leak
Ignoring OnRpcError No visibility when RPCs fail (player left, object destroyed, map mismatch)
Mismatched read/write order in payload Corrupted arguments
Passing a hand-set Sequence to SendUserRpc Overwritten by the SDK at send time — pointless
  • Architecture -- CRC64 for hash generation
  • Scene Management -- Internal RPCs drive the multi-map session
  • Time -- When RPCs are sent and received in the frame loop
Back to top