Skip to content

Commit 941812f

Browse files
committed
Memory usage improvements in KeybindingResolver
1 parent 0d3ac4f commit 941812f

3 files changed

Lines changed: 68 additions & 37 deletions

File tree

src/vs/base/common/keyCodes.ts

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -404,40 +404,48 @@ export namespace KeyCodeUtils {
404404
}
405405
}
406406

407-
// Binary encoding strategy:
408-
// 15: 1 bit for ctrlCmd
409-
// 14: 1 bit for shift
410-
// 13: 1 bit for alt
411-
// 12: 1 bit for winCtrl
412-
// 0: 12 bits for keyCode (up to a maximum keyCode of 4096. Given we have 83 at this point thats good enough)
407+
/**
408+
* Binary encoding strategy:
409+
* ```
410+
* 1111 11
411+
* 5432 1098 7654 3210
412+
* ---- CSAW KKKK KKKK
413+
* C = bit 11 = ctrlCmd flag
414+
* S = bit 10 = shift flag
415+
* A = bit 9 = alt flag
416+
* W = bit 8 = winCtrl flag
417+
* K = bits 0-7 = key code
418+
* ```
419+
*/
413420
const enum BinaryKeybindingsMask {
414-
CtrlCmd = 1 << 15,
415-
Shift = 1 << 14,
416-
Alt = 1 << 13,
417-
WinCtrl = 1 << 12,
418-
KeyCode = 0x00000fff,
421+
CtrlCmd = (1 << 11) >>> 0,
422+
Shift = (1 << 10) >>> 0,
423+
Alt = (1 << 9) >>> 0,
424+
WinCtrl = (1 << 8) >>> 0,
425+
KeyCode = 0x000000ff,
419426
ModifierMask = CtrlCmd | Shift | Alt | WinCtrl
420427
}
421428

422429
export const enum KeyMod {
423-
CtrlCmd = 1 << 15,
424-
Shift = 1 << 14,
425-
Alt = 1 << 13,
426-
WinCtrl = 1 << 12,
430+
CtrlCmd = (1 << 11) >>> 0,
431+
Shift = (1 << 10) >>> 0,
432+
Alt = (1 << 9) >>> 0,
433+
WinCtrl = (1 << 8) >>> 0,
427434
}
428435

429436
export function KeyChord(firstPart: number, secondPart: number): number {
430-
return firstPart | ((secondPart & 0x0000ffff) << 16);
437+
let chordPart = ((secondPart & 0x0000ffff) << 16) >>> 0;
438+
return (firstPart | chordPart) >>> 0;
431439
}
432440

