Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: send data by chunk in websocket #3988

Merged
merged 21 commits into from
Feb 20, 2025
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions packages/components/src/recycle-tree/tree/TreeNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,15 @@ const { Path } = path;
* @param items 插入的数组
*/
export function spliceArray(arr: number[], start: number, deleteCount = 0, items?: number[] | null) {
const a = arr.slice(0);
a.splice(start, deleteCount, ...(items || []));
return a;
// 如果没有修改操作,直接返回原数组
if (deleteCount === 0 && (!items || items.length === 0)) {
return arr;
}

// 直接使用 slice + concat 避免 spread operator
const before = arr.slice(0, start);
const after = arr.slice(start + deleteCount);
return before.concat(items || []).concat(after);
}

export enum BranchOperatorStatus {
Expand Down Expand Up @@ -568,10 +574,10 @@ export class CompositeTreeNode extends TreeNode implements ICompositeTreeNode {
}

/**
* 确保此“目录”的子级已加载(不影响“展开”状态)
* 确保此"目录"的子级已加载(不影响"展开"状态)
* 如果子级已经加载,则返回的Promise将立即解决
* 否则,将发出重新加载请求并返回Promise
* 一旦返回的Promise.resolve,CompositeTreeNode#children 便可以访问到对于节点
* 一旦返回的Promise.resolve,"CompositeTreeNode#children" 便可以访问到对于节点
*/
public async ensureLoaded(token?: CancellationToken) {
if (this._children) {
Expand Down
7 changes: 4 additions & 3 deletions packages/connection/__test__/browser/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { furySerializer } from '@opensumi/ide-connection';
import { WSWebSocketConnection, furySerializer } from '@opensumi/ide-connection';
import { ReconnectingWebSocketConnection } from '@opensumi/ide-connection/lib/common/connection/drivers/reconnecting-websocket';
import { sleep } from '@opensumi/ide-core-common';
import { Server, WebSocket } from '@opensumi/mock-socket';
Expand All @@ -21,10 +21,11 @@ describe('connection browser', () => {
let data2Received = false;

mockServer.on('connection', (socket) => {
socket.on('message', (msg) => {
const connection = new WSWebSocketConnection(socket as any);
connection.onMessage((msg) => {
const msgObj = furySerializer.deserialize(msg as Uint8Array);
if (msgObj.kind === 'open') {
socket.send(
connection.send(
furySerializer.serialize({
id: msgObj.id,
kind: 'server-ready',
Expand Down
165 changes: 165 additions & 0 deletions packages/connection/__test__/common/buffers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,158 @@ describe('Buffers', () => {
expect(cursor.lineOffset).toEqual(1);
expect(list.pos(cursor.offset)).toEqual({ buf: cursor.line, offset: cursor.lineOffset });
});

// 测试空缓冲区行为
describe('Empty Buffer Handling', () => {
it('should handle empty buffer operations', () => {
const empty = new Buffers();

expect(empty.byteLength).toBe(0);
expect(empty.slice()).toEqual(new Uint8Array(0));
expect(() => empty.pos(0)).toThrow('out of range');
expect(empty.splice(0, 0)).toEqual(expect.any(Buffers));
});
});

// 测试边界slice操作
describe('Edge Case Slicing', () => {
const buffer = create([0, 1, 2, 3, 4, 5], [3, 3]);

it('should handle start at chunk boundary', () => {
expect(buffer.slice(3, 5)).toEqual(new Uint8Array([3, 4]));
});

it('should handle end at chunk boundary', () => {
expect(buffer.slice(2, 3)).toEqual(new Uint8Array([2]));
});
});
it('should handle splice at exact chunk boundary', () => {
const buffer = createEnhanced([0, 1, 2, 3, 4, 5], [3, 3]);
buffer.splice(3, 2, new Uint8Array([99]));
expect(buffer.slice()).toEqual(new Uint8Array([0, 1, 2, 99, 5]));
});
// 测试非法索引访问
describe('Invalid Access Handling', () => {
const buffer = create([1, 2, 3], [3]);

it('should throw on negative index', () => {
expect(() => buffer.pos(-1)).toThrow('out of range');
});

it('should throw on overflow index', () => {
expect(() => buffer.pos(4)).toThrow('out of range');
});
});

// 测试超大缓冲区
describe('Large Buffer Handling', () => {
const MB1 = new Uint8Array(1024 * 1024);
const buffer = new Buffers();

beforeAll(() => {
// 填充1MB数据
for (let i = 0; i < 1024; i++) {
buffer.push(MB1.subarray(0, 1024));
}
});

it('should handle 1GB data slicing', () => {
const slice = buffer.slice(1024 * 512, 1024 * 512 + 100);
expect(slice.byteLength).toBe(100);
});
});

// 测试跨chunk的splice操作
describe('Cross-Chunk Splicing', () => {
const buffer = create([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [3, 3, 4]);

it('should splice across multiple chunks', () => {
const removed = buffer.splice(2, 5, new Uint8Array([99]));

expect(buffer.slice()).toEqual(new Uint8Array([0, 1, 99, 7, 8, 9]));
expect(removed.slice()).toEqual(new Uint8Array([2, 3, 4, 5, 6]));
});
});

// 测试slice4特殊方法
describe('slice4 Special Cases', () => {
it('should handle partial slice4', () => {
const buffer = create([1, 2, 3], [3]);
expect(buffer.slice4(2)).toEqual(new Uint8Array([3, 0, 0, 0]));
});

it('should handle edge slice4', () => {
const buffer = create([1, 2, 3, 4, 5], [5]);
expect(buffer.slice4(1)).toEqual(new Uint8Array([2, 3, 4, 5]));
});
});

// 测试Cursor高级功能
describe('Cursor Advanced Operations', () => {
const buffer = create([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [3, 3, 4]);

it('should handle moveTo across chunks', () => {
const cursor = buffer.cursor();
cursor.moveTo(5);
expect(cursor.line).toBe(1);
expect(cursor.lineOffset).toBe(2);
});

it('should reset correctly', () => {
const cursor = buffer.cursor(5);
cursor.reset();
expect(cursor.offset).toBe(0);
expect(cursor.line).toBe(0);
});
});

// 测试Dispose行为
describe('Resource Management', () => {
it('should clear resources on dispose', () => {
const buffer = create([1, 2, 3], [3]);
buffer.dispose();

expect(buffer.buffers).toEqual([]);
expect(buffer.byteLength).toBe(0);
});
});

// 性能测试
describe('Performance Tests', () => {
let largeBuffer: Buffers;

beforeAll(() => {
largeBuffer = new Buffers();
// 创建包含1000个10KB chunk的缓冲区
for (let i = 0; i < 1000; i++) {
largeBuffer.push(new Uint8Array(10 * 1024));
}
});

it('should handle slicing 1MB data under 50ms', () => {
const start = performance.now();
const slice = largeBuffer.slice(0, 1024 * 1024);
const duration = performance.now() - start;

expect(duration).toBeLessThan(50);
expect(slice.byteLength).toBe(1024 * 1024);
});

it('should handle 1000 splices under 1s', () => {
const buf = createEnhanced(
new Array(10000).fill(0).map((_, i) => i),
[100, 900, 9000],
);

const start = performance.now();
for (let i = 0; i < 1000; i++) {
buf.splice(i % 100, 5, new Uint8Array([i]));
}
const duration = performance.now() - start;

expect(duration).toBeLessThan(1000);
});
});
});

function create(xs: number[], split: number[]) {
Expand All @@ -264,3 +416,16 @@ function create(xs: number[], split: number[]) {
});
return bufs;
}

function createEnhanced(xs: number[], split: number[]): Buffers {
const bufs = new Buffers();
let offset = 0;
split.forEach((chunkSize) => {
if (chunkSize > 0) {
const chunk = new Uint8Array(xs.slice(offset, offset + chunkSize));
bufs.push(chunk);
offset += chunkSize;
}
});
return bufs;
}
Loading
Loading