Object Creation
Overview
Fusion provides three distinct paths for creating networked objects, plus a two-step process for sub-objects.
All paths produce objects backed by a Words buffer and replicated by the SDK, but they differ in ID assignment, lifetime and creation flow.
| Path | Method | ID Assignment | Use Case |
|---|---|---|---|
| Dynamic | CreateObject |
Auto-incremented per client | Player avatars, projectiles, pickups |
| Scene | CreateSceneObject |
Deterministic (scene + id) | Level geometry, doors, switches |
| Global Instance | CreateGlobalInstanceObject |
Deterministic (singleton) | Game managers, scoreboards |
| Sub-object | CreateSubObject + AddSubObject |
Explicit | Inventory items, attachments |
Dynamic Objects (CreateObject)
Dynamic objects are the most common path.
The creating client provides a type descriptor and header blob; the SDK assigns a unique ObjectId and replicates the object to all interested clients.
C++
ObjectRoot* Client::CreateObject(
size_t words, // Word count (user data + ExtraTailWords)
const TypeRef& type, // { Hash, WordCount } for type identification
const CharType* header, // Opaque header blob (scene path, config, etc.)
size_t headerLength,
uint32_t scene, // Scene sequence this object belongs to
ObjectOwnerModes ownerMode, // Ownership behavior
int32_t requiredObjectsCount = 0 // Dependencies before ready
);
ObjectId Assignment
C++
ObjectId Client::GetNewObjectId();
Every object receives an ObjectId composed of two fields:
C++
struct ObjectId {
PlayerId Origin; // Player who created the object
uint32_t Counter; // Monotonically increasing per-player counter
};
The SDK auto-assigns IDs via GetNewObjectId().
The Origin field enables conflict-free creation across clients without coordination.
Header
The header parameter is an opaque byte blob stored alongside the object.
Remote clients receive it in the OnObjectReady callback and use it to determine which scene to instantiate.
Typical contents include a scene resource path or a type hash.
The SDK does not interpret the header -- it passes it through as-is.
Remote Object Ready
When a remote client's object becomes fully synchronized, the SDK fires:
C++
Broadcaster<void(ObjectRoot*)> OnObjectReady;
The callback receives the fully initialized ObjectRoot with Header, Type and OwnerMode populated.
The ObjectReady flag on the root is set to true.
The integration layer reads the header, instantiates the appropriate scene and begins synchronization.
An object becomes "ready" when it reaches OBJECT_STATUS_CREATED and all of its required objects (specified by requiredObjectsCount) are also created and present.
Destruction
The owner destroys a dynamic object by calling:
C++
bool Client::DestroyObjectLocal(ObjectRoot* obj, bool engineObjectAlreadyDestroyed);
Set engineObjectAlreadyDestroyed to true if the engine-side entity has already been freed.
The SDK broadcasts the destruction to all clients:
C++
Broadcaster<void(const ObjectRoot*, DestroyModes)> OnObjectDestroyed;
DestroyModes
| Mode | Value | Description |
|---|---|---|
Local |
0 | Owner explicitly destroyed |
Remote |
1 | Destruction replicated from owner |
SceneChange |
2 | Destroyed due to scene transition |
Shutdown |
3 | Client shutting down |
RejectedNotOwner |
4 | Server rejected: not the owner |
ForceDestroy |
5 | Server forced destruction |
Scene Objects (CreateSceneObject)
Scene objects represent entities that exist in the level itself -- not spawned dynamically but part of the loaded scene.
Every client creates the same scene objects locally; the SDK reconciles them using deterministic IDs.
C++
ObjectRoot* Client::CreateSceneObject(
bool& alreadyPopulated, // [out] true if network data exists
size_t words,
const TypeRef& type,
const CharType* header,
size_t headerLength,
uint32_t scene, // Scene sequence
uint32_t id, // Deterministic object ID
ObjectOwnerModes ownerMode,
int32_t requiredObjectsCount = 0
);
The alreadyPopulated Pattern
The critical difference from CreateObject is the alreadyPopulated output parameter:
false-- This client is the first to create the object (typically the master client). Engine defaults should be serialized into the Words buffer.true-- Another client already populated the object. The Words buffer contains network data that should be deserialized into the engine state.
This bidirectional flow eliminates the need for separate "spawn data" exchange for scene objects.
Deterministic IDs
Scene objects use a deterministic id parameter instead of auto-assigned IDs.
All clients loading the same scene must produce the same ID for the same object.
A common approach is hashing the object's name or path:
C++
uint32_t id = CRC64(rootNodeName);
Global Instance Objects (CreateGlobalInstanceObject)
A variant for singleton objects that persist across scene changes:
C++
ObjectRoot* Client::CreateGlobalInstanceObject(
bool& alreadyPopulated,
size_t words,
const TypeRef& type,
const CharType* header,
size_t headerLength,
uint32_t scene,
uint32_t id,
ObjectOwnerModes ownerMode,
int32_t requiredObjectsCount = 0
);
Global instance objects survive scene transitions and maintain their state across ChangeScene() calls.
They follow the same alreadyPopulated pattern as scene objects.
Sub-Objects (CreateSubObject + AddSubObject)
Sub-objects are child objects attached to an existing root object.
They share the root's authority but have their own ObjectId, Words buffer and synchronization state.
Two-Step Creation
C++
// Step 1: Create the child
ObjectChild* Client::CreateSubObject(
ObjectId parent, // Parent root's ObjectId
size_t words,
const TypeRef& type,
const CharType* header,
size_t headerLength,
uint32_t targetObjectHash, // Type identifier for remote matching
ObjectId id, // Explicit child ID
ObjectSpecialFlags SpecialFlags
);
// Step 2: Attach to parent
bool Client::AddSubObject(ObjectRoot* ParentObject, ObjectChild* SubObject);
Creation Flow
C++
// 1. Create the child
ObjectChild* child = client->CreateSubObject(
parentId, words, type,
header, headerLength,
typeHash, childId, ObjectSpecialFlags::None
);
// 2. Write initial state into child Words
child->SetSendUpdates(false);
write_initial_state(child);
// 3. Attach to parent
client->AddSubObject(parentRoot, child);
// 4. Activate
child->SetSendUpdates(true);
child->SetHasValidData(true);
AddSubObject must be called after the child's Words buffer is populated.
Once added, the SDK replicates the child to remote clients.
Remote Sub-Object Callback
C++
Broadcaster<void(ObjectChild*)> OnSubObjectCreated;
Remote clients receive the ObjectChild with its TargetObjectHash and Parent ID.
The integration layer matches TargetObjectHash to determine the child's type.
Sub-Object Destruction
C++
bool Client::DestroySubObjectLocal(ObjectChild* obj);
Broadcaster<void(ObjectChild*, DestroyModes)> OnSubObjectDestroyed;
Querying Sub-Objects
C++
bool Client::HasSubObjects(const Object* Root);
const std::vector<ObjectId>& Client::GetSubObject(const Object* Root);
Object* Client::FindSubObjectWithHash(ObjectRoot* Root, uint32_t subObjectHash);
Word Count Calculation
The words parameter in all creation methods must include both user data words and the SDK-reserved tail:
C++
size_t total_words = user_property_words + Object::ExtraTailWords; // ExtraTailWords = 6
The ExtraTailWords account for the ObjectTail structure (RequiredObjectsCount, InterestKey, Destroyed, SendRate, Dummy).
TypeRef Convention
C++
struct TypeRef {
uint64_t Hash; // CRC64 or engine-specific hash
uint32_t WordCount; // Total words including tail
};
The Hash must match between creator and consumer for remote instantiation to succeed.
The WordCount should equal the words parameter passed to the creation method.
Activation Pattern
After creating an object and writing initial state:
C++
// Pause sending during initial population
obj->SetSendUpdates(false);
// Write all initial properties via memcpy into Words buffer
populate_words(obj);
// Resume sending and mark data as valid
obj->SetSendUpdates(true);
obj->SetHasValidData(true);
This prevents the SDK from sending a partially-populated object.
The SetSendUpdates(false) call ensures no outgoing packet includes this object until all properties are written.
Required Objects
The requiredObjectsCount parameter specifies how many other objects must be present before the OnObjectReady callback fires.
Required object IDs are stored in the ObjectTail at the end of the Words buffer, just before the standard tail fields.
C++
// Query required objects
int32_t count = root->RequiredObjectsCount();
ObjectId* ids = root->RequiredObjects();
bool isReq = root->IsRequired(someId);
The total word count must account for required objects:
C++
size_t total = user_words + Object::ExtraTailWords;
// requiredObjectsCount is passed separately, not added to word count
Related
- Objects -- Object hierarchy, Words buffer layout, ObjectTail
- Ownership -- ObjectOwnerModes and authority transfer
- Serialization -- Words buffer and type-to-word mapping
- Overview
- Dynamic Objects (CreateObject)
- Scene Objects (CreateSceneObject)
- Global Instance Objects (CreateGlobalInstanceObject)
- Sub-Objects (CreateSubObject + AddSubObject)
- Two-Step Creation
- Creation Flow
- Remote Sub-Object Callback
- Sub-Object Destruction
- Querying Sub-Objects
- Word Count Calculation
- TypeRef Convention
- Activation Pattern
- Required Objects
- Related