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.
Related
- Architecture -- Fundamental types and memory model
- Object Creation -- CreateObject, CreateMapObject, CreateSubObject
- Serialization -- Words buffer layout and encoding
- Ownership -- Owner modes and authority