-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
perf(core): defer sequence constructor logic
- Loading branch information
1 parent
0e47dde
commit 85bb4d1
Showing
10 changed files
with
150 additions
and
80 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { Cache } from './cache'; | ||
|
||
describe('Cache decorator', () => { | ||
it('should cache getter', () => { | ||
const spy = jest.fn(); | ||
|
||
class Test { | ||
@Cache() | ||
get prop() { | ||
return spy(); | ||
} | ||
} | ||
|
||
const t = new Test(); | ||
|
||
t.prop; | ||
t.prop; | ||
|
||
expect(spy).toBeCalledTimes(1); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
/** Caches getter property after first call. */ | ||
export function Cache() { | ||
return (_: unknown, name: PropertyKey, descriptor: PropertyDescriptor) => { | ||
const { get, configurable, enumerable } = descriptor; | ||
|
||
descriptor.get = function () { | ||
const value = get.call(this); | ||
|
||
Object.defineProperty(this, name, { | ||
configurable, | ||
enumerable, | ||
writable: false, | ||
value, | ||
}); | ||
|
||
return value; | ||
}; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export * from './keyboard'; | ||
export * from './util'; | ||
export { Cache } from './cache'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,33 @@ | ||
import { fromHex, HexCode } from '../common'; | ||
import { Cache } from '@/common'; | ||
import { fromHex, HexCode, toHex } from '../common'; | ||
|
||
export abstract class HexCodeSequence<T extends HexCode[]> { | ||
export abstract class HexCodeSequence { | ||
/** Transformed value each hex code is a one character. */ | ||
readonly tValue = this.value.map(fromHex).join(''); | ||
@Cache() | ||
get tValue() { | ||
return typeof this.input === 'string' | ||
? this.input | ||
: HexCodeSequence.fromHex(this.input); | ||
} | ||
|
||
/** Raw value of in hex. */ | ||
@Cache() | ||
get value() { | ||
return typeof this.input === 'string' | ||
? HexCodeSequence.toHex(this.input) | ||
: this.input; | ||
} | ||
|
||
/** Length of hex codes. */ | ||
readonly length = this.value.length; | ||
readonly length = this.input.length; | ||
|
||
constructor(private readonly input: HexCode[] | string) {} | ||
|
||
static toHex(value: string) { | ||
return value.split('').map(toHex); | ||
} | ||
|
||
constructor(public readonly value: T) {} | ||
static fromHex(value: HexCode[]) { | ||
return value.map(fromHex).join(''); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,56 +1,82 @@ | ||
import { Serializable } from '@/common'; | ||
import { Cache, Serializable, uniqueBy } from '@/common'; | ||
import { HexCode } from '../common'; | ||
import { Daemon } from './daemon'; | ||
import { HexCodeSequence } from './hex-code-sequence'; | ||
import { memoizedFindOverlap } from './overlap'; | ||
|
||
// interface will lose its index signature. | ||
export type SequenceJSON = { | ||
value: HexCode[]; | ||
parts: number[]; | ||
}; | ||
|
||
export class Sequence | ||
extends HexCodeSequence<HexCode[]> | ||
implements Serializable | ||
{ | ||
readonly indexes = this.parts.map((d) => d.index); | ||
|
||
/** List of indexes on which it is safe to break sequence. */ | ||
readonly breaks = this.getSequenceBreaks(); | ||
|
||
constructor(value: HexCode[], public readonly parts: Daemon[]) { | ||
super(value); | ||
export class Sequence extends HexCodeSequence implements Serializable { | ||
@Cache() | ||
get indexes() { | ||
return this.parts.map((d) => d.index); | ||
} | ||
|
||
toJSON(): SequenceJSON { | ||
return { | ||
value: this.value, | ||
parts: this.parts.map((p) => p.index), | ||
}; | ||
@Cache() | ||
get parts() { | ||
return this.permutation | ||
.flatMap((d) => d.getParts()) | ||
.filter(uniqueBy('index')); | ||
} | ||
|
||
// Sequence break is an index which doesn't have daemon attached. | ||
// For example daemons: FF 7A and BD BD can create sequence FF 7A BD BD. | ||
// At index 0 and 2 next daemon is not yet started and can be delayed if | ||
// buffer allows it. | ||
// It's not possible to break sequence on overlap. For example daemons: | ||
// FF 7A and 7A BD BD can not be broken on index 2 because they share | ||
// beginning and the end. | ||
private getSequenceBreaks() { | ||
return this.parts | ||
.filter((d) => !d.isChild) | ||
/** List of indexes on which it is safe to break sequence. */ | ||
@Cache() | ||
get breaks() { | ||
// Sequence break is an index which doesn't have daemon attached. | ||
// For example daemons: FF 7A and BD BD can create sequence FF 7A BD BD. | ||
// At index 0 and 2 next daemon is not yet started and can be delayed if | ||
// buffer allows it. | ||
// It's not possible to break sequence on overlap. For example daemons: | ||
// FF 7A and 7A BD BD can not be broken on index 2 because they share | ||
// beginning and the end. | ||
return this.permutation | ||
.map((d) => { | ||
const start = this.tValue.indexOf(d.tValue); | ||
const end = start + d.length - 1; | ||
|
||
return [start, end]; | ||
return { start, end }; | ||
}) | ||
.filter(([s1], i, array) => { | ||
const prev = array[i - 1]; | ||
const e2 = prev ? prev[1] : -1; | ||
.filter(({ start }, i, array) => { | ||
const { end = -1 } = array[i - 1] ?? {}; | ||
|
||
return s1 > e2; | ||
return start > end; | ||
}) | ||
.map(([start]) => start); | ||
.map(({ start }) => start); | ||
} | ||
|
||
constructor( | ||
value: HexCode[] | string, | ||
private readonly permutation: Daemon[] | ||
) { | ||
super(value); | ||
} | ||
|
||
/** Creates sequence out of permutation of daemons up to given length. */ | ||
static fromPermutation( | ||
permutation: Daemon[], | ||
max: number = Number.POSITIVE_INFINITY | ||
): Sequence { | ||
let { tValue } = permutation[0]; | ||
|
||
for (let i = 1; i < permutation.length; i++) { | ||
tValue = memoizedFindOverlap(tValue, permutation[i].tValue); | ||
|
||
if (tValue.length > max) { | ||
return null; | ||
} | ||
} | ||
|
||
return new Sequence(tValue, permutation); | ||
} | ||
|
||
toJSON(): SequenceJSON { | ||
return { | ||
value: this.value, | ||
parts: this.indexes, | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters