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?)
| Option | Type | Description |
|---|---|---|
propertySet | StoragePropertySet | Storage properties to keep in sync. |
ownership | Ownership | Ownership for the realtime store. Defaults to Unowned unless this is part of an instantiated prefab. |
persistence | Persistence | Persistence for the realtime store. Defaults to Session. |
networkIdOptions | NetworkIdOptions | How 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 | null | Finds the Sync Entity already attached to a component. |
SyncEntity.getSyncEntityOnSceneObject(sceneObject: SceneObject): SyncEntity | null | Finds the Sync Entity on a scene object. |
SyncEntity.findById(networkId: string): SyncEntity | null | Looks up an entity by its network id. |
Properties
| Name | Type | Description |
|---|---|---|
networkId | string | Unique id for this entity across the session. |
currentStore | GeneralDataStore | The underlying realtime store. |
persistence | Persistence | Configured persistence. |
ownership | Ownership | Configured ownership. |
ownerInfo | ConnectedLensModule.UserInfo | null | Current owner, if any. |
isHostOwned | boolean | Whether the store is host-owned. |
propertySet | StoragePropertySet | The set of synced properties. |
networkRoot | NetworkRootInfo | Set when this entity belongs to an instantiated prefab. |
isSetupFinished | boolean | Whether setup has completed. |
destroyed | boolean | Whether the entity has been destroyed. |
storeCallbacks | StoreEventWrapper | Low-level store event callbacks. |
messaging | NetworkMessageWrapper | Low-level messaging wrapper. |
Ownership
Ownership decides who is allowed to write. Check before writing rather than writing and hoping.
| Name (signature) | Description |
|---|---|
canIModifyStore(): boolean | Whether the local client may write. Check this before every write. |
doIOwnStore(): boolean | Whether the local client owns the store. |
isStoreOwned(): boolean | Whether anyone owns the store. |
getOwnerId(): string | null | Owner connection id. |
getOwnerUserId(): string | null | Owner persistent user id. |
getOwnerConnectionId(): string | null | Owner 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(): SessionController | The owning Session Controller. |
getSession(): MultiplayerSession | null | The underlying session. |
addStorageProperty<T>(storageProperty: StorageProperty<T>): StorageProperty<T> | Adds a property to the set after construction, returning it for chaining. |
destroy(deleteStore = false): void | Destroys the entity, optionally deleting its store. |
Events
| Name | Called with | Description |
|---|---|---|
onSetupFinished | none | Setup completed. Safe to read and write from here. |
onOwnerUpdated | userInfo | Ownership changed hands. |
onEventReceived | NetworkMessage | A custom event arrived, local or remote. |
onRemoteEventReceived | NetworkMessage | A custom event arrived from another client only. |
onDestroyed | none | The entity was destroyed. |
onLocalDestroyed | none | The entity was destroyed locally. |
onRemoteDestroyed | none | The 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().
Related
- Storage Property
- Session Controller
- Sync Helpers: enable objects once an entity is ready or owned.