JSPM

  • Created
  • Published
  • Downloads 19
  • Score
    100M100P100Q73456F
  • License MIT

Realtime database for multiplayer games

Package Exports

  • mudb
  • mudb/client
  • mudb/server

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (mudb) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

mudb

A database for HTML5 multiplayer games.

example

This is heavily commented example showing how to create a server/client pair and protocol using mudb. A system using mudb has to specify 3 things:

  1. A protocol
  2. A server
  3. A client

Here is an example of a trivial game where many users can move a box around with their mouse, broken into 3 files:

mouse game

First, we create a module which defines the network protocol called protocol.js here for simplicity:

procotol.js

// Here we load in some basic schema types
const MuFloat64 = require('muschema/float64')
const MuStruct = require('muschema/struct')
const MuDictionary = require('muschema/dictionary')

// We define a point type using mustruct as a pair of floating point numbers
const Point = MuStruct({
    x: MuFloat64(),
    y: MuFloat64()
})

module.exports = {
    client: {
        state: Point,
        message: {},
        rpc: {},
    },
    server: {
        state: MuDictionary(Entity),
        message: {},
        rpc: {},
    },
}

Then, we define a server module. This exports a function which takes a munet socketServer as input.

server.js

const createServer = require('mudb/server')
const protocol = require('./protocol')

module.exports = function (socketServer) {
    const server = createServer({
        protocol,
        socketServer
    })

    return server.start({
        message: {},
        rpc: {},
        ready() {},
        connect(client) {
            // when a client connects we create a new entry in the player dictionary
            server.state[client.sessionId] = client.schema.clone(client.state);
            server.commit();
        },
        state(client) {
            const serverEntity = server.state[client.sessionId];
            const clientEntity = client.state;
            serverEntity.x = clientEntity.x;
            serverEntity.y = clientEntity.y;
            server.commit();
        },
        disconnect(client) {
            delete server.state[client.sessionId];
            server.commit();
        },
    })
}

Finally we create a client which renders the state of the world using HTML 5 canvas and collects user input:

client.js

const createClient = require('mudb/client')
const protocol = require('./protocol')

module.exports = function (socket) {
    const client = createClient({
        protocol,
        socket
    })

    const canvas = document.createElement('canvas');
    canvas.width = canvas.height = 256;
    document.body.appendChild(canvas);
    const context = canvas.getContext('2d');

    function draw () {
        if (!client.running || !context) {
            return;
        }

        // clear background
        context.fillStyle = '#000';
        context.fillRect(0, 0, 256, 256);

        // draw all objects in the game
        const state = client.server.state;
        Object.keys(state).forEach((id) => {
            if (id !== client.sessionId) {
                context.fillStyle = '#fff';
                context.fillRect(state[id].x - 2.5, state[id].y - 2.5, 5, 5);
            }
        });

        // draw client cursor
        context.fillStyle = '#f00';
        context.fillRect(client.state.x - 3, client.state.y - 3, 6, 6);

        requestAnimationFrame(draw);
    }

    return client.start({
        message: {},
        rpc: {},
        ready (err?:any) {
            if (!err) {
                canvas.addEventListener('mousemove', (ev) => {
                    const bounds = canvas.getBoundingClientRect()
                    client.state.x = ev.clientX - bounds.left
                    client.state.y = ev.clientY - bounds.top
                    client.commit()
                });
                draw()
            }
        },
        state () {},
        close () {
            document.body.removeChild(canvas)
        },
    })
}

running the game

example-local.js

const { createLocalSocket, createLocalSocketServer } = require('munet/local')
const exampleServer = require('./server')
const exampleClient = require('./client')

const socketServer = createLocalSocketServer()
exampleServer(socketServer)

const addClientButton = document.createElement('input');
addClientButton.value = 'add client';
addClientButton.type = 'button';
addClientButton.addEventListener('click', 
    () => exampleClient(createLocalSocket({
        sessionId: 'client' + Math.random(),
        socketServer    
    })));

