Files
esengine/packages/network-shared/src/serialization/MessageCompressor.ts

655 lines
18 KiB
TypeScript
Raw Normal View History

/**
2025-08-20 10:16:54 +08:00
*
*
*/
import { createLogger } from '@esengine/ecs-framework';
/**
2025-08-20 10:16:54 +08:00
*
*/
2025-08-20 10:16:54 +08:00
export interface ICompressionAlgorithm {
/** 算法名称 */
readonly name: string;
/** 算法版本 */
readonly version: string;
/** 是否支持异步压缩 */
readonly supportsAsync: boolean;
2025-08-20 10:16:54 +08:00
/**
*
*/
compress(data: ArrayBuffer): ArrayBuffer;
2025-08-20 10:16:54 +08:00
/**
*
*/
decompress(data: ArrayBuffer): ArrayBuffer;
2025-08-20 10:16:54 +08:00
/**
*
*/
compressAsync?(data: ArrayBuffer): Promise<ArrayBuffer>;
2025-08-20 10:16:54 +08:00
/**
*
*/
decompressAsync?(data: ArrayBuffer): Promise<ArrayBuffer>;
2025-08-20 10:16:54 +08:00
/**
*
*/
estimateCompressedSize?(data: ArrayBuffer): number;
}
/**
*
*/
export interface CompressionConfig {
2025-08-20 10:16:54 +08:00
/** 默认压缩算法名称 */
defaultAlgorithm: string;
/** 最小压缩阈值(字节) */
threshold: number;
/** 是否启用异步压缩 */
enableAsync: boolean;
/** 压缩级别提示 (0-9) */
level: number;
/** 分块大小 */
chunkSize: number;
/** 是否启用压缩统计 */
enableStats: boolean;
}
/**
*
*/
export interface CompressionResult {
2025-08-20 10:16:54 +08:00
/** 压缩后的数据 */
data: ArrayBuffer;
/** 原始大小 */
originalSize: number;
2025-08-20 10:16:54 +08:00
/** 压缩后大小 */
compressedSize: number;
2025-08-20 10:16:54 +08:00
/** 压缩比 */
compressionRatio: number;
2025-08-20 10:16:54 +08:00
/** 压缩耗时 */
compressionTime: number;
2025-08-20 10:16:54 +08:00
/** 使用的算法 */
algorithm: string;
/** 是否实际进行了压缩 */
wasCompressed: boolean;
}
/**
*
*/
export interface DecompressionResult {
/** 解压缩后的数据 */
data: ArrayBuffer;
/** 原始压缩大小 */
compressedSize: number;
/** 解压缩后大小 */
decompressedSize: number;
/** 解压缩耗时 */
decompressionTime: number;
/** 使用的算法 */
algorithm: string;
}
/**
*
*/
export interface CompressionStats {
2025-08-20 10:16:54 +08:00
/** 总压缩次数 */
totalCompressions: number;
/** 总解压缩次数 */
totalDecompressions: number;
/** 总原始字节数 */
totalOriginalBytes: number;
2025-08-20 10:16:54 +08:00
/** 总压缩字节数 */
totalCompressedBytes: number;
2025-08-20 10:16:54 +08:00
/** 平均压缩比 */
averageCompressionRatio: number;
2025-08-20 10:16:54 +08:00
/** 平均压缩时间 */
averageCompressionTime: number;
2025-08-20 10:16:54 +08:00
/** 平均解压缩时间 */
averageDecompressionTime: number;
2025-08-20 10:16:54 +08:00
/** 算法使用统计 */
algorithmUsage: Record<string, number>;
}
/**
*
*/
export class NoCompressionAlgorithm implements ICompressionAlgorithm {
readonly name = 'none';
readonly version = '1.0.0';
readonly supportsAsync = false;
2025-08-20 10:16:54 +08:00
compress(data: ArrayBuffer): ArrayBuffer {
return data.slice(0);
}
2025-08-20 10:16:54 +08:00
decompress(data: ArrayBuffer): ArrayBuffer {
return data.slice(0);
}
2025-08-20 10:16:54 +08:00
estimateCompressedSize(data: ArrayBuffer): number {
return data.byteLength;
}
}
/**
* LZ字符串压缩算法实现
*/
export class LZCompressionAlgorithm implements ICompressionAlgorithm {
readonly name = 'lz-string';
readonly version = '1.0.0';
readonly supportsAsync = false;
2025-08-20 10:16:54 +08:00
compress(data: ArrayBuffer): ArrayBuffer {
// 将ArrayBuffer转换为字符串
const decoder = new TextDecoder();
const input = decoder.decode(data);
2025-08-20 10:16:54 +08:00
if (!input) {
return data.slice(0);
}
2025-08-20 10:16:54 +08:00
// LZ压缩算法
const dictionary: { [key: string]: number } = {};
let dictSize = 256;
2025-08-20 10:16:54 +08:00
// 初始化字典
for (let i = 0; i < 256; i++) {
dictionary[String.fromCharCode(i)] = i;
}
2025-08-20 10:16:54 +08:00
let w = '';
const result: number[] = [];
2025-08-20 10:16:54 +08:00
for (let i = 0; i < input.length; i++) {
const c = input.charAt(i);
const wc = w + c;
2025-08-20 10:16:54 +08:00
if (dictionary[wc] !== undefined) {
w = wc;
} else {
result.push(dictionary[w]);
dictionary[wc] = dictSize++;
w = c;
2025-08-20 10:16:54 +08:00
// 防止字典过大
if (dictSize >= 0xFFFF) {
dictSize = 256;
// 重置字典
for (const key in dictionary) {
if (dictionary[key] >= 256) {
delete dictionary[key];
}
}
}
}
}
2025-08-20 10:16:54 +08:00
if (w) {
result.push(dictionary[w]);
}
2025-08-20 10:16:54 +08:00
// 将结果转换为ArrayBuffer
return this.numbersToArrayBuffer(result);
}
2025-08-20 10:16:54 +08:00
decompress(data: ArrayBuffer): ArrayBuffer {
if (data.byteLength === 0) {
return data.slice(0);
}
2025-08-20 10:16:54 +08:00
const numbers = this.arrayBufferToNumbers(data);
if (numbers.length === 0) {
return data.slice(0);
}
2025-08-20 10:16:54 +08:00
const dictionary: { [key: number]: string } = {};
let dictSize = 256;
2025-08-20 10:16:54 +08:00
// 初始化字典
for (let i = 0; i < 256; i++) {
dictionary[i] = String.fromCharCode(i);
}
2025-08-20 10:16:54 +08:00
let w = String.fromCharCode(numbers[0]);
const result = [w];
2025-08-20 10:16:54 +08:00
for (let i = 1; i < numbers.length; i++) {
const k = numbers[i];
let entry: string;
2025-08-20 10:16:54 +08:00
if (dictionary[k] !== undefined) {
entry = dictionary[k];
} else if (k === dictSize) {
entry = w + w.charAt(0);
} else {
throw new Error('LZ解压缩错误无效的压缩数据');
}
2025-08-20 10:16:54 +08:00
result.push(entry);
dictionary[dictSize++] = w + entry.charAt(0);
w = entry;
2025-08-20 10:16:54 +08:00
// 防止字典过大
if (dictSize >= 0xFFFF) {
dictSize = 256;
// 重置字典
for (const key in dictionary) {
if (parseInt(key) >= 256) {
delete dictionary[key];
}
}
}
}
2025-08-20 10:16:54 +08:00
// 将结果转换为ArrayBuffer
const output = result.join('');
const encoder = new TextEncoder();
return encoder.encode(output).buffer;
}
2025-08-20 10:16:54 +08:00
estimateCompressedSize(data: ArrayBuffer): number {
// 简单估算假设压缩率在30%-70%之间
const size = data.byteLength;
return Math.floor(size * 0.5); // 50%的估算压缩率
}
2025-08-20 10:16:54 +08:00
/**
* ArrayBuffer
*/
private numbersToArrayBuffer(numbers: number[]): ArrayBuffer {
// 使用变长编码以节省空间
const bytes: number[] = [];
2025-08-20 10:16:54 +08:00
for (const num of numbers) {
if (num < 128) {
// 小于128用1字节
bytes.push(num);
} else if (num < 16384) {
// 小于16384用2字节最高位为1表示有下一字节
bytes.push(0x80 | (num & 0x7F));
bytes.push((num >> 7) & 0x7F);
} else {
// 大于等于16384用3字节
bytes.push(0x80 | (num & 0x7F));
bytes.push(0x80 | ((num >> 7) & 0x7F));
bytes.push((num >> 14) & 0x7F);
}
}
2025-08-20 10:16:54 +08:00
return new Uint8Array(bytes).buffer;
}
2025-08-20 10:16:54 +08:00
/**
* ArrayBuffer转换为数字数组
*/
private arrayBufferToNumbers(buffer: ArrayBuffer): number[] {
const bytes = new Uint8Array(buffer);
const numbers: number[] = [];
2025-08-20 10:16:54 +08:00
for (let i = 0; i < bytes.length; i++) {
const byte1 = bytes[i];
2025-08-20 10:16:54 +08:00
if ((byte1 & 0x80) === 0) {
// 单字节数字
numbers.push(byte1);
} else {
// 多字节数字
let num = byte1 & 0x7F;
i++;
2025-08-20 10:16:54 +08:00
if (i < bytes.length) {
const byte2 = bytes[i];
num |= (byte2 & 0x7F) << 7;
2025-08-20 10:16:54 +08:00
if ((byte2 & 0x80) !== 0) {
// 三字节数字
i++;
if (i < bytes.length) {
const byte3 = bytes[i];
num |= (byte3 & 0x7F) << 14;
}
}
}
2025-08-20 10:16:54 +08:00
numbers.push(num);
}
}
2025-08-20 10:16:54 +08:00
return numbers;
}
}
/**
*
*/
export class MessageCompressor {
private logger = createLogger('MessageCompressor');
private config: CompressionConfig;
2025-08-20 10:16:54 +08:00
private algorithms = new Map<string, ICompressionAlgorithm>();
private stats: CompressionStats;
/**
*
*/
constructor(config: Partial<CompressionConfig> = {}) {
this.config = {
2025-08-20 10:16:54 +08:00
defaultAlgorithm: 'none',
threshold: 1024, // 1KB以上才压缩
enableAsync: true,
2025-08-20 10:16:54 +08:00
level: 6,
chunkSize: 64 * 1024, // 64KB分块
2025-08-20 10:16:54 +08:00
enableStats: true,
...config
};
this.stats = {
2025-08-20 10:16:54 +08:00
totalCompressions: 0,
totalDecompressions: 0,
totalOriginalBytes: 0,
totalCompressedBytes: 0,
2025-08-20 10:16:54 +08:00
averageCompressionRatio: 1.0,
averageCompressionTime: 0,
averageDecompressionTime: 0,
2025-08-20 10:16:54 +08:00
algorithmUsage: {}
};
2025-08-20 10:16:54 +08:00
// 注册默认算法
this.registerAlgorithm(new NoCompressionAlgorithm());
this.registerAlgorithm(new LZCompressionAlgorithm());
}
/**
*
*/
public registerAlgorithm(algorithm: ICompressionAlgorithm): void {
if (this.algorithms.has(algorithm.name)) {
this.logger.warn(`压缩算法 '${algorithm.name}' 已存在,将被覆盖`);
}
2025-08-20 10:16:54 +08:00
this.algorithms.set(algorithm.name, algorithm);
this.stats.algorithmUsage[algorithm.name] = 0;
2025-08-20 10:16:54 +08:00
this.logger.info(`注册压缩算法: ${algorithm.name} v${algorithm.version}`);
}
/**
*
*/
public unregisterAlgorithm(algorithmName: string): boolean {
if (algorithmName === 'none') {
this.logger.warn('无法注销默认的无压缩算法');
return false;
}
2025-08-20 10:16:54 +08:00
const removed = this.algorithms.delete(algorithmName);
if (removed) {
delete this.stats.algorithmUsage[algorithmName];
this.logger.info(`注销压缩算法: ${algorithmName}`);
}
2025-08-20 10:16:54 +08:00
return removed;
}
/**
*
*/
public getRegisteredAlgorithms(): string[] {
return Array.from(this.algorithms.keys());
}
/**
*
*/
public hasAlgorithm(algorithmName: string): boolean {
return this.algorithms.has(algorithmName);
}
/**
*
*/
2025-08-20 10:16:54 +08:00
public async compress(
data: ArrayBuffer | string,
2025-08-20 10:16:54 +08:00
algorithmName?: string
): Promise<CompressionResult> {
const startTime = performance.now();
2025-08-20 10:16:54 +08:00
// 转换输入数据
const inputBuffer = typeof data === 'string'
? new TextEncoder().encode(data).buffer
2025-08-20 10:16:54 +08:00
: data;
const originalSize = inputBuffer.byteLength;
2025-08-20 10:16:54 +08:00
// 选择压缩算法
const selectedAlgorithm = algorithmName || this.config.defaultAlgorithm;
const algorithm = this.algorithms.get(selectedAlgorithm);
2025-08-20 10:16:54 +08:00
if (!algorithm) {
throw new Error(`未找到压缩算法: ${selectedAlgorithm}`);
}
try {
2025-08-20 10:16:54 +08:00
let compressedData: ArrayBuffer;
let wasCompressed = false;
2025-08-20 10:16:54 +08:00
// 检查是否需要压缩
if (originalSize < this.config.threshold || selectedAlgorithm === 'none') {
compressedData = inputBuffer.slice(0);
} else {
// 选择同步或异步压缩
if (this.config.enableAsync && algorithm.supportsAsync && algorithm.compressAsync) {
compressedData = await algorithm.compressAsync(inputBuffer);
} else {
compressedData = algorithm.compress(inputBuffer);
}
wasCompressed = true;
}
const endTime = performance.now();
const compressionTime = endTime - startTime;
2025-08-20 10:16:54 +08:00
const compressedSize = compressedData.byteLength;
const compressionRatio = originalSize > 0 ? compressedSize / originalSize : 1;
2025-08-20 10:16:54 +08:00
// 更新统计信息
if (this.config.enableStats) {
this.updateCompressionStats(
selectedAlgorithm,
originalSize,
compressedSize,
2025-08-20 10:16:54 +08:00
compressionTime
);
}
const result: CompressionResult = {
data: compressedData,
originalSize,
compressedSize,
compressionRatio,
compressionTime,
2025-08-20 10:16:54 +08:00
algorithm: selectedAlgorithm,
wasCompressed
};
2025-08-20 10:16:54 +08:00
this.logger.debug(
`压缩完成: ${originalSize}B -> ${compressedSize}B ` +
`(${(compressionRatio * 100).toFixed(1)}%) ` +
`用时 ${compressionTime.toFixed(2)}ms, 算法: ${selectedAlgorithm}`
);
return result;
} catch (error) {
2025-08-20 10:16:54 +08:00
this.logger.error(`压缩失败 (${selectedAlgorithm}):`, error);
throw error;
}
}
/**
*
*/
2025-08-20 10:16:54 +08:00
public async decompress(
data: ArrayBuffer,
2025-08-20 10:16:54 +08:00
algorithmName: string
): Promise<DecompressionResult> {
const startTime = performance.now();
2025-08-20 10:16:54 +08:00
const compressedSize = data.byteLength;
2025-08-20 10:16:54 +08:00
const algorithm = this.algorithms.get(algorithmName);
if (!algorithm) {
throw new Error(`未找到解压缩算法: ${algorithmName}`);
}
try {
2025-08-20 10:16:54 +08:00
let decompressedData: ArrayBuffer;
2025-08-20 10:16:54 +08:00
// 选择同步或异步解压缩
if (this.config.enableAsync && algorithm.supportsAsync && algorithm.decompressAsync) {
decompressedData = await algorithm.decompressAsync(data);
} else {
decompressedData = algorithm.decompress(data);
}
const endTime = performance.now();
const decompressionTime = endTime - startTime;
2025-08-20 10:16:54 +08:00
const decompressedSize = decompressedData.byteLength;
2025-08-20 10:16:54 +08:00
// 更新统计信息
if (this.config.enableStats) {
this.updateDecompressionStats(algorithmName, decompressionTime);
}
2025-08-20 10:16:54 +08:00
const result: DecompressionResult = {
data: decompressedData,
compressedSize,
decompressedSize,
decompressionTime,
algorithm: algorithmName
};
this.logger.debug(
`解压缩完成: ${compressedSize}B -> ${decompressedSize}B ` +
`用时 ${decompressionTime.toFixed(2)}ms, 算法: ${algorithmName}`
);
return result;
} catch (error) {
2025-08-20 10:16:54 +08:00
this.logger.error(`解压缩失败 (${algorithmName}):`, error);
throw error;
}
}
/**
2025-08-20 10:16:54 +08:00
*
*/
2025-08-20 10:16:54 +08:00
public estimateCompressedSize(
data: ArrayBuffer,
2025-08-20 10:16:54 +08:00
algorithmName?: string
): number {
const selectedAlgorithm = algorithmName || this.config.defaultAlgorithm;
const algorithm = this.algorithms.get(selectedAlgorithm);
2025-08-20 10:16:54 +08:00
if (!algorithm) {
return data.byteLength;
}
2025-08-20 10:16:54 +08:00
if (algorithm.estimateCompressedSize) {
return algorithm.estimateCompressedSize(data);
}
2025-08-20 10:16:54 +08:00
// 如果没有估算函数,返回原始大小
return data.byteLength;
}
/**
2025-08-20 10:16:54 +08:00
*
*/
2025-08-20 10:16:54 +08:00
public getStats(): CompressionStats {
return { ...this.stats };
}
/**
*
*/
2025-08-20 10:16:54 +08:00
public resetStats(): void {
this.stats = {
2025-08-20 10:16:54 +08:00
totalCompressions: 0,
totalDecompressions: 0,
totalOriginalBytes: 0,
totalCompressedBytes: 0,
2025-08-20 10:16:54 +08:00
averageCompressionRatio: 1.0,
averageCompressionTime: 0,
averageDecompressionTime: 0,
2025-08-20 10:16:54 +08:00
algorithmUsage: {}
};
2025-08-20 10:16:54 +08:00
// 重新初始化算法使用统计
for (const algorithmName of this.algorithms.keys()) {
this.stats.algorithmUsage[algorithmName] = 0;
}
}
/**
2025-08-20 10:16:54 +08:00
*
*/
2025-08-20 10:16:54 +08:00
public updateConfig(newConfig: Partial<CompressionConfig>): void {
Object.assign(this.config, newConfig);
this.logger.info('压缩器配置已更新');
}
/**
2025-08-20 10:16:54 +08:00
*
*/
2025-08-20 10:16:54 +08:00
public getConfig(): CompressionConfig {
return { ...this.config };
}
/**
2025-08-20 10:16:54 +08:00
*
*/
2025-08-20 10:16:54 +08:00
private updateCompressionStats(
algorithmName: string,
originalSize: number,
compressedSize: number,
2025-08-20 10:16:54 +08:00
compressionTime: number
): void {
this.stats.totalCompressions++;
this.stats.totalOriginalBytes += originalSize;
this.stats.totalCompressedBytes += compressedSize;
this.stats.algorithmUsage[algorithmName]++;
// 更新平均压缩比
this.stats.averageCompressionRatio = this.stats.totalOriginalBytes > 0
? this.stats.totalCompressedBytes / this.stats.totalOriginalBytes
2025-08-20 10:16:54 +08:00
: 1.0;
// 更新平均压缩时间
this.stats.averageCompressionTime =
(this.stats.averageCompressionTime * (this.stats.totalCompressions - 1) + compressionTime)
2025-08-20 10:16:54 +08:00
/ this.stats.totalCompressions;
}
/**
2025-08-20 10:16:54 +08:00
*
*/
2025-08-20 10:16:54 +08:00
private updateDecompressionStats(algorithmName: string, decompressionTime: number): void {
this.stats.totalDecompressions++;
2025-08-20 10:16:54 +08:00
// 更新平均解压缩时间
this.stats.averageDecompressionTime =
(this.stats.averageDecompressionTime * (this.stats.totalDecompressions - 1) + decompressionTime)
2025-08-20 10:16:54 +08:00
/ this.stats.totalDecompressions;
}
2025-08-20 10:16:54 +08:00
}
2025-08-20 10:16:54 +08:00
/**
*
*/
export const globalCompressor = new MessageCompressor();