433441
export class BinaryKeybindings {
434442

435443
public static extractFirstPart(keybinding: number): number {
436-
return keybinding & 0x0000ffff;
444+
return (keybinding & 0x0000ffff) >>> 0;
437445
}
438446

439447
public static extractChordPart(keybinding: number): number {
440-
return (keybinding >> 16) & 0x0000ffff;
448+
return (keybinding & 0xffff0000) >>> 16;
441449
}
442450

443451
public static hasChord(keybinding: number): boolean {

src/vs/platform/keybinding/common/keybindingResolver.ts

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,11 @@ export class KeybindingResolver {
6666
private _defaultBoundCommands: IBoundCommands;
6767
private _map: ICommandMap;
6868
private _chords: IChordsMap;
69-
private _lookupMap: {
70-
[commandId: string]: NormalizedKeybindingItem[];
71-
};
72-
private _lookupMapUnreachable: {
73-
// The value contains the keybinding or first part of a chord
74-
[commandId: string]: number[];
75-
};
69+
private _lookupMap: Map<string, ArrayBuffer>;
70+
/**
71+
* The value contains the keybinding or first part of a chord
72+
*/
73+
private _lookupMapUnreachable: Map<string, ArrayBuffer>;
7674
private _shouldWarnOnConflict: boolean;
7775

7876
constructor(defaultKeybindings: IKeybindingItem[], overrides: IKeybindingItem[], shouldWarnOnConflict: boolean = true) {
@@ -87,8 +85,8 @@ export class KeybindingResolver {
8785
}
8886

8987
this._map = Object.create(null);
90-
this._lookupMap = Object.create(null);
91-
this._lookupMapUnreachable = Object.create(null);
88+
this._lookupMap = new Map<string, ArrayBuffer>();
89+
this._lookupMapUnreachable = new Map<string, ArrayBuffer>();
9290
this._chords = Object.create(null);
9391

9492
let allKeybindings = KeybindingResolver.combine(defaultKeybindings, overrides);
@@ -194,8 +192,7 @@ export class KeybindingResolver {
194192
if (this._shouldWarnOnConflict && item.isDefault) {
195193
console.warn('Conflict detected, command `' + conflict.commandId + '` cannot be triggered by ' + KeybindingLabels.toUserSettingsLabel(keypress) + ' due to ' + item.command);
196194
}
197-
this._lookupMapUnreachable[conflict.commandId] = this._lookupMapUnreachable[conflict.commandId] || [];
198-
this._lookupMapUnreachable[conflict.commandId].push(conflict.keybinding);
195+
KeybindingResolver._push(this._lookupMapUnreachable, conflict.commandId, conflict.keybinding);
199196
}
200197
}
201198

@@ -237,12 +234,40 @@ export class KeybindingResolver {
237234
return true;
238235
}
239236

237+
private static _push(map: Map<string, ArrayBuffer>, key: string, value: number): void {
238+
let oldRawEntries = map.get(key);
239+
if (typeof oldRawEntries === 'undefined') {
240+
let entries = new Uint32Array(1);
241+
entries[0] = value;
242+
map.set(key, entries.buffer);
243+
} else {
244+
let oldEntries = new Uint32Array(oldRawEntries);
245+
let newEntries = new Uint32Array(oldEntries.length + 1);
246+
newEntries.set(oldEntries, 0);
247+
newEntries[newEntries.length - 1] = value;
248+
map.set(key, newEntries.buffer);
249+
}
250+
}
251+
252+
private static _read(map: Map<string, ArrayBuffer>, key: string): number[] {
253+
let arrBuff = map.get(key);
254+
if (typeof arrBuff === 'undefined') {
255+
return null;
256+
}
257+
258+
let arr = new Uint32Array(arrBuff);
259+
let result: number[] = [];
260+
for (let i = 0, len = arr.length; i < len; i++) {
261+
result[i] = arr[i];
262+
}
263+
return result;
264+
}
265+
240266
private _addToLookupMap(item: NormalizedKeybindingItem): void {
241267
if (!item.command) {
242268
return;
243269
}
244-
this._lookupMap[item.command] = this._lookupMap[item.command] || [];
245-
this._lookupMap[item.command].push(item);
270+
KeybindingResolver._push(this._lookupMap, item.command, item.keybinding);
246271
}
247272

248273
public getDefaultBoundCommands(): IBoundCommands {
@@ -267,14 +292,12 @@ export class KeybindingResolver {
267292
}
268293

269294
public lookupKeybinding(commandId: string): Keybinding[] {
270-
let rawPossibleTriggers = this._lookupMap[commandId];
271-
if (!rawPossibleTriggers) {
295+
let possibleTriggers = KeybindingResolver._read(this._lookupMap, commandId);
296+
if (!possibleTriggers) {
272297
return [];
273298
}
274299

275-
let possibleTriggers = rawPossibleTriggers.map(possibleTrigger => possibleTrigger.keybinding);
276-
277-
let remove = this._lookupMapUnreachable[commandId];
300+
let remove = KeybindingResolver._read(this._lookupMapUnreachable, commandId);
278301
if (remove) {
279302
possibleTriggers = possibleTriggers.filter((possibleTrigger) => {
280303
return remove.indexOf(possibleTrigger) === -1;

src/vs/platform/keybinding/test/common/keybindingService.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,7 @@ suite('Keybinding Service', () => {
493493
let lookupResult = resolver.lookupKeybinding(commandId);
494494
assert.equal(lookupResult.length, expectedKeys.length, 'Length mismatch @ commandId ' + commandId + '; GOT: ' + JSON.stringify(lookupResult, null, '\t'));
495495
for (let i = 0, len = lookupResult.length; i < len; i++) {
496-
assert.equal(lookupResult[i].value, expectedKeys[i]);
496+
assert.equal(lookupResult[i].value, expectedKeys[i], 'value mismatch @ commandId ' + commandId);
497497
}
498498
};
499499

0 commit comments

Comments
 (0)