nengi.Instance

Updated for v1.0.0

Instances are the serverside of nengi. Instances handle connections from clients, receive commands, and send out data.

Instance API at a glance

const instance = new nengi.Instance(nengiConfig, webConfig)
instance.onConnect((client, clientData, callback) => {})
instance.onDisconnect(client => {})
instance.addEntity(entity)
instance.removeEntity(entity)
instance.addLocalMessage(localMessage)
instance.sendMessage(message, clientOrClients)
instance.getNextCommand()
instance.update()

constructor

const instance = new nengi.Instance(nengiConfig, webConfig)

The same nengiConfig must be used for both nengi.Instance nengi.Client. The nengiConfig contains the common language that allow the client and server to communicate. See the manual page for nengiConfig for more information.

The webConfig can be either { port } which will create an http+websocket server and start listening, or can have an good ol' node { httpServer } passed into it.

Entities + Messages

See the manual page for Entity for more information.

See the manual page for LocalMessage for more information.

See the manual page for Message for more information.

connecting a client

instance.onConnect((client, clientData, callback) => { 
    callback({ accepted: true, text: 'Welcome!' })
    // or callback({ accepted: fase, text: 'some error message' }) 
})

This `client` object is created on connect, and the very same `client` object is associated with any commands they send. Feel free to attach state to the client object when they first connect, and access that state later when processing their commands.

inspecting clientData

The clientData object contains data sent by the game client along with its connection handshake. The clientData.fromClient is populated by the second argument sent via client.connect(address, { foo: 'bar' }). This can be used to submit an authToken or other means of identifying a player.

{
    fromClient: {
        foo: 'bar',
    }
}

Keep in mind that clientData.fromClient originates from the game client, and is therefore inherently unauthoritative and potentially fraudulent. Be sure to sanitize whatever it is!

accepting or denying connections

A client's connection is accepted when the we invoke the callback with { accepted: true, text: '' } and the connection is denied if we invoke the callback with { accepted: false, text: '' }. The text property exists so that we can provide the client application a reason for denied connections.

warning about onConnect

Any user code (your code) in the onConnect handler will silently fail if it throws an exception. The client will not connect, the server will not crash, and everything will be generally confusing. This is a BUG caused by an overly drastic security feature that I added, and hopefully will be fixed in the future. If you believe you have encountered this problem, then I suggest using breakpoints or console log statements all over the onConnect user code until the offending line is found.

processing commands from clients

In between ticks on the server, commands sent from clients build up into a queue. This is how we process that queue:

let cmd = null
while (cmd = this.instance.getNextCommand()) {
    const tick = cmd.tick
    const client = cmd.client

    for (let i = 0; i < cmd.commands.length; i++) {
        const command = cmd.commands[i]

        if (command.protocol.name === 'Something') {
            // do something to the game
        }
    }
}

I recommend processing commands at the *start* of each tick on the server.

sending out data

instance.update()

Invoking instance.update will send out a snapshot of the instance state to each client. I recommend doing this at the *end* of each tick on the server.