npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@hastearcade/snowglobe

v0.4.15

Published

A TypeScript port of CrystalOrb, a high-level Rust game networking library

Downloads

49

Readme

Snowglobe ☃️

An experimental TypeScript port of the Rust multiplayer game networking library CrystalOrb by Ernest Wong.

Snowglobe is under active development and will break often. The API/implementation will eventually diverge from CrystalOrb due to particular characteristics of JavaScript, but for now it remains virtually 1:1.

Snowglobe demo

Install

Install snowglobe using NPM:

npm i @hastearcade/snowglobe

About

Snowglobe is an orchestrator designed to solve state syncronization between a game server and a set of game clients. In a perfect network, maintaining the state of your game world between a set of parties would simply be a matter of applying updates to the game world to all parties simultaneously, but we do not live in a perfect world.

The networking and physics libraries required to build a game or simulation are outside the scope of Snowglobe. Snowglobe will orchestrate the game sync algorithm, but to build a full game the developer will need to utilize networking libraries like Geckos.io and physics libraries like Rapier.

The problem becomes non-trivial when layering in latency, bad actors, and disconnects. Glenn Fiedler describes the problems and solutions here.

Snowglobe assists in solving the difficulty of networking physics for Typescript based games or simulations by implementing Client-side prediction, Server reconciliation, and Display State interpolation. Ernest Wong and the Crystal Orb team did an excellent job describing the solution as:

  • Client-side prediction. Clients immediately apply their local input to their simulation before waiting for the server, so that the player's inputs feel responsive.
  • Server reconciliation. The Server runs a delayed, authoritative version of the simulation, and periodically sends authoritative snapshots to each client. Since the server's snapshots represent an earlier simulation frame, each client fast-forwards the snapshot they receive until it matches the same timestamp as what's being shown on screen. Once the timestamps match, clients smoothly blend their states into the snapshot states.
  • Display state interpolation. The simulation can run at a different time-step from the render framerate, and the client will automatically interpolate between the two simulation frames to get the render display state.

Quick Start

Below is a (non-comprehensive) sample of the ported CrystalOrb API. This example is missing critical features like a game loop, networking, serialization, etc.

See the Code Architecture section below for a more detailed description of the Snowglobe types and integration points.

import * as Snowglobe from 'snowglobe'

type MyCommand = Snowglobe.Command & { kind: 'jump' }

type MySnapshot = Snowglobe.Snapshot & {
  position: number
  velocity: number
}

type MyDisplayState = Snowglobe.DisplayState & {
  position: number
  velocity: number
}

function interpolate(state1: MyDisplayState, state2: MyDisplayState, t: number) {
  return {
    // simple lerp function
    position: (1 - t) * state1.position + t * state2.position,
    velocity: (1 - t) * state1.velocity + t * state2.velocity
  }
}

class Net implements Snowglobe.NetworkResource {
  // implement methods like send(), connections(), etc.
}

class World implements Snowglobe.World {
  // implement step(), applyCommand(), applySnapshot(), etc.
}

const config: Snowglobe.Config = Snowglobe.makeConfig()
const makeWorld = () => new World()
const client = new Client(makeWorld, config, interpolate)
const server = new Server(makeWorld(), config, 0)

Examples

npm run example:standalone # starter example
npm run example:demo # mock client/server demo

Code Architecture

The following diagram shows a visual representation of the code architecture. The diagram is meant to be understood by reading alongside examples/standalone.ts.

Snowglobe Diagram

The typical game or simulation will have a game loop that runs at 60 fps. Snowglobe is responsible for facilitating client side prediction, server reconciliation, and display state interpolation. This helps solve some of the challenges with networked games and physics. Snowglobe provides the following types to be inherited from in order to utilize Snowglobe's toolset.

World: All games start with a representation of a World. To utilize Snowglobe you will need to inherit from Snowglobe.World and implement the step, commandIsValid, applyCommand, applySnapshot, snapshot, and displayState functionality. Please send the standalone implementation of MyWorld for more details.

DisplayState: While every game has a World, not every game needs to display the entire world to the player at one time. You might have many objects that need to be culled from the scene. Thus, Snowglobe assumes there is an abstraction of the rendered state called DisplayState. As a developer, you must create a custom MyDisplayState class that inherits from Snowglobe.DisplayState. This display state should represent the minimal amount of state necessary to render to the screen.

Snapshot: Snapshots are points in time pictures of a world. All data is frozen and utilized by the Snowglobe system to interpolate state changes between the Client and Server. A snapshot is the minimal data object representing the entire physics simulation. The goal should be to keep the size of your snapshots as small as possible to reduce the load on the network. MySnapshot could have the same properties as MyWorld, but it is unlikely in a real world example.

Command: A Snowglobe command represents a player or server issuing an instruction to the game world. This could be moving left or spawning new NPCS. The game world will need to have the command processed which will result in a change of world state. This world state change will then need to be syncronized amongst the other clients. The developer will need to implement a MyCommand type that inherits from Command. This type should have all available commands that can be used by the system. In addition, the commandIsValid function of MyWorld will need to ensure that any command sent to the world is valid. For example, there may be certain commands that can only be processed by the server but not the Client

Once you have implemented MyWorld, MyDisplayState, MySnapshot, and MyCommand then Snowglobe will need to be incorporated into your game loop.

The game loop for standalone.ts has more code to facilitate the mock network needed than the actual Snowglobe integration. The primary integrations for Snowglobe are as follows:

  1. Create a Snowglobe Server or Client: const client1 = new Snowglobe.Client(makeWorld, config, interpolate)
  2. Issue commands:
client1Stage.ready.issueCommand(
  {
    kind: 'accelerate',
    clone() {
      return { ...this }
    }
  },
  client1Net
)
  1. Retrieve display state: client2DisplayState = client2Stage.ready.displayState()
  2. Update Client/Server: client1.update(deltaSeconds, secondsSinceStartup, client1Net)

TODO

  1. Make lag compensation optional
  2. Improve ownership management of entities and make it easier on the consumer of the library
  3. Document each setting in makeconfig so consumers know what impact it has
  4. Refactor Timestamps back to use Symbols, but make sure they work over real network/there is documentation
  5. Move geckos network into snowglobe as a plugin
  6. Update README with new lag compensation changes
  7. Fix build process to work with next/auto deploy with semantic versioning