Members
# id :String
Each view has an id which can be used to scope events between views. It is unique within the session for each user.
Note: The id
is not currently guaranteed to be unique for different users.
Views on multiple devices may or may not be given the same id.
This property is read-only. It is assigned in the view's constructor. There will be an error if you try to assign to it.
Type:
- String
Example:
this.publish(this.id, "changed");
# sessionId :String
Identifies the shared session.
The session id is used as "global" scope for events like the model-only
"view-join"
and "view-exit"
events.
See Session.join for how the session id is generated.
If your app has several sessions at the same time, each session id will be different.
Type:
- String
# session :Object|undefined
The session object
Same as returned by Session.join.
WILL BE UNDEFINED WHEN DISCONNECTED! In callbacks that can still be executed
after a disconnect, you should check if (!this.session) return
to avoid errors.
Type:
- Object | undefined
# viewId :String
Identifies the View of the current user.
All users in a session share the same Model (meaning all model objects) but each user has a different View
(meaning all the non-model state). The viewId
identifies each user's view, or more specifically,
their connection to the server.
It is sent as argument in the model-only "view-join"
and "view-exit"
events.
The viewId
is also used as a scope for local events, for example "synced"
.
Note: this.viewId
is different from this.id
which identifies each individual view object
(if you create multiple views in your code). this.viewId
identifies the local user, so it will be the same
in each individual view object. See "view-join"
event.
Type:
- String
Example:
this.subscribe(this.viewId, "synced", this.handleSynced);
Methods
# detach()
Unsubscribes all subscriptions this view has, and removes it from the list of views
This needs to be called when a view is no longer needed, to prevent memory leaks.
A session's root view is automatically sent detach
when the session becomes
inactive (for example, going dormant because its browser tab is hidden).
A root view should therefore override detach
(remembering to call super.detach()
)
to detach any subsidiary views that it has created.
removeChild(child) {
const index = this.children.indexOf(child);
this.children.splice(index, 1);
child.detach();
}
# publish(scope, event, dataopt)
Publish an event to a scope.
Events are the main form of communication between models and views in Croquet. Both models and views can publish events, and subscribe to each other's events. Model-to-model and view-to-view subscriptions are possible, too.
See Model.subscribe for a discussion of scopes and event names.
Optionally, you can pass some data along with the event. For events published by a view and received by a model, the data needs to be serializable, because it will be sent via the reflector to all users. For view-to-view events it can be any value or object.
Note that there is no way of testing whether subscriptions exist or not (because models can exist independent of views). Publishing an event that has no subscriptions is about as cheap as that test would be, so feel free to always publish, there is very little overhead.
Name | Type | Attributes | Description |
---|---|---|---|
scope |
String | see subscribe() |
|
event |
String | see subscribe() |
|
data |
* |
<optional> |
can be any value or object (for view-to-model, must be serializable) |
this.publish("input", "keypressed", {key: 'A'});
this.publish(this.model.id, "move-to", this.pos);
# subscribe(scope, eventSpec, handler) → {this}
Register an event handler for an event published to a scope.
Both scope
and event
can be arbitrary strings.
Typically, the scope would select the object (or groups of objects) to respond to the event,
and the event name would select which operation to perform.
A commonly used scope is this.id
(in a model) and model.id
(in a view) to establish
a communication channel between a model and its corresponding view.
Unlike in a model's subscribe method, you can specify when the event should be handled:
-
Queued: The handler will be called on the next run of the main loop, the same number of times this event was published. This is useful if you need each piece of data that was passed in each publish call.
An example would be log entries generated in the model that the view is supposed to print. Even if more than one log event is published in one render frame, the view needs to receive each one.
{ event: "name", handling: "queued" }
is the default. Simply specify"name"
instead. -
Once Per Frame: The handler will be called only once during the next run of the main loop. If publish was called multiple times, the handler will only be invoked once, passing the data of only the last
publish
call.For example, a view typically would only be interested in the current position of a model to render it. Since rendering only happens once per frame, it should subscribe using the
oncePerFrame
option. The event typically would be published only once per frame anyways, however, while the model is catching up when joining a session, this would be fired rapidly.{ event: "name", handling: "oncePerFrame" }
is the most efficient option, you should use it whenever possible. -
Immediate: The handler will be invoked synchronously during the publish call. This will tie the view code very closely to the model simulation, which in general is undesirable. However, if the event handler needs to set up another subscription, immediate execution ensures that a subsequent publish will be properly handled (especially when rapidly replaying events for a new user). Similarly, if the view needs to know the exact state of the model at the time the event was published, before execution in the model proceeds, then this is the facility to allow this without having to copy model state.
Pass
{event: "name", handling: "immediate"}
to enforce this behavior.
The handler
can be any callback function.
Unlike a model's handler which must be a method of that model,
a view's handler can be any function, including fat-arrow functions declared in-line.
Passing a method like in the model is allowed too, it will be bound to this
in the subscribe call.
Name | Type | Description | |||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
scope |
String | the event scope (to distinguish between events of the same name used by different objects) |
|||||||||
eventSpec |
String | Object | the event name (user-defined or system-defined), or an event handling spec object Properties
|
|||||||||
handler |
function | the event handler (can be any function) |
- Tutorials:
- Type
- this
this.subscribe("something", "changed", this.update); // "queued" handling implied
this.subscribe(this.id, {event: "moved", handling: "oncePerFrame"}, pos => this.sceneObject.setPosition(pos.x, pos.y, pos.z));
# unsubscribe(scope, event, handlernullable)
Unsubscribes this view's handler(s) for the given event in the given scope.
To unsubscribe only a specific handler, pass it as the third argument.
Name | Type | Attributes | Description |
---|---|---|---|
scope |
String | see subscribe |
|
event |
String | see subscribe |
|
handler |
function |
<nullable> |
(optional) the handler to unsubscribe (added in 1.1) |
this.unsubscribe("something", "changed");
this.unsubscribe("something", "changed", this.handleMove);
# unsubscribeAll()
Unsubscribes all of this view's handlers for any event in any scope.
# future(tOffset) → {this}
Schedule a message for future execution
This method is here for symmetry with Model.future.
It simply schedules the execution using globalThis.setTimeout. The only advantage to using this over setTimeout() is consistent style.
Name | Type | Default | Description |
---|---|---|---|
tOffset |
Number | 0 | time offset in milliseconds |
- Type
- this
# random() → {Number}
Answers Math.random()
This method is here purely for symmetry with Model.random.
- Type
- Number
# now() → {Number}
The model's current time
This is the time of how far the model has been simulated. Normally this corresponds roughly to real-world time, since the reflector is generating time stamps based on real-world time.
If there is backlog however (e.g while a newly joined user is catching up), this time will advance much faster than real time.
The unit is milliseconds (1/1000 second) but the value can be fractional, it is a floating-point value.
- See:
the model's time in milliseconds since the first user created the session.
- Type
- Number
# externalNow() → {number}
The latest timestamp received from reflector
Timestamps are received asynchronously from the reflector at the specified tick rate.
Model time however only advances synchronously on every iteration of the main loop.
Usually now == externalNow
, but if the model has not caught up yet, then now < externalNow
.
We call the difference "backlog". If the backlog is too large, Croquet will put an overlay on the scene,
and remove it once the model simulation has caught up.
The "synced"
event is sent when that happens.
The externalNow
value is rarely used by apps but may be useful if you need to synchronize views to real-time.
the latest timestamp in milliseconds received from the reflector
- Type
- number
const backlog = this.externalNow() - this.now();
# extrapolatedNow() → {number}
The model time extrapolated beyond latest timestamp received from reflector
Timestamps are received asynchronously from the reflector at the specified tick rate.
In-between ticks or messages, neither now() nor externalNow() advances.
extrapolatedNow
is externalNow
plus the local time elapsed since that timestamp was received,
so it always advances.
extrapolatedNow()
will always be >= now()
and externalNow()
.
However, it is only guaranteed to be monotonous in-between time stamps received from the reflector
(there is no "smoothing" to reconcile local time with reflector time).
milliseconds based on local Date.now()
but same epoch as model time
- Type
- number
# update(time)
Called on the root view from main loop once per frame. Default implementation does nothing.
Override to add your own view-side input polling, rendering, etc.
If you want this to be called for other views than the root view, you will have to call
those methods from the root view's update()
.
The time
received is related to the local real-world time. If you need to access the model's time,
use this.now()
.
Name | Type | Description |
---|---|---|
time |
Number | this frame's time stamp in milliseconds, as received by
requestAnimationFrame
(or passed into |
# wellKnownModel(name) → {Model}
Access a model that was registered previously using beWellKnownAs().
Note: The instance of your root Model class is automatically made well-known as "modelRoot"
and passed to the constructor of your root View during Session.join.
Name | Type | Description |
---|---|---|
name |
String | the name given in beWellKnownAs() |
the model if found, or undefined
- Type
- Model
const topModel = this.wellKnownModel("modelRoot");