Croquet 1.1.0

Croquet is a software system for creating multiuser digital experiences on the web. It lets you build real-time multiuser apps without writing a single line of server code, or deploying or managing any servers. Croquet is available as a JavaScript library that synchronizes Croquet apps using Croquet’s globally deployed reflector servers and provides seamless cross-platform real-time interactivity to any user with a network connection and a web browser.

While Croquet itself is independent of any specific UI framework, we provide our own frameworks that build on top of it (see here).

  • A blog post about How Croquet Works by our Chief Architect Vanessa Freudenberg
  • Vanessa gave a Keynote Speech at the Dynamic Languages Symposium in 2020
  • Watch a Video Tutorial our engineers gave to the AR/VR Capstone Class 2021 at UW's Reality Lab

Scroll down for an overview of Croquet, and the Changelog.

Use the Navigation Panel to try our Tutorials and guides and API docs.

Please review our Code of Conduct and join our Developer Discord Discord

Quickstart

First, get a free API key from croquet.io/keys

There are 3 main ways to getting started with Croquet:

  1. CodePen: play with our tutorials, click "Edit on CodePen", and develop your app there. To share it, change the view to full screen, and share the pen's url. Alternatively, click "Export" and choose "Export .zip" to download your app to your local computer for further editing and uploading to your own website.

  2. Script Tag: Add the following inside your page's <head> tag, replacing @1.1.0 with the version you want:

    <meta charset="utf-8">
    <script src="https://cdn.jsdelivr.net/npm/@croquet/croquet@1.1.0"></script>
    

    This will create the Croquet global to access Croquet.Model etc.

    See jsdelivr.com for ways to link to different versions or the latest version automatically. We recommend to link to a specific version in production.

  3. NPM: install the @croquet/croquet package:

    npm install @croquet/croquet
    

    Then import it in your JS file:

    import * as Croquet from "@croquet/croquet"
    

    Again, make sure to specify charset="utf-8" for your HTML or your script tags.

What is Croquet?

Croquet is a synchronization system for multiuser digital experiences. It allows multiple users to work or play together within a single shared distributed environment, and it guarantees that this distributed environment will remain bit-identical for every user.

This synchronization is largely invisible to the developer. Creating a Croquet application does not require the developer to write any server-side or networking code. Applications are structured into a shared and a local part, but both parts are executed locally and developed using only client-side tools. The Croquet library takes care of the rest.

Main Concepts

Every Croquet application consists of two parts:

  • The views handle user input and output. They process all keyboard / mouse / touch events, and determine what is displayed on the screen.

  • The models handle all calculation and simulation. This is where the actual work of the application takes place. The models are saved, shared, and loaded automatically.

Models are guaranteed to always be identical for all users. However, the views are not. Different users might be running on different hardware platforms, or might display different representations of the models.

When you launch a Croquet application, you automatically join a shared session. As long as you're in the session, your models will be identical to the models of every other user in the session.

To maintain this synchronization, models for all users execute in lockstep based on a shared session time.

The views interact with the models through events. When you publish an event from a view, it's mirrored to everyone else in your session, so everyone's models receive exactly the same event stream.

Behind the scenes

Internally, the shared time and event mirroring is handled by a network connection to reflectors. Reflectors are stateless, public message-passing services located in the cloud.

Snapshots are archived copies of all models in a session. Croquet apps periodically take snapshots and save them to the cloud. When you join an existing session, you sync with the other users by loading one of these snapshots.

Your API Key gives your app access to Croquet's reflectors, as well as to Croquet's file servers for storing snapshots.

Creating a Croquet App

To create a new a Croquet app, you define your own models and views. These classes inherit from the base classes Model and View in the Croquet library.

A simple app often only has one model and one view. In that case, the view contains all your input and output code, and the model contains all your simulation code.

class MyModel extends Croquet.Model {
    init() {
        ...
    }
}
MyModel.register("MyModel");

class MyView extends Croquet.View {
    constructor(model) {
        super(model);
        ...
    }

    update(time) {
        ...
    }
}

You then join a session by calling Session.join() and passing it your model and view classes. Session.join automatically connects to a nearby reflector, synchronizes your model with the models of any other users already in the same session, and starts executing.

You do need to provide some session meta data, like your API key, an appId, session name, and a password. Below we use autoSession/autoPassword but you can instead use whatever makes most sense for your app. In the tutorials we even often use constants for all, but you should not do that in production.

const apiKey = "your_api_key"; // paste from croquet.io/keys
const appId = "com.example.myapp";
const name = Croquet.App.autoSession();
const password = Croquet.App.autoPassword();
Croquet.Session.join({apiKey, appId, name, password, model: MyModel, view: MyView});

That's it. You don't need to worry about setting up a server, or writing special synchronization code. Croquet handles all of that invisibly, allowing you to concentrate on what your app does.

Models

