UNPKG

mudb

Version:

Real-time database for multiplayer games

209 lines (192 loc) 6.19 kB
import { MuASCII } from '../schema/ascii'; import { MuSortedArray } from '../schema/sorted-array'; export const IdSchema = new MuASCII(); export type Id = typeof IdSchema['identity']; export const IdSetSchema = new MuSortedArray(IdSchema, Infinity, compareId); export type IdSet = typeof IdSetSchema['identity']; export const ID_MAX = String.fromCharCode(128); export const ID_MIN = ''; const BASE_COUNT = 10; function log2 (v_:number) { let r = 0; let shift = 0; let v = v_; r = (v > 0xFFFF) ? (1 << 4) : 0; v >>>= r; shift = (v > 0xFF ) ? (1 << 3) : 0; v >>>= shift; r |= shift; shift = (v > 0xF ) ? (1 << 2) : 0; v >>>= shift; r |= shift; shift = (v > 0x3 ) ? (1 << 1) : 0; v >>>= shift; r |= shift; return r | (v >> 1); } function getStep (range:number, count:number) { const step = Math.floor(range / count); for (let i = 28; i >= 0; --i) { const nstep = (step >> i) << i; if (nstep) { return nstep; } } return step; } export function initialIds (count:number) : Id[] { const step = getStep(1 << 27, count); const result:Id[] = []; result.length = 0; for (let i = 0, id = step; i < count; ++i, id += step) { const x0 = id >> 21; const x1 = (id >> 14) & 0x7f; const x2 = (id >> 7) & 0x7f; const x3 = id & 0x7f; if (x3) { result.push( String.fromCharCode(x0) + String.fromCharCode(x1) + String.fromCharCode(x2) + String.fromCharCode(x3)); } else if (x2) { result.push( String.fromCharCode(x0) + String.fromCharCode(x1) + String.fromCharCode(x2)); } else if (x1) { result.push( String.fromCharCode(x0) + String.fromCharCode(x1)); } else { result.push(String.fromCharCode(x0)); } } return result; } function prefixLengthFromIndex (a:string, b:string, start:number) { const n = Math.min(a.length, b.length); for (let i = start; i < n; ++i) { if (a.charCodeAt(i) !== b.charCodeAt(i)) { return i; } } return n; } function computePrefix (a:string, b:string) { const n = Math.min(a.length, b.length); for (let i = 0; i < n; ++i) { if (a.charCodeAt(i) !== b.charCodeAt(i)) { return a.slice(0, i); } } return a.slice(0, n); } function prefixCode (a:Id, index:number) { const n = a.length; if (n <= index) { return 0; } else if (n <= index + 1) { return a.charCodeAt(index) << 21; } else if (n <= index + 2) { return (a.charCodeAt(index) << 21) + (a.charCodeAt(index + 1) << 14); } else if (n <= index + 3) { return (a.charCodeAt(index) << 21) + (a.charCodeAt(index + 1) << 14) + (a.charCodeAt(index + 2) << 7); } else { return (a.charCodeAt(index) << 21) + (a.charCodeAt(index + 1) << 14) + (a.charCodeAt(index + 2) << 7) + a.charCodeAt(index + 3); } } export function allocIds (begin:Id, end:Id, count:number) : Id[] { const prefix = computePrefix(begin, end); const lo = prefixCode(begin, prefix.length); const hi = prefixCode(end, prefix.length); const step = getStep(hi - lo, 2 * count); const bits = log2(BASE_COUNT + count); const offset = (Math.floor(1 + Math.random() * (1 << bits)) * getStep(hi - lo, 2 << bits)) >>> 0; const result:Id[] = []; for (let i = 0, id = lo + offset; i < count; ++i, id += step) { const x0 = id >> 21; const x1 = (id >> 14) & 0x7f; const x2 = (id >> 7) & 0x7f; const x3 = id & 0x7f; if (x3) { result.push(prefix + String.fromCharCode(x0) + String.fromCharCode(x1) + String.fromCharCode(x2) + String.fromCharCode(x3)); } else if (x2) { result.push(prefix + String.fromCharCode(x0) + String.fromCharCode(x1) + String.fromCharCode(x2)); } else if (x1) { result.push(prefix + String.fromCharCode(x0) + String.fromCharCode(x1)); } else { result.push(prefix + String.fromCharCode(x0)); } } return result; } function compareIdFromIndex (a:Id, b:Id, start:number) : number { const an = a.length; const bn = b.length; const n = Math.min(a.length, b.length); for (let i = start; i < n; ++i) { const d = a.charCodeAt(i) - b.charCodeAt(i); if (d) { return d; } } return an - bn; } export function compareId (a:Id, b:Id) : number { return compareIdFromIndex(a, b, 0); } export function compareTaggedId<T extends { id:Id }> (a:T, b:T) { return compareIdFromIndex(a.id, b.id, 0); } function interpolate (list:Id[], prefix:number, l:number, h:number, id:Id) { const a = prefixCode(list[l], prefix); const b = prefixCode(list[h], prefix); const x = prefixCode(id, prefix); const t = (x - a) / (b - a); const r = Math.max(l, Math.min(h, Math.round((1 - t) * l + t * h))) | 0; return r; } export function searchId (list:Id[], id:Id) : number { if (list.length === 0) { return 0; } let l = 0; let lprefix = prefixLengthFromIndex(list[l], id, 0); let h = list.length - 1; let hprefix = prefixLengthFromIndex(list[h], id, 0); let i = list.length; while (l < h) { let p = Math.min(lprefix, hprefix); const m = interpolate(list, p, l, h, id); const x = list[m]; p = prefixLengthFromIndex(id, x, p); if (p === x.length && p === id.length) { return m; } const d = compareIdFromIndex(x, id, p); if (d <= 0) { l = m + 1; lprefix = p; } else { i = m; h = m - 1; hprefix = p; } } if (l === h) { if (compareId(list[l], id) < 0) { return l + 1; } else { return l; } } return i; }