Storage Properties
Storage properties allow data to be easily synchronized on a SyncEntity and stored for subsequent use. For example, storage properties can be used to update a scoreboard or synchronize the position of an object.
Adding Storage Properties to a SyncEntity
Storage properties need to be added to a SyncEntity before they have any effect. You can add a storage property to a SyncEntity at any time, but it is recommended to do it as early as possible.
- TypeScript
- JavaScript
private exampleProp = StorageProperty.manualInt('exampleProp', 0);
onAwake() {
const syncEntity: SyncEntity = new SyncEntity(script);
syncEntity.addStorageProperty(exampleProp);
}
const exampleProp = StorageProperty.manualInt('example', 0);
const syncEntity = new SyncEntity(script);
syncEntity.addStorageProperty(exampleProp);
Storage properties can also be added to a SyncEntity during construction as a StoragePropertySet. A StoragePropertySet constructor takes an array of StorageProperties as an argument, letting you add them all at once instead of one at a time.
- TypeScript
- JavaScript
const syncEntity: SyncEntity = new SyncEntity(
this,
new StoragePropertySet([
StorageProperty.manualString('myString', 'hello'),
StorageProperty.forPosition(this.getTransform(), true),
]),
true
);
const syncEntity = new SyncEntity(script, new StoragePropertSet([
StorageProperty.manualString("myString", "hello"),
StorageProperty.forPosition(script.getTransform(), true)
));
Manual Storage Properties
Manual storage properties are handled by the developer and meant to be accessed through script. You will be responsible for changing the value and reacting to changes. Depending on the StorageType, smoothing may be configurable.
Manual Storage Types
Manual storage properties are also available for all regular StorageTypes. These storage properties follow the format StorageProperty.manual[StorageType]()
, and take the following parameters: key (string), initial value, and optional smoothing options (SnapshotBufferOptions).
Smoothing is not available for some StorageTypes, like string, boolean, and integer.
- TypeScript
- JavaScript
const scoreProp = StorageProperty.manualInt('score', 0);
const nameProp = StorageProperty.manualString('name', 'hello');
const scaleProp = StorageProperty.manualVec3(
'scale',
this.getTransfrom().getLocalScale(),
{ interpolationTarget: -0.25 }
);
const scoreProp = StorageProperty.manualInt('score', 0);
const nameProp = StorageProperty.manualString('name', 'hello');
const scaleProp = StorageProperty.manualVec3(
'scale',
script.getTransfrom().getLocalScale(),
{ interpolationTarget: -0.25 }
);
Custom Manual Storage Property
StorageProperty.manual()
can be used to configure your own manual storage property, by passing in a property name (string), StorageType, initial value, and optional smoothing options (SnapshotBufferOptions).
- TypeScript
- JavaScript
const matrixProp = StorageProperty.manual(
'matrix',
StorageTypes.mat4,
mat4.identity()
);
const matrixProp = StorageProperty.manual(
'matrix',
StorageTypes.mat4,
mat4.identity()
);
Setting Storage Property Values
Manual storage properties can be updated by calling StorageProperty.setPendingValue()
. In order to have the value sent to the network, the storage property must be modifiable, meaning the SyncEntity’s store is unowned or owned by the user who is setting it.
Storage property updates are sent to the server in the LateUpdate event.
- TypeScript
- JavaScript
const scoreProp = StorageProperty.manualInt('score', 0);
scoreProp.setPendingValue(3);
const textProp = StorageProperty.manualString('myText', '');
textProp.setPendingValue('new text!');
const scoreProp = StorageProperty.manualInt('score', 0);
scoreProp.setPendingValue(3);
const textProp = StorageProperty.manualString('myText', '');
textProp.setPendingValue('new text!');
In some rare situations, you might want to immediately set the current value of the property without waiting until end of frame, or going through the pending loop. You can do this by calling StorageProperty.setValueImmediate()
and passing in its SyncEntity.currentStore
and the new value. Only use this if you are confident that you have permission to change the store, for example after checking SyncEntity.canIModifyStore()
.
- TypeScript
- JavaScript
scoreProp.setValueImmediate(this.syncEntity.currentStore, -1);
scoreProp.setValueImmediate(syncEntity.currentStore, -1);
Getting Storage Property Values
There are three ways to access the value of a storage property: StorageProperty.currentValue
, StorageProperty.pendingValue
, and StorageProperty.currentOrPendingValue
.
CurrentValue
StorageProperty.currentValue
is the value currently stored on the server and expected to be synced across the network. StorageProperty.currentValue
is updated only after the value has been updated on the backend.
PendingValue
StorageProperty.pendingValue
is the local value that will potentially be sent to the backend, but not necessarily. This can be useful if you want your own local value distinct from the network value. For example, you may want to simulate state on an object while you wait for an update from the owner. It is also useful if you want to access the locally changed value of a storage property in the time before it is sent to the server during LateUpdate.
StorageProperty.pendingValue
is only sent to the server if the SyncEntity can be modified (i.e., the SyncEntity is unowned or owned by the local user).
CurrentOrPendingValue
StorageProperty.currentOrPendingValue
can be either the currentValue or the pendingValue, depending on which was set most recently. This is useful if you just want to use whichever value was updated last and do not need to care about where it came from.
When accessing a storage property value from a SyncEntity.notifyOnReady()
callback, use StorageProperty.currentOrPendingValue
. For the first user to join the session StorageProperty.currentValue
may return null
because the value has not yet been updated on the backend.
Reacting to Storage Property changes
Storage properties have events that fire when their values change, depending on who made the change.
OnAnyChange
The StorageProperty.onAnyChange
event occurs when any user has changed the StorageProperty.currentValue
of the storage property.
- TypeScript
- JavaScript
scoreProp.onAnyChange.add((newValue: number, oldValue: number) => {
print('Current value changed from ' + oldValue + ' to ' + newValue);
});
scoreProp.onAnyChange.add(function (newValue, oldValue) {
print('Current value changed from ' + oldValue + ' to ' + newValue);
});
OnLocalChange
The StorageProperty.onLocalChange
event occurs when the StorageProperty.currentValue
has been changed by the local user.
- TypeScript
- JavaScript
scoreProp.onLocalChange.add((newValue: number, oldValue: number) => {
print(
'I changed the current value changed from ' + oldValue + ' to ' + newValue
);
});
scoreProp.onLocalChange.add(function (newValue, oldValue) {
print(
'I changed the current value changed from ' + oldValue + ' to ' + newValue
);
});
OnRemoteChange
The StorageProperty.onRemoteChange
event occurs when the StorageProperty.currentValue
has been changed by another user.
- TypeScript
- JavaScript
scoreProp.onRemoteChange.add((newValue: number, oldValue: number) => {
print(
'Someone else changed the current value changed from ' +
oldValue +
' to ' +
newValue
);
});
scoreProp.onRemoteChange.add(function (newValue, oldValue) {
print(
'Someone else changed the current value changed from ' +
oldValue +
' to ' +
newValue
);
});
OnPendingValueChange
Use the StorageProperty.onPendingValueChange
event to be notified whenever the StorageProperty.pendingValue
is changed. This is useful in cases where send rate limits are enabled on the property, and you would like to react to local changes before they are sent out to the network.
- TypeScript
- JavaScript
scoreProp.onPendingValueChange.add((newValue: number, oldValue: number) => {
print(
'Someone else changed the current value changed from ' +
oldValue +
' to ' +
newValue
);
});
scoreProp.onPendingValueChange.add(function (newValue, oldValue) {
print(
'Someone else changed the current value changed from ' +
oldValue +
' to ' +
newValue
);
});
Automatic Storage Properties
Automatic storage properties can be used to automatically update component and asset properties, such as position, rotation, scale, text, and base color. Automatic storage properties are given getter
and setter
functions that automatically read and write to an external target. They are meant to be added to a SyncEntity and forgotten about.
For example, a storage property for localPosition
automatically reads from Transform.getLocalPosition()
to check the current local value, and writes to Transform.setLocalPosition()
when receiving a network value. You can add the storage property to the object’s SyncEntity, and the local position is automatically kept synchronized.
You can either use built-in automatic storage properties, or create your own.
Autmomatic Transform, Position, Rotation, and scale
For transform-related automatic properties, you need to pass in the PropertyType:
PropertyType.Local
: Synced relative to the scene object’s parent’s coordinate space.PropertyType.Location
: Synced relative to the co-located coordinate space.PropertyType.World
: Synced relative to Spectacles’ world origin, which is not recommended for colocation.
Here are examples of how to set up automatic storage properties for transform data:
- TypeScript
- JavaScript
const transform: Transform = this.getTransform();
const positionType = PropertyType.Local;
const rotationType = PropertyType.Local;
const scaleType = PropertyType.Local;
const transformProp = StorageProperty.forTransform(
transform,
positionType,
rotationType,
scaleType
);
const positionProp = StorageProperty.forPosition(transform, StorageType.Local);
const rotationProp = StorageProperty.forRotation(transform, StorageType.Local);
const scaleProp = StorageProperty.forScale(transform, StorageType.Local);
const transform = script.getTransform();
const positionType = PropertyType.Local;
const rotationType = PropertyType.Local;
const scaleType = PropertyType.Local;
const transformProp = StorageProperty.forTransform(
transform,
positionType,
rotationType,
scaleType
);
const positionProp = StorageProperty.forPosition(transform, StorageType.Local);
const rotationProp = StorageProperty.forRotation(transform, StorageType.Local);
const scaleProp = StorageProperty.forScale(transform, StorageType.Local);
Automatic Base Color
StorageProperty.forMeshVisualBaseColor()
automatically syncs the base color of a MaterialMeshVisual. The first parameter is the MaterialMeshVisual, and the second is an optional clone boolean. If clone is true, the material will be cloned and applied back to the visual, which is useful if the material is used in multiple places.
- TypeScript
- JavaScript
const colorProp = StorageProperty.forMeshVisualBaseColor(visual, false);
const colorProp = StorageProperty.forMeshVisualBaseColor(visual, false);
Automatic Material Property
StorageProperty.forMaterialProperty()
automatically syncs a Material property of your choice. It expects the following parameters: Material, key (string), and StorageType.
- TypeScript
- JavaScript
const materialProp = StorageProperty.forMaterialProperty(
material,
'propName',
StorageTypes.float
);
const materialProp = StorageProperty.forMaterialProperty(
material,
'propName',
StorageTypes.float
);
Automatic Material Mesh Visual
StorageProperty.forMeshVisualProperty()
automatically syncs a MeshVisual property of your choice. It expects the following parameters: MaterialMeshVisual, property name (string), StorageType, and an optional clone boolean. If clone is true, the MeshVisual’s material will be cloned and applied back to the visual, which is useful if the material is used in multiple places.
- TypeScript
- JavaScript
const meshProp = StorageProperty.forMeshVisualProperty(
visual,
'propName',
StorageTypes.float,
true
);
const meshProp = StorageProperty.forMeshVisualProperty(
visual,
'propName',
StorageTypes.float,
true
);
Automatic Text
StorageProperty.forTextText()
automatically syncs the text on a TextComponent. It takes the TextComponent as a parameter.
- TypeScript
- JavaScript
const textProp = StorageProperty.forTextText(textComponent);
const textProp = StorageProperty.forTextText(textComponent);
Automatic Storage Types
Automatic storage properties are available for all regular StorageTypes. These storage properties follow the format StorageProperty.auto[StorageType]()
, and take the following parameters: key (string), getter (function), setter (function), and optional smoothing options (SnapshotBufferOptions).
- TypeScript
- JavaScript
const transform: Transform = this.getTransform();
const scaleProp = StorageProperty.autoVec3(
'localScale',
() => {
// getter
return transform.getLocalScale();
},
(val) => {
// setter
transform.setLocalScale(val);
},
{ interpolationTarget: -0.25 }
);
const transform = script.getTransform();
const scaleProp = StorageProperty.autoVec3(
'localScale',
function () {
// getter
return transform.getLocalScale();
},
function (val) {
// setter
transform.setLocalScale(val);
},
{ interpolationTarget: -0.25 }
);
Custom Automatic Storage Property
StorageProperty.auto()
can be used to configure your own automatic storage property, by passing in a property name (string), StorageType, and getter and setter functions, and optional smoothing options (SnapshotBufferOptions).
- TypeScript
- JavaScript
const transform: Transform = this.getTransform();
const scaleProp = StorageProperty.auto(
'localScale',
StorageTypes.vec3,
() => {
// getter
return transform.getLocalScale();
},
(val) => {
// setter
transform.setLocalScale(val);
},
{ interpolationTarget: -0.25 }
);
const transform = script.getTransform();
const scaleProp = StorageProperty.auto(
'localScale',
StorageTypes.vec3,
function () {
// getter
return transform.getLocalScale();
},
function (val) {
// setter
transform.setLocalScale(val);
},
{ interpolationTarget: -0.25 }
);
Limiting Send Rate
Set StorageProperty.sendsPerSecondLimit
to a value greater than zero to limit how many times per second the property will send updates to the network.
When using this feature, StorageProperty.currentValue
will only be updated when the value is actually sent to the network. To get the most recent local version of a value, check StorageProperty.currentOrPendingValue
.
- TypeScript
- JavaScript
scoreProp.sendsPerSecondLimit = 10;
scoreProp.sendsPerSecondLimit = 10;
Smoothing Property Values
For properties that change very frequently, especially when the send rate is limited, it can be useful to smooth out received values. Otherwise, the result will appear choppy since values are only updated as they are received. When smoothing is enabled, intermediate values are approximated by interpolating between updates. Smoothing is not available for some StorageTypes, like string, boolean, and integer.
SnapshotBufferOptions
Smoothing is configurable using the SnapshotBufferOptions class, which includes the properties below. All properties are optional.
SnapshotBufferOptions.interpolationTarget
: Time delta in local seconds to target (default = -0.25)SnapshotBufferOptions.storageType
: Override the StorageType, if blank the StorageProperty's StorageType will be usedSnapshotBufferOptions.lerpFunc
: Override the function used for interpolating values, if blank one will be chosen based on StorageTypeSnapshotBufferOptions.size
: Max number of snapshots stored (default = 20)
- TypeScript
- JavaScript
private options = new SnapshotBufferOptions();
this.options.interpolationTarget = -0.25;
const options = new SnapshotBufferOptions();
options.interpolationTarget = -0.25;
Call StorageProperty.setSmoothing()
to configure smoothing for the property. You can either pass in a SnapshotBufferOptions object, or a JS object with matching properties. Even passing in an empty object like {}
is enough to enable smoothing.
- TypeScript
- JavaScript
scoreProp.setSmoothing({ interpolationTarget: -0.25 });
scoreProp.setSmoothing({ interpolationTarget: -0.25 });
You can pass in SnapshotBufferOptions as an optional third parameter when constructing a StorageProperty.
- TypeScript
- JavaScript
private prop = StorageProperty('prop', StorageTypes.float, {
interpolationTarget: -0.25,
});
const prop = new StorageProperty('prop', StorageTypes.float, {
interpolationTarget: -0.25,
});
SnapshotBufferOptions can also be passed into automatic and manual StorageProperty constructors that support smoothing.
- TypeScript
- JavaScript
const prop = StorageProperty.forPosition(transform, true, {
interpolationTarget: -0.25,
});
const otherProp = StorageProperty.manualFloat('myFloat', 0, {
interpolationTarget: -0.25,
});
const prop = StorageProperty.forPosition(transform, true, {
interpolationTarget: -0.25,
});
const otherProp = StorageProperty.manualFloat('myFloat', 0, {
interpolationTarget: -0.25,
});
Storage Types
StorageTypes.bool
StorageTypes.float
StorageTypes.double
StorageTypes.int
StorageTypes.string
StorageTypes.vec2
StorageTypes.vec3
StorageTypes.vec4
StorageTypes.quat
StorageTypes.mat2
StorageTypes.mat3
StorageTypes.mat4
StorageTypes.boolArray
StorageTypes.floatArray
StorageTypes.doubleArray
StorageTypes.intArray
StorageTypes.stringArray
StorageTypes.vec2Array
StorageTypes.vec3Array
StorageTypes.vec4Array
StorageTypes.quatArray
StorageTypes.mat2Array
StorageTypes.mat3Array
StorageTypes.mat4Array
StorageTypes.packedTransform
(same asStorageTypes.vec4Array
)
Payload and Rate Limits
Storage Property updates are batched each frame and sent as a single message, which counts against the payload and message rate limits.
If limits are exceeded in Lens Studio, an error will print in the Logger and the session may disconnect.