Croquet models are a little different from normal JavaScript classes. For one thing, instead of having a constructor, they have an init() method. init only executes the very first time the model is instantiated within a brand new session. If you join a session that's already in progress, your model will be initialized from a snapshot instead.

class MyModel extends Croquet.Model {
    init() {
        ...
    }
}
MyModel.register("MyModel");

Also, every Croquet model class needs to have its static register() method called after it is defined. This registers the model class with Croquet's internal class database so it can be properly stored and retrieved when a snapshot is created.

The root model of your app (the one named in Session.join()) is instantiated automatically. If your application uses multiple models, you instantiate them by calling create() instead of new.

See Model for the full class documentation.

Views

When Session.join creates the local root model and root view, it passes the view a reference to the model. This way the view can initialize itself to reflect whatever state the model may currently be in. Remember that when you join a session, your model might have been initalized by running its init() method, or it might have been loaded from an existing snapshot. Having direct access to the model allows the view to configure itself properly no matter how the model was initialized.

class MyView extends Croquet.View {
    constructor(model) {
        super(model);
        ...
    }

    update(time) {
        ...
    }
}

This illustrates an important feature of Croquet: A view can read directly from a model at any time. A view doesn't need to receive an event from a model to update itself. It can just pull whatever data it needs directly from the model whenever it wants. (Of course, a view must never write directly to a model, because that would break synchronization.)

The root view's update() method is called automatically for every animation frame (usually 60 times a second). This allows the view to continually refresh itself, which is useful for continuous animation (as opposed to updating only when an event is received). Internally this uses a callback via the browser's requestAnimationFrame function, and the callback timestamp is passed as update(timestamp).

If your app uses hierarchical views, your root view's update method needs to call all other views' update.

See View for the full class documentation.

Events

Even though views can read directly from models, the only way for a view to interact with a model is through events.

To send an event, call publish():

this.publish(scope, event, data)
  • Scope is a namespace so you can use the same event in different contexts.
  • Event is the name of the event itself.
  • Data is an optional data object containing addtional information.

And to receive an event, call subscribe():

this.subscribe(scope, event, this.handler)
  • Scope is a namespace so you can use the same event in different contexts.
  • Event is the name of the event itself.
  • Handler is the method that will be called when the event is published. (The handler accepts the data object as an argument.)

An event is routed automatically based on who publishes and who subscribes to it:

Input events (published by a view and handled by a model) are sent to every replica of the model in your current session.

By sending view-to-model events to each participant, Croquet ensures that all replicas of the model stay in sync. All replicas of the model receive exactly the same stream of events in exactly the same order.

Output events (published by a model and handled by a view) are generated by each replica simultaneously and do not require a network roundtrip. Typically they are queued and handled before each frame is rendered.

This is to ensure a strict separation between model code execution and view code execution. The model code must be executed precisely the same for every user to stay in sync, no matter if there are views subscribed on that user's machine or not. All event handlers are executed before invoking update().

Model events (published by a model and handled by a model) are generated and handled by each replica locally. They are not sent via the network. Event handlers are invoked synchronously during publish.

This allows you to use pub/sub inside of your model, if that makes sense for your app. It is equivalent to calling another model's method directly.

View events (published by a view and handled by a view) are also generated and handled locally. They are not sent via the network. Typically they are queued and handled before each frame is rendered.

Again, this allows you to use pub/sub inside your view. It is not a way to communicate between different clients, for that, both clients need to communicate with the shared model.

Both models and views can subscribe to the same event. This can be used to implement immediate user feedback: the local view can update itself by listening to the same input event it is sending via the reflector to the model, anticipating what will happen once the event comes back from the reflector. Care has to be taken to handle the case that another event arrives in the mean time.

There are also two special events that are generated by the system itself: "view-join" and "view-exit". These are broadcast whenever a user joins or leaves a session.

Time

Models have no concept of real-world time. All they know about is simulation time, which is governed by the reflector.

Every event that passes through the reflector is timestamped. The simulation time in the model is advanced up to the last event it received. This allows different replicas of the model to stay in sync even if their local real-world clocks diverge.

Calling this.now() will return the current simulation time.

In addition to normal events, the reflector also sends out a regular stream of heartbeat ticks. Heartbeat ticks advance the model's simulation time even if no view is sending any events. By default the reflector sends out heartbeat ticks 20 times a second, but you can change the frequency at session start.

The method future() can be used to schedule an event in the future. For example, if you wanted to create an animation routine in a model that executes every 100 milliseconds of simulation time, it would look like this:

step() {
    // ... do some stuff ...
    this.future(100).step();
}

Note that the ticks-per-second rate of the reflector is independent of the future interval used by your models. Individual models may use different future times.

Snapshots

Snapshots are copies of the model that are saved to the cloud. When your Croquet application is running, the reflector will periodically tell it to perform a snapshot.

