Skip to main content
Supported on
Snapchat

Sync Entity

A SyncEntity is the bridge between one of your scripts and one networked store. It owns a set of storage properties, tracks who owns the data, and raises events when values change or the entity is destroyed.

This is the main abstraction you write gameplay against. If you only need to sync a transform or a material, the drop-in components Sync Transform and Sync Materials create a Sync Entity for you.

Creating one

import { SyncEntity } from 'ConnectedFramework.lspkg/Core/SyncEntity';
import { StorageProperty } from 'ConnectedFramework.lspkg/Core/StorageProperty';
import { StoragePropertySet } from 'ConnectedFramework.lspkg/Core/StoragePropertySet';

@component
export class ScoreKeeper extends BaseScriptComponent {
private score = StorageProperty.manualInt('score', 0);
private syncEntity = new SyncEntity(this, {
propertySet: new StoragePropertySet([this.score]),
});

onAwake() {
this.syncEntity.onSetupFinished.add(() => {
print(`Ready. Score is ${this.score.currentValue}`);
});

this.score.onAnyChange.add((newValue) => {
print(`Score is now ${newValue}`);
});
}

addPoint() {
if (this.syncEntity.canIModifyStore()) {
this.score.setPendingValue((this.score.currentOrPendingValue ?? 0) + 1);
}
}
}

Always wait for onSetupFinished (or await waitForReady()) before reading or writing values. Before setup completes, the entity has no store to read from.

Constructor options

new SyncEntity(scriptComponent, options?)

OptionTypeDescription
propertySetStoragePropertySetStorage properties to keep in sync.
ownershipOwnershipOwnership for the realtime store. Defaults to Unowned unless this is part of an instantiated prefab.
persistencePersistencePersistence for the realtime store. Defaults to Session.
networkIdOptionsNetworkIdOptionsHow the unique networkId is generated. Required when there is no attached ScriptComponent.
Name (signature)Description
SyncEntity.createStandalone(networkId: string, options?)Creates an entity not attached to a script component. Takes the same options minus networkIdOptions.
SyncEntity.getSyncEntityOnComponent(component: Component): SyncEntity | nullFinds the Sync Entity already attached to a component.
SyncEntity.getSyncEntityOnSceneObject(sceneObject: SceneObject): SyncEntity | nullFinds the Sync Entity on a scene object.
SyncEntity.findById(networkId: string): SyncEntity | nullLooks up an entity by its network id.

Properties

NameTypeDescription
networkIdstringUnique id for this entity across the session.
currentStoreGeneralDataStoreThe underlying realtime store.
persistencePersistenceConfigured persistence.
ownershipOwnershipConfigured ownership.
ownerInfoConnectedLensModule.UserInfo | nullCurrent owner, if any.
isHostOwnedbooleanWhether the store is host-owned.
propertySetStoragePropertySetThe set of synced properties.
networkRootNetworkRootInfoSet when this entity belongs to an instantiated prefab.
isSetupFinishedbooleanWhether setup has completed.
destroyedbooleanWhether the entity has been destroyed.
storeCallbacksStoreEventWrapperLow-level store event callbacks.
messagingNetworkMessageWrapperLow-level messaging wrapper.

Ownership

Ownership decides who is allowed to write. Check before writing rather than writing and hoping.

Name (signature)Description
canIModifyStore(): booleanWhether the local client may write. Check this before every write.
doIOwnStore(): booleanWhether the local client owns the store.
isStoreOwned(): booleanWhether anyone owns the store.
getOwnerId(): string | nullOwner connection id.
getOwnerUserId(): string | nullOwner persistent user id.
getOwnerConnectionId(): string | nullOwner connection id.
tryClaimOwnership(): Promise<GeneralDataStore>Attempts to take ownership.
tryRevokeOwnership(): Promise<GeneralDataStore | null>Attempts to give up ownership.
requestOwnership(): Promise<GeneralDataStore>Requests ownership.
requestHostOwnership(): Promise<GeneralDataStore>Requests host ownership.
removeHostOwnership(): Promise<GeneralDataStore>Removes host ownership.

Lifecycle and session

Name (signature)Description
waitForReady(): Promise<void>Resolves once setup has finished.
getSessionController(): SessionControllerThe owning Session Controller.
getSession(): MultiplayerSession | nullThe underlying session.
addStorageProperty<T>(storageProperty: StorageProperty<T>): StorageProperty<T>Adds a property to the set after construction, returning it for chaining.
destroy(deleteStore = false): voidDestroys the entity, optionally deleting its store.

Events

NameCalled withDescription
onSetupFinishednoneSetup completed. Safe to read and write from here.
onOwnerUpdateduserInfoOwnership changed hands.
onEventReceivedNetworkMessageA custom event arrived, local or remote.
onRemoteEventReceivedNetworkMessageA custom event arrived from another client only.
onDestroyednoneThe entity was destroyed.
onLocalDestroyednoneThe entity was destroyed locally.
onRemoteDestroyednoneThe entity was destroyed by another client.

Custom events

For one-off signals that do not belong in synced state, such as "player emoted" or "round starting," send an event instead of writing a property:

Name (signature)Description
sendEvent(eventName: string, eventData?: unknown, onlySendRemote?: boolean)Sends a custom event. Pass onlySendRemote to skip the local callback.
getEntityEventWrapper<T>(eventName: string): EntityEventWrapper<T>Returns a typed wrapper for one event name, so you subscribe and send through a single typed object.
@component
export class Emoter extends BaseScriptComponent {
private syncEntity = new SyncEntity(this);

onAwake() {
this.syncEntity.onSetupFinished.add(() => {
this.syncEntity.onRemoteEventReceived.add('emote', (message) => {
print(`Remote emote: ${JSON.stringify(message.data)}`);
});
});
}

emote(emoteId: number) {
this.syncEntity.sendEvent('emote', { emoteId });
}
}

Prefer events for moments and storage properties for state. A late joiner sees current state, but never a past event.

Calling sendEvent() before setup has finished logs an error and the event is dropped, it is not queued. Send from onSetupFinished or after await waitForReady().

Was this page helpful?
Yes
No