Skip to content

Commit

Permalink
Merge pull request #27 from Myrannas/master
Browse files Browse the repository at this point in the history
Allow a consumer to switch between JSON and binary encoding for awareness
  • Loading branch information
dmonad authored Mar 31, 2023
2 parents 4459c3a + 168bbaf commit 960d4c2
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 11 deletions.
65 changes: 58 additions & 7 deletions awareness.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,54 @@ export class Awareness extends Observable {
}
}

/**
* A state encoder that serializes state into JSON.
*
* This is the default strategy used in this package.
*/
export class DefaultAwarenessStateEncoder {
/**
* Encode an awareness state entry
* @param {encoding.Encoder} encoder
* @param {any} update
*/
encodeState (encoder, update) {
encoding.writeVarString(encoder, JSON.stringify(update))
}

/**
* Decode an awareness state entry
* @param {decoding.Decoder} decoder
* @returns {string}
*/
decodeState (decoder) {
return JSON.parse(decoding.readVarString(decoder))
}
}

/**
* A state encoder that serializes state into a binary format.
*/
export class BinaryAwarenessStateEncoder {
/**
* Encode an awareness state entry
* @param {encoding.Encoder} encoder
* @param {any} update
*/
encodeState (encoder, update) {
encoding.writeAny(encoder, update)
}

/**
* Decode an awareness state entry
* @param {decoding.Decoder} decoder
* @returns {any}
*/
decodeState (decoder) {
return decoding.readAny(decoder)
}
}

/**
* Mark (remote) clients as inactive and remove them from the list of active peers.
* This change will be propagated to remote clients.
Expand Down Expand Up @@ -189,9 +237,10 @@ export const removeAwarenessStates = (awareness, clients, origin) => {
/**
* @param {Awareness} awareness
* @param {Array<number>} clients
* @param {DefaultAwarenessStateEncoder|BinaryAwarenessStateEncoder} stateEncoder The encoder to use for encoding and decoding each state entry
* @return {Uint8Array}
*/
export const encodeAwarenessUpdate = (awareness, clients, states = awareness.states) => {
export const encodeAwarenessUpdate = (awareness, clients, states = awareness.states, stateEncoder = new DefaultAwarenessStateEncoder()) => {
const len = clients.length
const encoder = encoding.createEncoder()
encoding.writeVarUint(encoder, len)
Expand All @@ -201,7 +250,7 @@ export const encodeAwarenessUpdate = (awareness, clients, states = awareness.sta
const clock = /** @type {MetaClientState} */ (awareness.meta.get(clientID)).clock
encoding.writeVarUint(encoder, clientID)
encoding.writeVarUint(encoder, clock)
encoding.writeVarString(encoder, JSON.stringify(state))
stateEncoder.encodeState(encoder, state)
}
return encoding.toUint8Array(encoder)
}
Expand All @@ -214,21 +263,22 @@ export const encodeAwarenessUpdate = (awareness, clients, states = awareness.sta
*
* @param {Uint8Array} update
* @param {function(any):any} modify
* @param {DefaultAwarenessStateEncoder|BinaryAwarenessStateEncoder} stateEncoder The encoder to use for encoding and decoding each state entry
* @return {Uint8Array}
*/
export const modifyAwarenessUpdate = (update, modify) => {
export const modifyAwarenessUpdate = (update, modify, stateEncoder = new DefaultAwarenessStateEncoder()) => {
const decoder = decoding.createDecoder(update)
const encoder = encoding.createEncoder()
const len = decoding.readVarUint(decoder)
encoding.writeVarUint(encoder, len)
for (let i = 0; i < len; i++) {
const clientID = decoding.readVarUint(decoder)
const clock = decoding.readVarUint(decoder)
const state = JSON.parse(decoding.readVarString(decoder))
const state = stateEncoder.decodeState(decoder)
const modifiedState = modify(state)
encoding.writeVarUint(encoder, clientID)
encoding.writeVarUint(encoder, clock)
encoding.writeVarString(encoder, JSON.stringify(modifiedState))
stateEncoder.encodeState(encoder, modifiedState)
}
return encoding.toUint8Array(encoder)
}
Expand All @@ -237,8 +287,9 @@ export const modifyAwarenessUpdate = (update, modify) => {
* @param {Awareness} awareness
* @param {Uint8Array} update
* @param {any} origin This will be added to the emitted change event
* @param {DefaultAwarenessStateEncoder|BinaryAwarenessStateEncoder} stateEncoder The encoder to use for encoding each state entry
*/
export const applyAwarenessUpdate = (awareness, update, origin) => {
export const applyAwarenessUpdate = (awareness, update, origin, stateEncoder = new DefaultAwarenessStateEncoder()) => {
const decoder = decoding.createDecoder(update)
const timestamp = time.getUnixTime()
const added = []
Expand All @@ -249,7 +300,7 @@ export const applyAwarenessUpdate = (awareness, update, origin) => {
for (let i = 0; i < len; i++) {
const clientID = decoding.readVarUint(decoder)
let clock = decoding.readVarUint(decoder)
const state = JSON.parse(decoding.readVarString(decoder))
const state = stateEncoder.decodeState(decoder)
const clientMeta = awareness.meta.get(clientID)
const prevState = awareness.states.get(clientID)
const currClock = clientMeta === undefined ? 0 : clientMeta.clock
Expand Down
13 changes: 9 additions & 4 deletions awareness.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@ import * as t from 'lib0/testing'
import * as awareness from './awareness'

/**
* @param {t.TestCase} tc
* @param {awareness.DefaultAwarenessStateEncoder|awareness.BinaryAwarenessStateEncoder} stateEncoder
* @return {function(t.TestCase): void}
*/
export const testAwareness = tc => {
const testAwarenessWithEncoding = (stateEncoder = new awareness.DefaultAwarenessStateEncoder()) => tc => {
const doc1 = new Y.Doc()
doc1.clientID = 0
const doc2 = new Y.Doc()
doc2.clientID = 1
const aw1 = new awareness.Awareness(doc1)
const aw2 = new awareness.Awareness(doc2)
aw1.on('update', /** @param {any} p */ ({ added, updated, removed }) => {
const enc = awareness.encodeAwarenessUpdate(aw1, added.concat(updated).concat(removed))
awareness.applyAwarenessUpdate(aw2, enc, 'custom')
const enc = awareness.encodeAwarenessUpdate(aw1, added.concat(updated).concat(removed), aw1.states, stateEncoder)
awareness.applyAwarenessUpdate(aw2, enc, 'custom', stateEncoder)
})
let lastChangeLocal = /** @type {any} */ (null)
aw1.on('change', /** @param {any} change */ change => {
Expand Down Expand Up @@ -51,3 +52,7 @@ export const testAwareness = tc => {
t.compare(aw1.getStates().get(0), undefined)
t.compare(lastChangeLocal, lastChange)
}

export const testAwareness = testAwarenessWithEncoding()
export const testAwarenessWithBinary = testAwarenessWithEncoding(new awareness.BinaryAwarenessStateEncoder())
export const testAwarenessWithDefault = testAwarenessWithEncoding(new awareness.DefaultAwarenessStateEncoder())

0 comments on commit 960d4c2

Please sign in to comment.