xP: use the binary protocol for incoming events
And batch event messages together as much as possible. JSON has proven itself to be really slow (for example, encoding/json.Marshaler is a slow interface), and browsers have significant overhead per WS message. Commands are still sent as JSON, sending them in binary would be a laborious rewrite without measurable merits. The xP server now only prints debug output when requested, because that was another source of major slowdowns.
This commit is contained in:
202
xP/public/xP.js
202
xP/public/xP.js
@@ -1,6 +1,6 @@
|
||||
// Copyright (c) 2022, Přemysl Eric Janouch <p@janouch.name>
|
||||
// SPDX-License-Identifier: 0BSD
|
||||
'use strict'
|
||||
import * as Relay from './proto.js'
|
||||
|
||||
// ---- RPC --------------------------------------------------------------------
|
||||
|
||||
@@ -31,6 +31,7 @@ class RelayRpc extends EventTarget {
|
||||
}
|
||||
|
||||
_initialize() {
|
||||
this.ws.binaryType = 'arraybuffer'
|
||||
this.ws.onopen = undefined
|
||||
this.ws.onmessage = event => {
|
||||
this._process(event.data)
|
||||
@@ -56,33 +57,30 @@ class RelayRpc extends EventTarget {
|
||||
}
|
||||
|
||||
_process(data) {
|
||||
if (typeof data !== 'string')
|
||||
throw "Binary messages not supported"
|
||||
if (typeof data === 'string')
|
||||
throw "JSON messages not supported"
|
||||
|
||||
let message = JSON.parse(data)
|
||||
if (typeof message !== 'object')
|
||||
throw "Invalid message"
|
||||
const r = new Relay.Reader(data)
|
||||
while (!r.empty)
|
||||
this._processOne(Relay.EventMessage.deserialize(r))
|
||||
}
|
||||
|
||||
_processOne(message) {
|
||||
let e = message.data
|
||||
if (typeof e !== 'object')
|
||||
throw "Invalid message"
|
||||
|
||||
switch (e.event) {
|
||||
case 'Error':
|
||||
case Relay.Event.Error:
|
||||
if (this.promised[e.commandSeq] !== undefined)
|
||||
this.promised[e.commandSeq].reject(e.error)
|
||||
else
|
||||
console.error("Unawaited error")
|
||||
break
|
||||
case 'Response':
|
||||
case Relay.Event.Response:
|
||||
if (this.promised[e.commandSeq] !== undefined)
|
||||
this.promised[e.commandSeq].resolve(e.data)
|
||||
else
|
||||
console.error("Unawaited response")
|
||||
break
|
||||
default:
|
||||
if (typeof e.event !== 'string')
|
||||
throw "Invalid event tag"
|
||||
|
||||
e.eventSeq = message.eventSeq
|
||||
this.dispatchEvent(new CustomEvent('event', {detail: e}))
|
||||
return
|
||||
@@ -115,13 +113,6 @@ class RelayRpc extends EventTarget {
|
||||
this.promised[seq] = {resolve, reject}
|
||||
})
|
||||
}
|
||||
|
||||
base64decode(str) {
|
||||
const text = atob(str), bytes = new Uint8Array(text.length)
|
||||
for (let i = 0; i < text.length; i++)
|
||||
bytes[i] = text.charCodeAt(i)
|
||||
return new TextDecoder().decode(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Utilities --------------------------------------------------------------
|
||||
@@ -183,7 +174,7 @@ function updateIcon(highlighted) {
|
||||
// ---- Event processing -------------------------------------------------------
|
||||
|
||||
let rpc = new RelayRpc(proxy)
|
||||
let rpcEventHandlers = {}
|
||||
let rpcEventHandlers = new Map()
|
||||
|
||||
let buffers = new Map()
|
||||
let bufferLast = undefined
|
||||
@@ -221,7 +212,7 @@ function bufferToggleLog() {
|
||||
if (bufferCurrent !== name)
|
||||
return
|
||||
|
||||
bufferLog = rpc.base64decode(resp.log)
|
||||
bufferLog = utf8Decode(resp.log)
|
||||
m.redraw()
|
||||
})
|
||||
}
|
||||
@@ -236,7 +227,7 @@ rpc.connect().then(result => {
|
||||
|
||||
servers.clear()
|
||||
|
||||
rpc.send({command: 'Hello', version: 1})
|
||||
rpc.send({command: 'Hello', version: Relay.version})
|
||||
connecting = false
|
||||
m.redraw()
|
||||
}).catch(error => {
|
||||
@@ -249,10 +240,11 @@ rpc.addEventListener('close', event => {
|
||||
})
|
||||
|
||||
rpc.addEventListener('event', event => {
|
||||
const handler = rpcEventHandlers[event.detail.event]
|
||||
const handler = rpcEventHandlers.get(event.detail.event)
|
||||
if (handler !== undefined) {
|
||||
handler(event.detail)
|
||||
if (bufferCurrent !== undefined || event.detail.event !== 'BufferLine')
|
||||
if (bufferCurrent !== undefined ||
|
||||
event.detail.event !== Relay.Event.BufferLine)
|
||||
m.redraw()
|
||||
}
|
||||
})
|
||||
@@ -263,7 +255,7 @@ rpcEventHandlers['Ping'] = e => {
|
||||
|
||||
// ~~~ Buffer events ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
rpcEventHandlers['BufferUpdate'] = e => {
|
||||
rpcEventHandlers.set(Relay.Event.BufferUpdate, e => {
|
||||
let b = buffers.get(e.bufferName)
|
||||
if (b === undefined) {
|
||||
buffers.set(e.bufferName, (b = {
|
||||
@@ -277,9 +269,9 @@ rpcEventHandlers['BufferUpdate'] = e => {
|
||||
b.hideUnimportant = e.hideUnimportant
|
||||
b.kind = e.context.kind
|
||||
b.server = servers.get(e.context.serverName)
|
||||
}
|
||||
})
|
||||
|
||||
rpcEventHandlers['BufferStats'] = e => {
|
||||
rpcEventHandlers.set(Relay.Event.BufferStats, e => {
|
||||
let b = buffers.get(e.bufferName)
|
||||
if (b === undefined)
|
||||
return
|
||||
@@ -287,20 +279,20 @@ rpcEventHandlers['BufferStats'] = e => {
|
||||
b.newMessages = e.newMessages,
|
||||
b.newUnimportantMessages = e.newUnimportantMessages
|
||||
b.highlighted = e.highlighted
|
||||
}
|
||||
})
|
||||
|
||||
rpcEventHandlers['BufferRename'] = e => {
|
||||
rpcEventHandlers.set(Relay.Event.BufferRename, e => {
|
||||
buffers.set(e.new, buffers.get(e.bufferName))
|
||||
buffers.delete(e.bufferName)
|
||||
}
|
||||
})
|
||||
|
||||
rpcEventHandlers['BufferRemove'] = e => {
|
||||
rpcEventHandlers.set(Relay.Event.BufferRemove, e => {
|
||||
buffers.delete(e.bufferName)
|
||||
if (e.bufferName === bufferLast)
|
||||
bufferLast = undefined
|
||||
}
|
||||
})
|
||||
|
||||
rpcEventHandlers['BufferActivate'] = e => {
|
||||
rpcEventHandlers.set(Relay.Event.BufferActivate, e => {
|
||||
let old = buffers.get(bufferCurrent)
|
||||
if (old !== undefined)
|
||||
bufferResetStats(old)
|
||||
@@ -333,9 +325,9 @@ rpcEventHandlers['BufferActivate'] = e => {
|
||||
textarea.value = b.input
|
||||
textarea.setSelectionRange(b.inputStart, b.inputEnd, b.inputDirection)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
rpcEventHandlers['BufferLine'] = e => {
|
||||
rpcEventHandlers.set(Relay.Event.BufferLine, e => {
|
||||
let b = buffers.get(e.bufferName), line = {...e}
|
||||
delete line.event
|
||||
delete line.eventSeq
|
||||
@@ -372,37 +364,37 @@ rpcEventHandlers['BufferLine'] = e => {
|
||||
}
|
||||
}
|
||||
|
||||
if (line.isHighlight ||
|
||||
(!visible && b.kind === 'PrivateMessage' && !line.isUnimportant)) {
|
||||
if (line.isHighlight || (!visible && !line.isUnimportant &&
|
||||
b.kind === Relay.BufferKind.PrivateMessage)) {
|
||||
beep()
|
||||
if (!visible)
|
||||
b.highlighted = true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
rpcEventHandlers['BufferClear'] = e => {
|
||||
rpcEventHandlers.set(Relay.Event.BufferClear, e => {
|
||||
let b = buffers.get(e.bufferName)
|
||||
if (b !== undefined)
|
||||
b.lines.length = 0
|
||||
}
|
||||
})
|
||||
|
||||
// ~~~ Server events ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
rpcEventHandlers['ServerUpdate'] = e => {
|
||||
rpcEventHandlers.set(Relay.Event.ServerUpdate, e => {
|
||||
let s = servers.get(e.serverName)
|
||||
if (s === undefined)
|
||||
servers.set(e.serverName, (s = {}))
|
||||
s.state = e.state
|
||||
}
|
||||
})
|
||||
|
||||
rpcEventHandlers['ServerRename'] = e => {
|
||||
rpcEventHandlers.set(Relay.Event.ServerRename, e => {
|
||||
servers.set(e.new, servers.get(e.serverName))
|
||||
servers.delete(e.serverName)
|
||||
}
|
||||
})
|
||||
|
||||
rpcEventHandlers['ServerRemove'] = e => {
|
||||
rpcEventHandlers.set(Relay.Event.ServerRemove, e => {
|
||||
servers.delete(e.serverName)
|
||||
}
|
||||
})
|
||||
|
||||
// --- Colours -----------------------------------------------------------------
|
||||
|
||||
@@ -499,18 +491,19 @@ let Content = {
|
||||
return a
|
||||
},
|
||||
|
||||
makeMark: line => {
|
||||
switch (line.rendition) {
|
||||
case Relay.Rendition.Indent: return m('span.mark', {}, '')
|
||||
case Relay.Rendition.Status: return m('span.mark', {}, '–')
|
||||
case Relay.Rendition.Error: return m('span.mark.error', {}, '⚠')
|
||||
case Relay.Rendition.Join: return m('span.mark.join', {}, '→')
|
||||
case Relay.Rendition.Part: return m('span.mark.part', {}, '←')
|
||||
case Relay.Rendition.Action: return m('span.mark.action', {}, '✶')
|
||||
}
|
||||
},
|
||||
|
||||
view: vnode => {
|
||||
let line = vnode.children[0]
|
||||
let mark = undefined
|
||||
switch (line.rendition) {
|
||||
case 'Indent': mark = m('span.mark', {}, ''); break
|
||||
case 'Status': mark = m('span.mark', {}, '–'); break
|
||||
case 'Error': mark = m('span.mark.error', {}, '⚠'); break
|
||||
case 'Join': mark = m('span.mark.join', {}, '→'); break
|
||||
case 'Part': mark = m('span.mark.part', {}, '←'); break
|
||||
case 'Action': mark = m('span.mark.action', {}, '✶'); break
|
||||
}
|
||||
|
||||
let classes = new Set()
|
||||
let flip = c => {
|
||||
if (classes.has(c))
|
||||
@@ -518,45 +511,49 @@ let Content = {
|
||||
else
|
||||
classes.add(c)
|
||||
}
|
||||
|
||||
let fg = -1, bg = -1, inverse = false
|
||||
return m('.content', vnode.attrs, [mark, line.items.flatMap(item => {
|
||||
switch (item.kind) {
|
||||
case 'Text':
|
||||
return Content.linkify(item.text, {
|
||||
class: Array.from(classes.keys()).join(' '),
|
||||
style: Content.applyColor(fg, bg, inverse),
|
||||
})
|
||||
case 'Reset':
|
||||
classes.clear()
|
||||
fg = bg = -1
|
||||
inverse = false
|
||||
break
|
||||
case 'FgColor':
|
||||
fg = item.color
|
||||
break
|
||||
case 'BgColor':
|
||||
bg = item.color
|
||||
break
|
||||
case 'FlipInverse':
|
||||
inverse = !inverse
|
||||
break
|
||||
case 'FlipBold':
|
||||
flip('b')
|
||||
break
|
||||
case 'FlipItalic':
|
||||
flip('i')
|
||||
break
|
||||
case 'FlipUnderline':
|
||||
flip('u')
|
||||
break
|
||||
case 'FlipCrossedOut':
|
||||
flip('s')
|
||||
break
|
||||
case 'FlipMonospace':
|
||||
flip('m')
|
||||
break
|
||||
}
|
||||
})])
|
||||
return m('.content', vnode.attrs, [
|
||||
Content.makeMark(line),
|
||||
line.items.flatMap(item => {
|
||||
switch (item.kind) {
|
||||
case Relay.Item.Text:
|
||||
return Content.linkify(item.text, {
|
||||
class: Array.from(classes.keys()).join(' '),
|
||||
style: Content.applyColor(fg, bg, inverse),
|
||||
})
|
||||
case Relay.Item.Reset:
|
||||
classes.clear()
|
||||
fg = bg = -1
|
||||
inverse = false
|
||||
break
|
||||
case Relay.Item.FgColor:
|
||||
fg = item.color
|
||||
break
|
||||
case Relay.Item.BgColor:
|
||||
bg = item.color
|
||||
break
|
||||
case Relay.Item.FlipInverse:
|
||||
inverse = !inverse
|
||||
break
|
||||
case Relay.Item.FlipBold:
|
||||
flip('b')
|
||||
break
|
||||
case Relay.Item.FlipItalic:
|
||||
flip('i')
|
||||
break
|
||||
case Relay.Item.FlipUnderline:
|
||||
flip('u')
|
||||
break
|
||||
case Relay.Item.FlipCrossedOut:
|
||||
flip('s')
|
||||
break
|
||||
case Relay.Item.FlipMonospace:
|
||||
flip('m')
|
||||
break
|
||||
}
|
||||
}),
|
||||
])
|
||||
},
|
||||
}
|
||||
|
||||
@@ -669,8 +666,17 @@ let Status = {
|
||||
let status = `${bufferCurrent}`
|
||||
if (b.hideUnimportant)
|
||||
status += `<H>`
|
||||
if (b.server !== undefined)
|
||||
status += ` (${b.server.state})`
|
||||
|
||||
// This should be handled differently, so don't mind the lookup.
|
||||
if (b.server !== undefined) {
|
||||
let state = b.server.state
|
||||
for (const s in Relay.ServerState)
|
||||
if (Relay.ServerState[s] == b.server.state) {
|
||||
state = s
|
||||
break
|
||||
}
|
||||
status += ` (${state})`
|
||||
}
|
||||
return m('.status', {}, status)
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user