example-ws-server.js

// TODO

example-ws-client.js

// TODO

table of contents

1 install

npm install mudb muschema munet

2 api

2.1 protocols

The first step to creating any application with mudb is to specify a protocol schema using muschema. Each protocol then specifies two protocol interfaces, one for the client and one for the server. A protocol interface is an object with the following properties:

  • state which defines the state protocol
  • message which is an object containing all message types and their arguments
  • rpc which is an object containing all rpc types and their arguments

Example:

Here is a protocol for a simple game with a chat server

const MuVoid = require('muschema/void')
const MuFloat = require('muschema/float64')
const MuStruct = require('muschema/struct')
const MuString = require('muschema/string')
const MuDictionary = require('muschema/dictionary')

const Vec2 = MuStruct({
    x: MuFloat(),
    y: MuFLoat()
})

const Agent = MuStruct({
    position: Vec2,
    velocity: Vec2,
    name: MuString('player')
})

module.exports = {
    client: {
        state: Agent,
        message: {
            chat: MuStruct({
                sender: MuString(),
                message: MuString()
            }
        },
        rpc: {}
    },
    server: {
        state: MuDictionary(Agent),
        message: {
            chat: MuString()
        },
        rpc: {
            setName:[MuString(), MuVoid()]
        }
    }
}

2.2 server

A server in mudb processes messages from many clients. It may choose to accept or reject incoming connections and dispatch messages to clients as appropriate.

2.2.1 server constructor

mudb/server exports the constructor for the server. It takes an object which accepts the following arguments:

  • protocol which is a protocol schema as described above (see muschema for more details)
  • socketServer a munet socket server instance (see munet for more details)
  • windowLength an optional parameter describing the number of states to buffer

Example:

const server = require('mudb/server')({
    socketServer: ..., // some socket server interface created by munet
    protocol
})

2.2.2 server events

Once a server is created, it is necessary to register event handlers and start the server. This is done using the server.start() method. This method takes an object that has the following properties:

  • ready() which is called when the server is started
  • message which is an object containing implementations of handlers for all of the message types
  • rpc an object implementing all handlers for rpc types
  • connect(client) called when a client connects to the server
  • state(client) called when a new state update is recieved by a client
  • disconnect(client) called when a client disconnects from the server

Example:

Working from the chat server schema above, here is a simple server for the above chat log

server.start({
    ready() {
        console.log('server started')
    },
    message: {
        chat(client, msg) {
            server.broadcast.chat(server.state[client.sessionId].name, msg)
        }
    },
    rpc: {
        setName(client, name, cb) {
            const ids = Object.keys(server.state)
            for (let i = 0; i < ids.length; ++i) {
                if (server.state[ids[i]].name === name) {
                    return cb('name already in use')    
                }
            }
            server.state[client.sessionId].name = name
            return cb()
        }
    },
    connect(client) {
        server.state[client.sessionId] = client.schema.create()
        server.broadcast.chat('server', 'player joined')
        server.commit()
    },
    state(client) {
        const serverState = server.state[client.sessionId]
        const clientState = client.state

        serverState.position.x = clientState.position.x
        serverState.position.y = clientState.position.y
        serverState.velocity.x = clientState.velocity.x
        serverState.velocity.y = clientState.velocity.y

        server.commit()
    },
    disconnect(client) {
        delete server.state[client.sessionId]
        server.commit()
    }
})

2.3 client

2.3.1 client constructor

const client = require('mudb/client')({
    socket,
    protocol
})

2.3.2 client events

  • ready()
  • message
  • rpc
  • state()
  • close()

2.4 state

2.5 rpc

2.6 messages

2.6.1 broadcast

3 usage tips

4 more examples

TODO

  • more test cases

credits

Development supported by Shenzhen DianMao Digital Technology Co., Ltd.

Written in Shenzhen, China.

(c) 2017 Mikola Lysenko, Shenzhen DianMao Digital Technology Co., Ltd.