nengi.Instance

Updated for v0.3.3

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

Instance API at a glance

let instance = new nengi.Instance(nengiConfig, { port: 8079 })
instance.onConnect((client, clientData, callback) => { 
    callback({ accepted: true, text: 'Welcome!' })
    // or callback({ accepted: fase, text: 'some error message' }) 
})
instance.onDisconnect(client => {})
instance.addEntity(entity)
instance.removeEntity(entity)
instance.addLocalMessage(localMessage)
instance.sendMessage(message, clientOrClients)
instance.getNextCommand()
instance.update()

constructor

// create an instance
let 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 either have a port as shown above in which case nengi will create an http+websocket server and start listening, or can have an httpServer passed into it. Passing in your own httpServer allows for more detailed control and is one way to enable HTTPS/WSS. Personally I prefer doing the encryption via nginx.

Entities

See the manual page for Entity for more information.

var entity = new HypotheticalEntity()
instance.addEntity(entity)
instance.removeEntity(entity)

An entity prior to being added to an instance has no id. After being added, the instance adds an id, type, and protocol directly to the entity object. When an entity is removed from an instance, its id is changed to -1.

LocalMessages

See the manual page for LocalMessage for more information.

instance.addLocalMessage(new ExplosionEffectMessage(534, 324))

That's just a hypothetical example of course ;).

Messages

See the manual page for Message for more information.

instance.sendMessage(new MapDataMessage(), clientOrClients)

connecting a client

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

inspecting clientData

The clientData object contains data sent by the game client along with its connection handshake and (in a future version of nengi) data sent from another nengi instance if this player was transfered here. 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',
    },
    fromTransfer: { /* not covered in this manual */ }
}

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 only so that we can provide our client application more context for denied connections. The clientData object

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

Inbetween 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()) {
    let tick = cmd.tick
    let client = cmd.client

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

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

This code should be run every tick on the server and used to translate the player commands into changes in the game world (e.g. moving the entity they control, etc).

sending out data

instance.update()

Invoking instance.update will send out a snapshot of the instance state to each client. In a realtime game this should be done towards the end of every server tick.