mudb
Version:
Real-time database for multiplayer games
381 lines (314 loc) • 10.3 kB
Markdown
# core
the starting point of creating a `mudb` application
Building a `mudb` application is all about implementing protocols.
* construct client/server with the socket implementation of your choice
* define messaging interfaces and group them together by protocol
* register protocols and the corresponding message and event handlers
## example
**schema.ts**
```ts
import { MuStruct, MuASCII, MuUTF8 } from 'mudb/schema'
// you can define as many protocols as you need
// each protocol should be a sensible grouping of several messaging interfaces
// minimal but useless protocol
export const DummyProtocolSchema = {
client: { },
server: { },
}
export const ChatProtocolSchema = {
// useful for tracing
name: 'chat',
// define all message types sent to client
// key: message type
// value: message structure
client: {
// all `log` messages share the structure { id:string, text:string }
log: new MuStruct({
id: new MuASCII(),
text: new MuUTF8(),
})
},
// define all message types sent to server
server: {
direct: new MuStruct({
id: new MuASCII(),
text: new MuUTF8(),
}),
world: new MuUTF8(),
},
}
```
**server.ts**
```ts
import http = require('http')
import { MuWebSocketServer } from 'mudb/socket/web/server'
import { MuServer } from 'mudb/server'
import { ChatProtocolSchema, DummyProtocolSchema } from './schema'
// construct server
const httpServer = http.createServer()
const socketServer = new MuWebSocketServer({
server: httpServer,
})
const server = new MuServer(socketServer)
// register protocols
const serverChatProtocol = server.protocol(ChatProtocolSchema)
const serverDummyProtocol = server.protocol(DummyProtocolSchema)
// configure protocols
serverChatProtocol.configure({
// define message handlers, which must coincide with `ChatProtocolSchema.server`
message: {
direct: (client, { id, text }) => {
// send a `log` message to a specific client
// refer to `ChatProtocolSchema.client.log` for the valid message structure
serverChatProtocol.clients[id].message.log({
id: client.sessionId,
text: text,
})
},
world: (client, text) => {
// send a `log` message to all connected clients
serverChatProtocol.broadcast.log({
id: client.sessionId,
text: text,
})
}
}
})
// start server
server.start()
httpServer.listen(9966)
```
**client.ts**
```ts
import { MuWebSocket } from 'mudb/socket/web/client'
import { MuClient } from 'mudb/client'
import { ChatProtocolSchema, DummyProtocolSchema } from './schema'
// construct client
const socket = new MuWebSocket({
sessionId: Math.random().toString(36).substring(2),
url: 'ws://127.0.0.1:9966'
})
const client = new MuClient(socket)
// register protocols
// note that you must register the same set of protocols in the same order as the server code does
const clientChatProtocol = client.protocol(ChatProtocolSchema)
const clientDummyProtocol = client.protocol(DummyProtocolSchema)
// configure protocols
clientChatProtocol.configure({
// define message handlers, which must coincide with `ChatProtocolSchema.client`
message: {
log: ({ id, text }) => {
console.log(`${id}: ${text}`)
}
}
})
// start client
client.start({
ready: () => {
// send a `world` message to server when client is ready
clientChatProtocol.server.message.world('hi everyone')
}
})
```
## API
* server-side
* [`MuServer`](#muserver)
* [`MuServerProtocol`](#muserverprotocol)
* [`MuRemoteClient`](#muremoteclient)
* client-side
* [`MuClient`](#muclient)
* [`MuClientProtocol`](#muclientprotocol)
* [`MuRemoteServer`](#muremoteserver)
---
### `MuServer`
```ts
import { MuServer } from 'mudb/server'
new MuServer(
socketServer:MuSocketServer,
logger?:MuLogger,
skipProtocolValidation:boolean=false,
)
```
* `socketServer` a [`MuSocketServer`](socket/README#musocketserver) that handles communications with clients
* `skipProtocolValidation` whether to skip validating protocol consistency
#### props
```ts
bandwidth:{
[sessionId:string]:{
sent:{
[message:string]:{
count:number,
bytes:number,
},
},
received:{
[message:string]:{
count:number,
bytes:number,
},
},
},
}
```
An accumulator tracking bandwidth usage by session.
#### methods
```ts
protocol(schema:{
server:{ [message:string]:MuSchema<any> },
client:{ [message:string]:MuSchema<any> },
name?:string,
}) : MuServerProtocol
```
Registers a protocol.
Caveats
* You CANNOT register or configure protocols after the server is started.
```ts
start(spec?:{
ready?:() => void,
close?:(error?:any) => void,
}) : void
```
* `ready()` is called when the socket server is up and running
* `close()` is called when the socket server is being shut down
```ts
destroy() : void
```
Shuts down the server and invoke close handlers.
---
### `MuServerProtocol`
Created by calling `server.protocol()`, carries a bunch of server-side message dispatchers and event handlers exclusive to the protocol.
#### props
```ts
server:MuServer
```
```ts
clients:{ [sessionId:string]:MuRemoteClient }
```
A table of server-side abstractions of connected clients, indexed by session id.
```ts
broadcast:{ [message:string]:(data:any, unreliable?:boolean) => void }
```
A table of message dispatchers, calling one of which will broadcast a message to all connected clients. For example, calling `protocol.broadcast.sync(state)` will send `state` as a `sync` message to all the clients through reliable delivery.
#### methods
```ts
configure(spec:{
message:{ [type:string]:(client:MuRemoteClient, data:any, unreliable?:boolean) => void },
ready?:() => void,
connect?:(client:MuRemoteClient) => void,
raw?:(client:MuRemoteClient, data:Uint8Array|string, unreliable:boolean) => void,
disconnect?:(client:MuRemoteClient) => void,
close?:() => void,
}) : void
```
Defines event handlers specific to the protocol. The optional handlers default to noop if not specified.
* `message` a table of message handlers
* `ready()` is called when the server is ready
* `connect()` is called when a client is connected
* `raw()` handles `raw` messages from clients
* `disconnect()` is called when a client is disconnected
* `close()` is called when the serve is being shut down
Caveats:
* You SHOULD configure the protocols before the server is started.
* `configure()` can be called only once for each protocol.
```ts
broadcastRaw(data:Uint8Array|string, unreliable?:boolean) : void
```
Broadcasts `data` as a `raw` message to all the clients. `data` will be sent as is.
---
### `MuRemoteClient`
An abstraction of a connected client. Available as an argument to the event handlers representing the client in question.
#### props
```ts
sessionId:string
```
The session id the client is constructed with.
```ts
message:{ [type:string]:(data:any, unreliable?:boolean) => void }
```
A table of message dispatchers, calling one of which will send a message to the corresponding client. For example, calling `client.message.sync(state, true)` will send `state` as a `sync` message to the client through unreliable delivery.
#### methods
```ts
sendRaw(data:Uint8Array|string, unreliable?:boolean) : void
```
Sends `data` as a `raw` message to the corresponding client.
```ts
close() : void
```
Closes the connection.
---
### `MuClient`
```ts
import { MuClient } from 'mudb/client'
new MuClient(
socket:MuSocket,
logger?:MuLogger,
skipProtocolValidation:boolean=false,
)
```
* `socket` a [`MuSocket`](socket/README#musocket) that handles communications with the server
* `skipProtocolValidation` whether to skip validating protocol consistency
#### methods
```ts
protocol(schema:{
server:{ [message:string]:MuSchema<any> },
client:{ [message:string]:MuSchema<any> },
name?:string,
}) : MuClientProtocol
```
Registers a protocol.
Caveats:
* You CANNOT register protocols after the client is started.
* You MUST register the same set of protocols in the exact same order as the server code does, otherwise the protocol validation will fail and the connection will be killed.
```ts
start(spec?:{
ready?:(error?:string) => void,
close?:(error?:string) => void,
}) : void
```
Starts the client and connects to the server.
* `ready()` is called when the connection is ready
* `close()` is called when the socket is being closed
```ts
destroy() : void
```
Closes the connection to server.
---
### `MuClientProtocol`
Created by calling `client.protocol()`, carries a bunch of client-side message dispatchers and event handlers exclusive to the protocol.
#### props
```ts
client:MuClient
```
```ts
server:MuRemoteServer
```
#### methods
```ts
configure(spec:{
message:{ [type:string]:(data:any, unreliable:boolean) => void },
ready: () => void,
raw?:(data:Uint8Array|string, unreliable:boolean) => void,
close: () => void,
}) : void
```
Defines event handlers specific to the protocol. The optional handlers default to noop if not specified.
* `message` a table of message handlers
* `ready()` is called when the connection is established
* `raw()` handles `raw` messages from the server
* `close()` is called when the connection is being closed
Caveats:
* You SHOULD configure the protocols before the client is started.
* `configure()` can be called only once for each protocol.
---
### `MuRemoteServer`
Client-side abstraction of the server.
#### props
```ts
message:{ [type:string]:(data:any, unreliable?:boolean) => void }
```
A table of message dispatchers, calling one of which will send a message to the server. For example, calling `server.message.sync(state, true)` will send `state` as a `sync` message to the server through unreliable delivery.
#### methods
```ts
sendRaw(data:Uint8Array|string, unreliable?:boolean) : void
```
Sends `data` as a `raw` message to the server.