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:
Connor Peet 2022-12-02 15:54:36 -08:00 committed by GitHub
parent 07956026d7
commit 0899758dae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 83 additions and 27 deletions

View File

@ -164,7 +164,50 @@ interface IWriter {
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;
@ -177,7 +220,7 @@ class BufferReader implements IReader {
}
}
class BufferWriter implements IWriter {
export class BufferWriter implements IWriter {
private buffers: VSBuffer[] = [];
@ -196,17 +239,8 @@ enum DataType {
Buffer = 2,
VSBuffer = 3,
Array = 4,
Object = 5
}
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);
Object = 5,
Int = 6
}
function createOneByteBuffer(value: number): VSBuffer {
@ -222,53 +256,58 @@ const BufferPresets = {
VSBuffer: createOneByteBuffer(DataType.VSBuffer),
Array: createOneByteBuffer(DataType.Array),
Object: createOneByteBuffer(DataType.Object),
Uint: createOneByteBuffer(DataType.Int),
};
declare const Buffer: any;
const hasBuffer = (typeof Buffer !== 'undefined');
function serialize(writer: IWriter, data: any): void {
export function serialize(writer: IWriter, data: any): void {
if (typeof data === 'undefined') {
writer.write(BufferPresets.Undefined);
} else if (typeof data === 'string') {
const buffer = VSBuffer.fromString(data);
writer.write(BufferPresets.String);
writer.write(createSizeBuffer(buffer.byteLength));
writeInt32VQL(writer, buffer.byteLength);
writer.write(buffer);
} else if (hasBuffer && Buffer.isBuffer(data)) {
const buffer = VSBuffer.wrap(data);
writer.write(BufferPresets.Buffer);
writer.write(createSizeBuffer(buffer.byteLength));
writeInt32VQL(writer, buffer.byteLength);
writer.write(buffer);
} else if (data instanceof VSBuffer) {
writer.write(BufferPresets.VSBuffer);
writer.write(createSizeBuffer(data.byteLength));
writeInt32VQL(writer, data.byteLength);
writer.write(data);
} else if (Array.isArray(data)) {
writer.write(BufferPresets.Array);
writer.write(createSizeBuffer(data.length));
writeInt32VQL(writer, data.length);
for (const el of data) {
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 {
const buffer = VSBuffer.fromString(JSON.stringify(data));
writer.write(BufferPresets.Object);
writer.write(createSizeBuffer(buffer.byteLength));
writeInt32VQL(writer, buffer.byteLength);
writer.write(buffer);
}
}
function deserialize(reader: IReader): any {
export function deserialize(reader: IReader): any {
const type = reader.read(1).readUInt8(0);
switch (type) {
case DataType.Undefined: return undefined;
case DataType.String: return reader.read(readSizeBuffer(reader)).toString();
case DataType.Buffer: return reader.read(readSizeBuffer(reader)).buffer;
case DataType.VSBuffer: return reader.read(readSizeBuffer(reader));
case DataType.String: return reader.read(readIntVQL(reader)).toString();
case DataType.Buffer: return reader.read(readIntVQL(reader)).buffer;
case DataType.VSBuffer: return reader.read(readIntVQL(reader));
case DataType.Array: {
const length = readSizeBuffer(reader);
const length = readIntVQL(reader);
const result: any[] = [];
for (let i = 0; i < length; i++) {
@ -277,7 +316,8 @@ function deserialize(reader: IReader): any {
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);
}
}

View File

@ -11,7 +11,7 @@ import { canceled } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { isEqual } from 'vs/base/common/resources';
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 {
@ -319,6 +319,22 @@ suite('Base IPC', function () {
const r = await ipcService.buffersLength([VSBuffer.alloc(2), VSBuffer.alloc(3)]);
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 () {

View File

@ -27,7 +27,7 @@ Unit tests from layers `common` and `browser` are run inside `chromium`, `webkit
## 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