Objects

Overview

Networked objects are the core data model in Fusion.
Each object owns a fixed-size buffer of Words that is automatically replicated to other clients.
The SDK provides a three-level class hierarchy for different object roles.

Class Hierarchy

Class Role Description
Object Abstract base Holds Words buffer, shadow state, type information and SDK bookkeeping
ObjectRoot Top-level entity Tracks ownership, timing, scene membership and sub-objects
ObjectChild Sub-object Attached to a root; shares authority but has independent Words buffer and ObjectId

All three classes live in the FusionCore namespace.

Object (Base)

The common base class.
Holds the Words buffer, shadow state, type information and SDK bookkeeping.

C++

class Object {
public:
    ObjectId Id;                       // Globally unique identifier
    TypeRef  Type;                     // Type hash + word count

    void*    Engine;                   // Opaque pointer for engine integration
    Data     EngineBlob;               // Engine-specific spawn payload
    uint32_t EngineFlags;              // Engine-specific bitflags
    uint64_t EngineHash;               // Engine-specific hash (sub-object identity)

    BufferT<Word> Words;               // Current replicated state
    BufferT<Word> Shadow;              // Last-acked state (for dirty detection)

    static constexpr size_t ExtraTailWords = sizeof(ObjectTail) / 4;  // = 18

    bool GetHasValidData() const;
    void SetHasValidData();
    void SetSendUpdates(bool sendUpdates);

    ObjectType GetObjectType() const;
    uint8_t    GetSendFlags() const;
    Tick       GetRemoteTickSent() const;
    Tick       GetRemoteTickAcked() const;
    bool       SendHeader() const;

    EMAReport GetSendReport() const;
    EMAReport GetRecvReport() const;

    NetworkedStringHeap&       GetStringHeap();
    const NetworkedStringHeap& GetStringHeap() const;

    virtual ObjectRoot* Root() = 0;

    // String heap operations
    StringHandle AddString(const PhotonCommon::CharType* str);
    const PhotonCommon::CharType* ResolveString(const StringHandle& handle, StringMessage& outStatus);
    StringHandle FreeString(const StringHandle& handle);
    bool IsValidStringHandle(const StringHandle& handle);
    uint32_t GetStringLength(const StringHandle& handle);
};

The older public Header (immutable spawn data), Status (lifecycle int with OBJECT_STATUS_* constants), SpecialFlags (ObjectSpecialFlags enum) and DynamicOwnerCooldownTime static constant have all been removed. Use EngineBlob/EngineFlags/EngineHash for engine-binding metadata, ObjectRoot::IsReady() / GetHasReceivedState() for lifecycle queries, and Client::SetDynamicOwnerCooldown(seconds) to configure the ownership-transfer cooldown at runtime.

Key Fields

Field Type Purpose
Id ObjectId Globally unique identifier (origin player + map + counter)
Engine void* Opaque pointer for engine integration to store its own data
EngineBlob Data Engine-specific spawn payload — the role formerly played by the public Header field.
EngineFlags uint32_t Engine-specific bitflags supplied to Create* at spawn time. Replaces the older ObjectSpecialFlags enum.
EngineHash uint64_t Engine-specific identity hash (used by CreateSubObject to route a child onto the correct slot of its parent).
Words BufferT<Word> Current replicated state buffer
Shadow BufferT<Word> Previous acked state for change detection
Type TypeRef Type hash and total word count (including tail)
String heap NetworkedStringHeap Per-object string pool — accessed via GetStringHeap(). Allocated lazily (no fixed capacity preset).

ObjectRoot

A top-level networked entity.
Tracks ownership, timing, scene membership and sub-objects.

C++

class ObjectRoot final : public Object {
public:
    // All ownership / time / version state is private with getters:
    Map               GetMap() const;            // Replaces the older public `Scene` field
    PlayerId          GetOwner() const;
    ObjectOwnerModes  GetOwnerMode() const;
    double            GetTime() const;
    bool              IsReady() const;            // Replaces the older `ObjectReady` field
    bool              GetHasReceivedState() const;
    int32_t           GetPluginVersion() const;
    int32_t           GetClientVersion() const;
    const std::vector<ObjectId>& GetSubObjects() const;

    // Combined stats for the root + its sub-objects.
    EMAReport GetCombinedSendReport() const;
    EMAReport GetCombinedRecvReport() const;

    // Predicted-input queue (used with ObjectOwnerModes::PlayerPredicted under
    // SimulationMode::Authority).
    void     QueueInput(float dt, Data payload);
    void     ExecuteInputs(float dt);
    uint32_t GetInputQueueCount() const;
    float    GetInputQueueDeltaTime() const;
    double   GetInputTime() const;

    static bool Is(const Object* obj);
    static ObjectRoot* Cast(Object* obj);
    static const ObjectRoot* Cast(const Object* obj);

    bool                 IsRequired(ObjectId id) const;
    std::span<ObjectId>  RequiredObjects() const;   // Was `ObjectId*` in older SDKs
    ObjectRoot*          Root() override;            // Returns this
};

ObjectChild

A sub-object attached to a root.
Shares the root's authority but has its own independent Words buffer and ObjectId.

C++

class ObjectChild final : public Object {
    // Parent is private; access via the static GetParent helper.
public:
    static ObjectId GetParent(const Object* obj);
    static bool Is(const Object* obj);
    static ObjectChild* Cast(Object* obj);
    static const ObjectChild* Cast(const Object* obj);

    ObjectRoot* Root() override;       // Navigates to parent root
};

Root() traverses up to the parent ObjectRoot, which the SDK uses for authority checks. The base class's GetStringHeap() returns the child's own per-object heap.

The older public TargetObjectHash field on ObjectChild is gone; the equivalent identity is supplied as the engineHash parameter to Client::CreateSubObject (widened to 64 bits) and stored in the base class's EngineHash field.

ObjectType Enum

C++

enum class ObjectType : uint8_t {
    Base  = 1,
    Child = 2,
    Root  = 3
};

Use the static Is() and Cast() methods on ObjectRoot and ObjectChild for safe type checks rather than comparing ObjectType directly:

C++

if (ObjectRoot::Is(obj)) {
    ObjectRoot* root = ObjectRoot::Cast(obj);
    // ...
}

if (ObjectChild::Is(obj)) {
    ObjectChild* child = ObjectChild::Cast(obj);
    ObjectId parentId = child->Parent;
}

Words Buffer

The Words buffer is a flat array of int32_t values that holds all replicated property data for an object.
Properties are serialized sequentially without type markers -- the integration layer and all clients must agree on the same layout.

Region Content Notes
Words 0 .. N User properties (Prop 0, Prop 1, ... Prop N) Each property occupies a fixed number of words (e.g., float = 1, Vector3 = 3, StringHandle = 2)
Last 18 words ObjectTail Reserved for SDK use — never write to them

The total buffer size is: sum(property_words) + ExtraTailWords (where ExtraTailWords = 18).

Writing and Reading

The authority client writes property values into the Words buffer during the outbound sync phase.
The SDK transmits only changed words (detected by comparing Words against Shadow).
Remote clients read from their local copy of the Words buffer during the inbound sync phase.

C++

// Authority: write a float at word offset 0
memcpy(&obj->Words.Ptr[0], &my_float, sizeof(float));

// Remote: read it back
float value;
memcpy(&value, &obj->Words.Ptr[0], sizeof(float));

Shadow Buffer

The Shadow buffer is a parallel array with the same size as Words.
After the SDK transmits an update, it compares Words against Shadow to detect which words changed and need retransmission.
After acknowledgment, Shadow is updated.

The engine integration should not modify Shadow directly.

ObjectTail

The last 18 words of every Words buffer are reserved for the SDK's internal use:

C++

#pragma pack(push, 4)
struct ObjectTail {
    uint32_t Reserved[8];             // Eight reserved words for forward-compat
    int32_t  RequiredObjectsCount;    // Number of required object dependencies
    uint64_t InterestKey;             // AOI interest key (8 bytes, 2 words)
    int32_t  Destroyed;               // Destruction flag
    int32_t  RoomSendRate;            // Server-arbitrated send rate (renamed from SendRate)
    PlayerId PredictingPlayer;        // Player currently predicting this object (uint16_t)
    uint32_t RejectedSequence;        // Latest input sequence rejected by the server
    uint32_t InputSequence;           // Latest applied input sequence
    uint32_t InputTime;               // Synchronized time at which the latest input was sampled
    int32_t  Dummy;                   // Trailing alignment padding
};
#pragma pack(pop)

static_assert(sizeof(ObjectTail) == 72);  // 18 words * 4 bytes

Writing to the tail area corrupts SDK state.
The Destroyed field is especially dangerous -- overwriting it prevents proper cleanup.
The InterestKey is managed by the AOI system via SetGlobalInterestKey(), SetAreaInterestKey() and SetUserInterestKey(). Prediction-related fields (PredictingPlayer, RejectedSequence, InputSequence, InputTime) are written by the SDK from Client::SetPredictingPlayer and the queued-input flow.

When computing buffer sizes, always add Object::ExtraTailWords (18):

C++

size_t total_words = sum_of_property_words + Object::ExtraTailWords;

Lifecycle Queries

The SDK no longer exposes a numeric Object::Status field with OBJECT_STATUS_NEW/PENDING/CREATED constants. Use the explicit accessors on ObjectRoot instead:

Method Meaning
ObjectRoot::IsReady() The root and all required sub-objects have been resolved; subscribers of OnObjectReady can interact with the object.
ObjectRoot::GetHasReceivedState() At least one state packet has been applied to this object since creation.
Object::GetHasValidData() The integration explicitly marked the Words buffer as carrying meaningful data (via SetHasValidData()).

Bandwidth Tracking

Each object tracks bytes sent and received via exponential moving averages.
The older per-frame byte counters (GetBytesSendLastTick, GetBytesReceivedLastTick, ConsumeBytesSendLastTick, ConsumeBytesReceivedLastTick, ResetReceivedBytes) are gone; query the EMA report instead:

C++

EMAReport sendReport = obj->GetSendReport();
EMAReport recvReport = obj->GetRecvReport();

// For a root, sub-object traffic can also be combined:
EMAReport rootSend = root->GetCombinedSendReport();
EMAReport rootRecv = root->GetCombinedRecvReport();

EMAReport exposes TotalAvg, TotalAvgPerSecond, CurrentAvgPerSecond, Min, Max — useful for network debugging and bandwidth allocation.

Object Lookup

The Client maintains two maps for object lookup:

C++

// All objects (roots + children)
std::unordered_map<ObjectId, Object*>& client->AllObjects();

// Root objects only
std::unordered_map<ObjectId, ObjectRoot*>& client->AllRootObjects();

// Single-object lookup
Object* client->FindObject(ObjectId id);
ObjectRoot* client->FindObjectRoot(ObjectId id);
Object* client->FindSubObjectWithHash(ObjectRoot* root, uint32_t subObjectHash);

Object Lifecycle Summary

Step Action Description
1 Allocate Client creates object, assigns ObjectId, allocates Words/Shadow
2 Populate Integration writes initial state into Words buffer
3 Activate SetHasValidData() + SetSendUpdates(true)
4 Replicate Each frame: Words vs Shadow delta detection, dirty words sent
5 Ready OnObjectReady fires when the object and its required objects are all resolved (ObjectRoot::IsReady() returns true)
6 Destroy DestroyObjectLocal() marks for removal; OnObjectDestroyed on remotes

See Object Creation for the three creation paths.

Back to top