mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 04:49:35 +00:00
ipc: use vql for uint types (#167407)
* ipc: use vql for uint types On the plane I was reverse-engineering ipc.ts to implement it in Rust and see if we could have a "service mode" for the CLI that we could interact with like any other vscode process. In doing so, I noticed that numbers in the protocol--which are used at least twice in the message header and ID--were encoded as JSON. I was curious what benefits we'd get from encoding them as variable-length integers instead. It makes the message shorter, as expected. Encode/decode time are very, very slightly lower. I'm not sure it's worth the extra complexity, but I have included it here for your consideration. * fixup tests
This commit is contained in:
parent
07956026d7
commit
0899758dae
|
@ -164,7 +164,50 @@ interface IWriter {
|
||||||
write(buffer: VSBuffer): void;
|
write(buffer: VSBuffer): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BufferReader implements IReader {
|
|
||||||
|
/**
|
||||||
|
* @see https://en.wikipedia.org/wiki/Variable-length_quantity
|
||||||
|
*/
|
||||||
|
function readIntVQL(reader: IReader) {
|
||||||
|
let value = 0;
|
||||||
|
for (let n = 0; ; n += 7) {
|
||||||
|
const next = reader.read(1);
|
||||||
|
value |= (next.buffer[0] & 0b01111111) << n;
|
||||||
|
if (!(next.buffer[0] & 0b10000000)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const vqlZero = createOneByteBuffer(0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see https://en.wikipedia.org/wiki/Variable-length_quantity
|
||||||
|
*/
|
||||||
|
function writeInt32VQL(writer: IWriter, value: number) {
|
||||||
|
if (value === 0) {
|
||||||
|
writer.write(vqlZero);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let len = 0;
|
||||||
|
for (let v2 = value; v2 !== 0; v2 = v2 >>> 7) {
|
||||||
|
len++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scratch = VSBuffer.alloc(len);
|
||||||
|
for (let i = 0; value !== 0; i++) {
|
||||||
|
scratch.buffer[i] = value & 0b01111111;
|
||||||
|
value = value >>> 7;
|
||||||
|
if (value > 0) {
|
||||||
|
scratch.buffer[i] |= 0b10000000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.write(scratch);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BufferReader implements IReader {
|
||||||
|
|
||||||
private pos = 0;
|
private pos = 0;
|
||||||
|
|
||||||
|
@ -177,7 +220,7 @@ class BufferReader implements IReader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class BufferWriter implements IWriter {
|
export class BufferWriter implements IWriter {
|
||||||
|
|
||||||
private buffers: VSBuffer[] = [];
|
private buffers: VSBuffer[] = [];
|
||||||
|
|
||||||
|
@ -196,17 +239,8 @@ enum DataType {
|
||||||
Buffer = 2,
|
Buffer = 2,
|
||||||
VSBuffer = 3,
|
VSBuffer = 3,
|
||||||
Array = 4,
|
Array = 4,
|
||||||
Object = 5
|
Object = 5,
|
||||||
}
|
Int = 6
|
||||||
|
|
||||||
function createSizeBuffer(size: number): VSBuffer {
|
|
||||||
const result = VSBuffer.alloc(4);
|
|
||||||
result.writeUInt32BE(size, 0);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function readSizeBuffer(reader: IReader): number {
|
|
||||||
return reader.read(4).readUInt32BE(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createOneByteBuffer(value: number): VSBuffer {
|
function createOneByteBuffer(value: number): VSBuffer {
|
||||||
|
@ -222,53 +256,58 @@ const BufferPresets = {
|
||||||
VSBuffer: createOneByteBuffer(DataType.VSBuffer),
|
VSBuffer: createOneByteBuffer(DataType.VSBuffer),
|
||||||
Array: createOneByteBuffer(DataType.Array),
|
Array: createOneByteBuffer(DataType.Array),
|
||||||
Object: createOneByteBuffer(DataType.Object),
|
Object: createOneByteBuffer(DataType.Object),
|
||||||
|
Uint: createOneByteBuffer(DataType.Int),
|
||||||
};
|
};
|
||||||
|
|
||||||
declare const Buffer: any;
|
declare const Buffer: any;
|
||||||
const hasBuffer = (typeof Buffer !== 'undefined');
|
const hasBuffer = (typeof Buffer !== 'undefined');
|
||||||
|
|
||||||
function serialize(writer: IWriter, data: any): void {
|
export function serialize(writer: IWriter, data: any): void {
|
||||||
if (typeof data === 'undefined') {
|
if (typeof data === 'undefined') {
|
||||||
writer.write(BufferPresets.Undefined);
|
writer.write(BufferPresets.Undefined);
|
||||||
} else if (typeof data === 'string') {
|
} else if (typeof data === 'string') {
|
||||||
const buffer = VSBuffer.fromString(data);
|
const buffer = VSBuffer.fromString(data);
|
||||||
writer.write(BufferPresets.String);
|
writer.write(BufferPresets.String);
|
||||||
writer.write(createSizeBuffer(buffer.byteLength));
|
writeInt32VQL(writer, buffer.byteLength);
|
||||||
writer.write(buffer);
|
writer.write(buffer);
|
||||||
} else if (hasBuffer && Buffer.isBuffer(data)) {
|
} else if (hasBuffer && Buffer.isBuffer(data)) {
|
||||||
const buffer = VSBuffer.wrap(data);
|
const buffer = VSBuffer.wrap(data);
|
||||||
writer.write(BufferPresets.Buffer);
|
writer.write(BufferPresets.Buffer);
|
||||||
writer.write(createSizeBuffer(buffer.byteLength));
|
writeInt32VQL(writer, buffer.byteLength);
|
||||||
writer.write(buffer);
|
writer.write(buffer);
|
||||||
} else if (data instanceof VSBuffer) {
|
} else if (data instanceof VSBuffer) {
|
||||||
writer.write(BufferPresets.VSBuffer);
|
writer.write(BufferPresets.VSBuffer);
|
||||||
writer.write(createSizeBuffer(data.byteLength));
|
writeInt32VQL(writer, data.byteLength);
|
||||||
writer.write(data);
|
writer.write(data);
|
||||||
} else if (Array.isArray(data)) {
|
} else if (Array.isArray(data)) {
|
||||||
writer.write(BufferPresets.Array);
|
writer.write(BufferPresets.Array);
|
||||||
writer.write(createSizeBuffer(data.length));
|
writeInt32VQL(writer, data.length);
|
||||||
|
|
||||||
for (const el of data) {
|
for (const el of data) {
|
||||||
serialize(writer, el);
|
serialize(writer, el);
|
||||||
}
|
}
|
||||||
|
} else if (typeof data === 'number' && (data | 0) === data) {
|
||||||
|
// write a vql if it's a number that we can do bitwise operations on
|
||||||
|
writer.write(BufferPresets.Uint);
|
||||||
|
writeInt32VQL(writer, data);
|
||||||
} else {
|
} else {
|
||||||
const buffer = VSBuffer.fromString(JSON.stringify(data));
|
const buffer = VSBuffer.fromString(JSON.stringify(data));
|
||||||
writer.write(BufferPresets.Object);
|
writer.write(BufferPresets.Object);
|
||||||
writer.write(createSizeBuffer(buffer.byteLength));
|
writeInt32VQL(writer, buffer.byteLength);
|
||||||
writer.write(buffer);
|
writer.write(buffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function deserialize(reader: IReader): any {
|
export function deserialize(reader: IReader): any {
|
||||||
const type = reader.read(1).readUInt8(0);
|
const type = reader.read(1).readUInt8(0);
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case DataType.Undefined: return undefined;
|
case DataType.Undefined: return undefined;
|
||||||
case DataType.String: return reader.read(readSizeBuffer(reader)).toString();
|
case DataType.String: return reader.read(readIntVQL(reader)).toString();
|
||||||
case DataType.Buffer: return reader.read(readSizeBuffer(reader)).buffer;
|
case DataType.Buffer: return reader.read(readIntVQL(reader)).buffer;
|
||||||
case DataType.VSBuffer: return reader.read(readSizeBuffer(reader));
|
case DataType.VSBuffer: return reader.read(readIntVQL(reader));
|
||||||
case DataType.Array: {
|
case DataType.Array: {
|
||||||
const length = readSizeBuffer(reader);
|
const length = readIntVQL(reader);
|
||||||
const result: any[] = [];
|
const result: any[] = [];
|
||||||
|
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
|
@ -277,7 +316,8 @@ function deserialize(reader: IReader): any {
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
case DataType.Object: return JSON.parse(reader.read(readSizeBuffer(reader)).toString());
|
case DataType.Object: return JSON.parse(reader.read(readIntVQL(reader)).toString());
|
||||||
|
case DataType.Int: return readIntVQL(reader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { canceled } from 'vs/base/common/errors';
|
||||||
import { Emitter, Event } from 'vs/base/common/event';
|
import { Emitter, Event } from 'vs/base/common/event';
|
||||||
import { isEqual } from 'vs/base/common/resources';
|
import { isEqual } from 'vs/base/common/resources';
|
||||||
import { URI } from 'vs/base/common/uri';
|
import { URI } from 'vs/base/common/uri';
|
||||||
import { ClientConnectionEvent, IChannel, IMessagePassingProtocol, IPCClient, IPCServer, IServerChannel, ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
|
import { BufferReader, BufferWriter, ClientConnectionEvent, deserialize, IChannel, IMessagePassingProtocol, IPCClient, IPCServer, IServerChannel, ProxyChannel, serialize } from 'vs/base/parts/ipc/common/ipc';
|
||||||
|
|
||||||
class QueueProtocol implements IMessagePassingProtocol {
|
class QueueProtocol implements IMessagePassingProtocol {
|
||||||
|
|
||||||
|
@ -319,6 +319,22 @@ suite('Base IPC', function () {
|
||||||
const r = await ipcService.buffersLength([VSBuffer.alloc(2), VSBuffer.alloc(3)]);
|
const r = await ipcService.buffersLength([VSBuffer.alloc(2), VSBuffer.alloc(3)]);
|
||||||
return assert.strictEqual(r, 5);
|
return assert.strictEqual(r, 5);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('round trips numbers', () => {
|
||||||
|
const input = [
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
-1,
|
||||||
|
12345,
|
||||||
|
-12345,
|
||||||
|
42.6,
|
||||||
|
123412341234
|
||||||
|
];
|
||||||
|
|
||||||
|
const writer = new BufferWriter();
|
||||||
|
serialize(writer, input);
|
||||||
|
assert.deepStrictEqual(deserialize(new BufferReader(writer.buffer)), input);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
suite('one to one (proxy)', function () {
|
suite('one to one (proxy)', function () {
|
||||||
|
|
|
@ -27,7 +27,7 @@ Unit tests from layers `common` and `browser` are run inside `chromium`, `webkit
|
||||||
|
|
||||||
## Run (with node)
|
## Run (with node)
|
||||||
|
|
||||||
yarn run mocha --ui tdd --run src/vs/editor/test/browser/controller/cursor.test.ts
|
yarn test-node --run src/vs/editor/test/browser/controller/cursor.test.ts
|
||||||
|
|
||||||
## Coverage
|
## Coverage
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue