Skip to main content
Supported on
Snapchat

Storage Property

A StorageProperty is one value kept in sync across the session: a score, a position, a game phase, a player's ready flag. You group them into a StoragePropertySet and hand that to a Sync Entity.

Properties come in two kinds. Manual properties are ones you set yourself. Automatic properties read from a getter and write through a setter, so the framework detects changes for you.

Manual versus automatic

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

@component
export class Example extends BaseScriptComponent {
private currentHealth = 100;

// Manual: you call setPendingValue when the value changes.
private score = StorageProperty.manualInt('score', 0);

// Automatic: the framework reads the getter and applies remote
// values through the setter.
private health = StorageProperty.auto<number>(
'health',
StorageTypes.int,
() => this.currentHealth,
(value) => {
this.currentHealth = value;
}
);

scorePoint() {
this.score.setPendingValue((this.score.currentOrPendingValue ?? 0) + 1);
}
}

Use manual when writes happen at discrete moments, such as scoring. Use automatic when the value lives somewhere else already and changes continuously.

Reading a value

Three members hold a value, and picking the wrong one is the most common source of confusion:

NameTypeDescription
currentValueType | nullThe value believed to be synced across the network. In most cases this is what you want to read.
pendingValueType | nullThe local value that may be sent at the next opportunity. May differ from currentValue.
currentOrPendingValueType | nullThe most recently changed local value, whether current or pending. Read this when you want the most up-to-date local value.

The distinction matters when sendsPerSecondLimit is set: currentValue only updates when the value is actually sent to the network, so a value changing every frame will look stale there while currentOrPendingValue stays fresh.

Members

NameTypeDescription
sendsPerSecondLimitnumberIf zero or greater, limits how often updates are sent. Useful to avoid rate limiting when a value changes every frame. Defaults to -1, meaning unlimited.
equalsCheck(a, b) => booleanComparison used to detect a change. Should return true when two values are equal or reasonably close. Defaults to strict equality.
needToSendUpdatebooleanWhether a change is waiting to be sent.
markedDirtybooleanSet to force a send and skip the equality check.
getterFunc(() => Type) | nullGetter, for automatic properties.
setterFunc((val: Type) => void) | nullSetter, for automatic properties.
Name (signature)Description
setPendingValue(newValue: Type): voidSets the local value, to be sent at the next opportunity.

Events

NameCalled withDescription
onAnyChangenewValue, oldValue, updateInfo | nullcurrentValue changed, by any client.
onRemoteChangenewValue, oldValue, updateInfocurrentValue changed by a remote client.
onLocalChangenewValue, oldValuecurrentValue changed by the local client.
onPendingValueChangenewValue, oldValueThe pending value changed.

Use onRemoteChange when the local client already knows about its own change and only needs to react to others. Use onAnyChange for UI that should reflect the value regardless of who set it.

Factories

Manual

StorageProperty.manual<T>(key, propertyType, startingValue?, smoothingOptions?) is the general form, where propertyType is a StorageTypes value from ConnectedFramework.lspkg/Core/StorageTypes. Typed shorthands avoid passing a StorageTypes value:

manualString, manualBool, manualInt, manualFloat, manualDouble, manualQuat, and the array variants manualBoolArray, manualStringArray, manualIntArray, manualFloatArray, manualDoubleArray, manualQuatArray.

Automatic

StorageProperty.auto<T>(key, propertyType, getterFunc, setterFunc, smoothingOptions?) is the general form, with the same shorthands: autoBool, autoString, autoInt, autoFloat, autoDouble, autoQuat, and the array variants autoStringArray, autoBoolArray, autoFloatArray, autoDoubleArray, autoIntArray, autoQuatArray.

Bound to scene state

These wire a property directly to something in the scene, so you do not write a getter and setter yourself:

Name (signature)Description
forTransform(transform, positionType, rotationType, scaleType, smoothingOptions?)Syncs a transform. This is what Sync Transform uses.
forPosition(transform, ...)Syncs position only.
forRotation(transform, ...)Syncs rotation only.
forScale(transform, ...)Syncs scale only.
forTextText(text: Text)Syncs a Text component's string.
forMaterialProperty<T>(...)Syncs a named material property. Used by Sync Materials.
forMeshVisualProperty<T>(...)Syncs a named mesh visual property.
forMeshVisualBaseColor(...)Syncs a mesh visual's base color.
wrapProperty(...)Wraps an arbitrary object property.
getStoreValueDynamic<T>(...)Reads a store value whose type is decided at runtime.

Smoothing

Every factory takes optional smoothing options. With smoothing on, remote values are interpolated rather than snapped, which matters for anything visibly moving. Sync Transform exposes this as a checkbox plus an interpolation target.

Usage

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

@component
export class RoundPhase extends BaseScriptComponent {
@input
phaseLabel!: Text;

private phase = StorageProperty.manualString('phase', 'waiting');
private syncEntity = new SyncEntity(this, {
propertySet: new StoragePropertySet([this.phase]),
});

onAwake() {
this.phase.onAnyChange.add((newValue) => {
this.phaseLabel.text = newValue;
});
}

startRound() {
if (this.syncEntity.canIModifyStore()) {
this.phase.setPendingValue('playing');
}
}
}
Was this page helpful?
Yes
No