staff_data/packages/common/src/collaboration/y-awareness.ts

545 lines
15 KiB
TypeScript
Raw Normal View History

2024-12-30 08:26:40 +08:00
/**
* Yjs的分布式感知协议(Awareness Protocol)
* (线)
*
* @author
* @date 2023
*/
import * as encoding from 'lib0/encoding';
import * as decoding from 'lib0/decoding';
import * as time from 'lib0/time';
import { Observable } from 'lib0/observable';
import * as f from 'lib0/function';
import * as Y from 'yjs'; // eslint-disable-line
import { YWsProvider } from './y-socket';
/**
* ,
* ,线
*/
export const outdatedTimeout: number = 30000;
/**
* MetaClientState
*
*
* @interface
*/
export interface MetaClientState {
clock: number; // 单调递增的时钟值,用于状态版本控制
lastUpdated: number; // 最后一次更新的Unix时间戳
}
/**
* StateMap类型定义了客户端ID到状态记录的映射关系
* key为clientID,value为该客户端的状态对象
*/
type StateMap = Map<number, Record<string, any>>;
/**
* Awareness类 -
* Observable以提供事件机制
*/
export class Awareness extends Observable<string> {
/** Yjs文档实例,用于协同编辑 */
doc: Y.Doc;
/** 当前客户端的唯一标识 */
clientID: number;
/** 存储所有客户端的状态映射表 Map<clientID, state> */
states: StateMap;
/** 存储所有客户端的元数据映射表 Map<clientID, MetaClientState> */
meta: Map<number, MetaClientState>;
/** 定时检查过期状态的定时器句柄 */
private _checkInterval: ReturnType<typeof setInterval>;
/**
*
* @param doc Yjs文档实例
*/
constructor(doc: Y.Doc) {
super();
/**
*
*/
this.doc = doc;
this.clientID = doc.clientID;
this.states = new Map();
this.meta = new Map();
/**
* ,
* 1/10
*/
this._checkInterval = setInterval(() => {
const now = time.getUnixTime();
/**
* (),
*
*/
if (this.getLocalState() !== null && (outdatedTimeout / 2 <= now - (this.meta.get(this.clientID)?.lastUpdated || 0))) {
this.setLocalState(this.getLocalState());
}
/**
*
* 过期条件:非本地客户端 && &&
*/
const remove: number[] = [];
this.meta.forEach((meta, clientid) => {
if (clientid !== this.clientID && outdatedTimeout <= now - meta.lastUpdated && this.states.has(clientid)) {
remove.push(clientid);
}
});
if (remove.length > 0) {
removeAwarenessStates(this, remove, 'timeout');
}
}, Math.floor(outdatedTimeout / 10));
/**
* ,
*/
doc.on('destroy', () => {
this.destroy();
});
/**
*
*/
this.setLocalState({});
}
/**
* ,
*/
override destroy() {
this.emit('destroy', [this]);
this.setLocalState(null);
super.destroy();
clearInterval(this._checkInterval);
}
/**
*
* @returns null
*/
getLocalState(): Record<string, any> | null {
return this.states.get(this.clientID) || null;
}
/**
*
* @param state null()
*/
setLocalState(state: Record<string, any> | null) {
const clientID = this.clientID;
const currLocalMeta = this.meta.get(clientID);
const clock = currLocalMeta === undefined ? 0 : currLocalMeta.clock + 1;
const prevState = this.states.get(clientID);
/**
*
*/
if (state === null) {
this.states.delete(clientID);
} else {
this.states.set(clientID, state);
}
/**
* ()
*/
this.meta.set(clientID, {
clock,
lastUpdated: time.getUnixTime()
});
/**
* :
* - added: 新增的状态
* - updated: 更新的状态
* - filteredUpdated: 实际发生变化的更新状态
* - removed: 移除的状态
*/
const added: number[] = [];
const updated: number[] = [];
const filteredUpdated: number[] = [];
const removed: number[] = [];
if (state === null) {
removed.push(clientID);
} else if (prevState == null) {
if (state != null) {
added.push(clientID);
}
} else {
updated.push(clientID);
if (!f.equalityDeep(prevState, state)) {
filteredUpdated.push(clientID);
}
}
/**
* :
* - change: 仅当状态实际发生变化时触发
* - update: 所有更新操作都会触发
*/
if (added.length > 0 || filteredUpdated.length > 0 || removed.length > 0) {
this.emit('change', [{ added, updated: filteredUpdated, removed }, 'local']);
}
this.emit('update', [{ added, updated, removed }, 'local']);
}
/**
*
* @param field
* @param value
*/
setLocalStateField(field: string, value: any) {
const state = this.getLocalState();
if (state !== null) {
this.setLocalState({
...state,
[field]: value
});
}
}
/**
*
* @returns StateMap
*/
getStates(): StateMap {
return this.states;
}
}
/**
* Awareness中移除指定客户端的状态
* @param awareness {Awareness} - Awareness实例
* @param clients {number[]} - ID数组
* @param origin {any} -
*/
export const removeAwarenessStates = (awareness: Awareness, clients: number[], origin: any) => {
/**
* ID
*
*/
const removed: number[] = [];
/**
*
*/
for (let i = 0; i < clients.length; i++) {
const clientID = clients[i];
/**
*
*
*/
if (awareness.states.has(clientID)) {
/**
* states中删除该客户端状态
*/
awareness.states.delete(clientID);
/**
*
* :
* 1. meta信息
* 2. clock值(+1)
*/
if (clientID === awareness.clientID) {
const curMeta = awareness.meta.get(clientID) as MetaClientState;
awareness.meta.set(clientID, {
clock: curMeta.clock + 1,
lastUpdated: time.getUnixTime()
});
}
/**
* ID加入removed数组
*/
removed.push(clientID);
}
}
/**
* ,
* :
* 1. change -
* 2. update -
* :
* - added: 新增的状态()
* - updated: 更新的状态()
* - removed: 移除的状态
* - origin: 变更来源
*/
if (removed.length > 0) {
awareness.emit('change', [{ added: [], updated: [], removed }, origin]);
awareness.emit('update', [{ added: [], updated: [], removed }, origin]);
}
}
/**
* Awareness状态编码为二进制更新数据
* @param awareness {Awareness} - Awareness实例,
* @param clients {number[]} - ID数组
* @param states {StateMap} - ,使awareness中的states
* @returns {Uint8Array} -
*/
export const encodeAwarenessUpdate = (awareness: Awareness, clients: number[], states: StateMap = awareness.states): Uint8Array => {
/**
*
*
*/
const len = clients.length;
/**
*
*
*/
const encoder = encoding.createEncoder();
/**
*
* 使VarUint变长编码,
*/
encoding.writeVarUint(encoder, len);
/**
* ,
*/
for (let i = 0; i < len; i++) {
/**
* ID
*/
const clientID = clients[i];
/**
* states中获取该客户端的状态
* 使null
*/
const state = states.get(clientID) || null;
/**
* awareness.meta中获取该客户端的时钟值
* MetaClientState类型包含clock字段表示状态版本
*/
const clock = (awareness.meta.get(clientID) as MetaClientState).clock;
/**
* :
* 1. clientID
* 2. clock版本号
* 3. JSON字符串写入
*/
encoding.writeVarUint(encoder, clientID);
encoding.writeVarUint(encoder, clock);
encoding.writeVarString(encoder, JSON.stringify(state));
}
/**
* Uint8Array返回
*
*/
return encoding.toUint8Array(encoder);
}
/**
* Awareness更新数据的工具函数
* @param update {Uint8Array} - awareness更新二进制数据
* @param modify {Function} -
* @returns {Uint8Array} -
*/
export const modifyAwarenessUpdate = (update: Uint8Array, modify: (state: any) => any): Uint8Array => {
/**
* ,update数据
* decoding模块提供了二进制数据的解码能力
*/
const decoder = decoding.createDecoder(update);
/**
* ,
* encoding模块提供了数据编码为二进制的能力
*/
const encoder = encoding.createEncoder();
/**
* update中包含的awareness状态数量
* 使VarUint变长编码,
*/
const len = decoding.readVarUint(decoder);
/**
*
*/
encoding.writeVarUint(encoder, len);
/**
* awareness状态
*/
for (let i = 0; i < len; i++) {
/**
* :
* - clientID: 客户端唯一标识
* - clock: 状态版本时钟
* - state: JSON格式的状态数据
*/
const clientID = decoding.readVarUint(decoder);
const clock = decoding.readVarUint(decoder);
const state = JSON.parse(decoding.readVarString(decoder));
/**
* 使modify回调函数处理状态
* state进行任意修改
*/
const modifiedState = modify(state);
/**
* :
* 1. clientID
* 2. clock
* 3. JSON字符串写入
*/
encoding.writeVarUint(encoder, clientID);
encoding.writeVarUint(encoder, clock);
encoding.writeVarString(encoder, JSON.stringify(modifiedState));
}
/**
* Uint8Array返回
*
*/
return encoding.toUint8Array(encoder);
}
/**
* Awareness状态更新的函数
* @param awareness Awareness实例,
* @param update
* @param origin
*/
export const applyAwarenessUpdate = (awareness: Awareness, update: Uint8Array, origin: any) => {
/**
* ,update数据
* Uint8Array是固定长度的8位无符号整数数组
*/
const decoder = decoding.createDecoder(update);
/**
* Unix时间戳,
*/
const timestamp = time.getUnixTime();
/**
* :
* added - ID
* updated - ID
* filteredUpdated - ID
* removed - ID
*/
const added: number[] = [];
const updated: number[] = [];
const filteredUpdated: number[] = [];
const removed: number[] = [];
/**
*
* 使(VarUint)
*/
const len = decoding.readVarUint(decoder);
/**
*
*/
for (let i = 0; i < len; i++) {
/**
* ID
* 使JSON字符串传输,
*/
const clientID = decoding.readVarUint(decoder);
let clock = decoding.readVarUint(decoder);
const state = JSON.parse(decoding.readVarString(decoder));
/**
*
* meta包含clock()lastUpdated()
*/
const clientMeta = awareness.meta.get(clientID);
const prevState = awareness.states.get(clientID);
const currClock = clientMeta === undefined ? 0 : clientMeta.clock;
/**
*
* 使
*/
if (currClock < clock || (currClock === clock && state === null && awareness.states.has(clientID))) {
if (state === null) {
/**
*
* 本地状态特殊处理:增加时钟值而不删除状态
*/
if (clientID === awareness.clientID && awareness.getLocalState() != null) {
clock++;
} else {
awareness.states.delete(clientID);
}
} else {
/**
*
* 使Map数据结构存储状态
*/
awareness.states.set(clientID, state);
}
/**
*
*
*/
awareness.meta.set(clientID, {
clock,
lastUpdated: timestamp
});
/**
* clientID添加到相应的跟踪数组
* 使
*/
if (clientMeta === undefined && state !== null) {
added.push(clientID);
} else if (clientMeta !== undefined && state === null) {
removed.push(clientID);
} else if (state !== null) {
if (!f.equalityDeep(state, prevState)) {
filteredUpdated.push(clientID);
}
updated.push(clientID);
}
}
}
if (added.length > 0 || filteredUpdated.length > 0 || removed.length > 0) {
awareness.emit('change', [{ added, updated: filteredUpdated, removed }, origin]);
}
if (!(origin instanceof YWsProvider))
awareness.emit('update', [{ added, updated, removed }, origin]);
}