Snapshots are used to synchronize other users when they join a session that's already in progress. They also provide automatic save functionality. If you quit or reload while your application is running, it will automatically reload the last snapshot when the application restarts. However, snapshots are only valid until you update the application code.

Automatic snapshotting is the reason your model state needs to be stored as object properties, as opposed to a more functional style. All the properties of your model objects are serialized automatically (except those whose names start with a dollar sign like this.$foo). JavaScript can not serialize functions, and does not provide access to closure variables, so these cannot be used to hold model state (see Model.types for what types are supported out-of-the-box by the Croquet snapshot mechanism, and how to add support for custom types). The view code has no such restrictions, you can use any style you like, object-oriented or not.

Note: The snapshot code is currently unoptimized, so you may experience a performance hitch when the snapshot is taken. The Croquet team is working to resolve this issue and make snapshots invisible to both user and developer, but for the time being your application may occasionally pause if your model is very large.

Random

Croquet guarantees that the same sequence of random numbers is generated in every replica of your application. If you call Math.random() within a model it will return the same number for all replicas.

Calls to Math.random() within a view will behave normally. Different instances will receive different random numbers.

Technical FAQ

To use Croquet, what changes do I need to make to my firewall configuration?

Most people do not have to modify their network or firewall settings to use any product released by Croquet, including Croquet, Croquet for Unity, Microverse World Builder, or Metaverse Web Showcase.

However, if you are on a network behind a firewall with a strict set of rules, you may have to make some changes to your firewall configuration.

An app built with Croquet will make network connections to the Croquet infrastructure with the following parameters:

Parameter Data
Domain croquet.io (and subdomains)
Hosts croquet.io, api.croquet.io, files.us.croquet.io, files.eu.croquet.io (likely more files.*.croquet.io in the future)
Port 443 (https)

We last updated this information on 2023-05-02.

Changelog

date item
2024-03-20 release 1.1.0 (bug fixes, optimizations, Node.js support, BigInt support, added Model.cancelFuture, Model.evaluate, View.session, debug=write,offline and viewOptions for Session.join, also Model.unsubscribe and View.unsubscribe can take a handler arg, new tutorial: πŸ•ΉοΈ Multiblaster)
2021-11-25 release 1.0.5 (bug fixes, better error reporting, works on insecure origin)
2021-08-24 release 1.0.4 (stricter Session parameter checks)
2021-08-23 release 1.0.3 (bug fixes; require API key, warn about Date usage in model code, event rate limit)
2021-05-18 release 0.5.0 (bug fixes; seamless rejoin with default rejoinLimit of 1000ms, autoSession / autoPassword are async now; added viewCount and static wellKnownModel)
2020-11-20 release 0.4.0 (bug fixes; enable encryption, Session.join takes named args, Model.register requires class id, getModel() and extrapolatedNow() added, removed wellKnownName arg from Model.create)
2020-09-03 release 0.3.3 (bug fixes; session.leave() returns promise, support for virtual-dom)
2020-08-21 release 0.3.2 (bug fixes; much faster session creation)
2020-06-08 release 0.3.1 (bug fixes; "view-join" and "view-exit" events are now model-only)
2020-05-18 release 0.3.0 (bug fixes; Session.join instead of startSession, adds session.leave(), future message arguments are passed by identity, not copied anymore)
2020-03-24 release 0.2.7 (bug fixes; startSession supports passing options to root model's init, message replay no longer visible to app)
2019-12-12 release 0.2.6 (bug fixes; works on MS Edge)
2019-10-18 release 0.2.5 (bug fixes; new widget API) version aligned with npm
2019-10-01 release 0.2.2 (bug fixes; updated qr-code support)
2019-09-13 release 0.2.1 (bug fixes)
2019-09-05 release 0.2.0 (scalable reflector fleet, snapshots in bucket)
2019-08-14 release 0.1.9 (bug fixes; automatic reflector selection)
2019-07-24 release 0.1.8 (bug fixes)
2019-07-24 release 0.1.7 (bug fixes; reverted to 0.1.6 due to instabilities)
2019-07-23 new US east coast reflector available in startSession
2019-07-18 release 0.1.6 (bug fixes; documentation updates;
inactive clients will now be disconnected after 10 seconds)
2019-07-10 release 0.1.5 (bug fixes)
2019-07-09 release 0.1.4 (bug fixes)
2019-07-09 tutorial fleshed out: πŸ’» 3D Animation
2019-07-06 new tutorial: πŸ’» View Smoothing
2019-07-01 release 0.1.3 (bug fixes; add 5-letter moniker to session badge)
2019-06-29 release 0.1.2 (bug fixes)
2019-06-28 release 0.1.1 (bug fixes)
2019-06-27 docs: View.subscribe, startSession
2019-06-26 release 0.1.0

Copyright Β© 2019-2023 Croquet Corporation

THE CROQUET CLIENT LIBRARY IS PROVIDED β€œAS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.