Skip to content

Commit

Permalink
feat(context): index bindings by tag to speed up matching by tag
Browse files Browse the repository at this point in the history
Fixes #4356
  • Loading branch information
raymondfeng committed Jan 10, 2020
1 parent cb1f10c commit 98c881c
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 7 deletions.
80 changes: 79 additions & 1 deletion packages/context/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,19 @@ export class Context extends EventEmitter {
*/
protected readonly registry: Map<string, Binding> = new Map();

/**
* Index for bindings by tag names
*/
protected readonly bindingsIndexedByTag: Map<
string,
Set<Readonly<Binding<unknown>>>
> = new Map();

/**
* A listener for binding events
*/
private bindingEventListener: (binding: Binding<unknown>, op: string) => void;

/**
* Parent context
*/
Expand Down Expand Up @@ -134,6 +147,11 @@ export class Context extends EventEmitter {
}
this._parent = _parent;
this.name = name ?? uuidv1();
this.bindingEventListener = (binding: Binding<unknown>, op: string) => {
if (op === 'tag') {
this.updateTagIndexForBinding(binding);
}
};
}

/**
Expand Down Expand Up @@ -358,7 +376,10 @@ export class Context extends EventEmitter {
if (existingBinding !== binding) {
if (existingBinding != null) {
this.emit('unbind', existingBinding, this);
this.removeTagIndexForBinding(existingBinding);
}
this.updateTagIndexForBinding(binding);
binding.on('changed', this.bindingEventListener);
this.emit('bind', binding, this);
}
return this;
Expand Down Expand Up @@ -505,6 +526,8 @@ export class Context extends EventEmitter {
if (binding?.isLocked)
throw new Error(`Cannot unbind key "${key}" of a locked binding`);
this.registry.delete(key);
binding.removeListener('changed', this.bindingEventListener);
this.removeTagIndexForBinding(binding);
this.emit('unbind', binding, this);
return true;
}
Expand Down Expand Up @@ -607,6 +630,32 @@ export class Context extends EventEmitter {
}
}

/**
* Remove tag index for the given binding
* @param binding - Binding object
*/
private removeTagIndexForBinding(binding: Readonly<Binding<unknown>>) {
for (const [, bindings] of this.bindingsIndexedByTag) {
bindings.delete(binding);
}
}

/**
* Update tag index for the given binding
* @param binding - Binding object
*/
private updateTagIndexForBinding(binding: Readonly<Binding<unknown>>) {
this.removeTagIndexForBinding(binding);
for (const tag of binding.tagNames) {
let bindings = this.bindingsIndexedByTag.get(tag);
if (bindings == null) {
bindings = new Set();
this.bindingsIndexedByTag.set(tag, bindings);
}
bindings.add(binding);
}
}

/**
* Check if a binding exists with the given key in the local context without
* delegating to the parent context
Expand Down Expand Up @@ -686,7 +735,36 @@ export class Context extends EventEmitter {
findByTag<ValueType = BoundValue>(
tagFilter: BindingTag | RegExp,
): Readonly<Binding<ValueType>>[] {
return this.find(filterByTag(tagFilter));
if (
tagFilter instanceof RegExp ||
(typeof tagFilter === 'string' &&
(tagFilter.includes('*') || tagFilter.includes('?')))
) {
return this.find(filterByTag(tagFilter));
}
return this._findByTagIndex(tagFilter);
}

protected _findByTagIndex<ValueType = BoundValue>(
tag: BindingTag,
): Readonly<Binding<ValueType>>[] {
let tagMap: Record<string, unknown>;
if (typeof tag === 'string') {
tagMap = {[tag]: tag};
} else {
tagMap = tag;
}
const bindings: Set<Readonly<Binding<ValueType>>> = new Set();
for (const t of Object.keys(tagMap)) {
const bindingsByTag = this.bindingsIndexedByTag.get(t);
if (bindingsByTag == null) continue;
bindingsByTag.forEach(b => {
if (filterByTag(tag)(b)) bindings.add(b as Binding<ValueType>);
});
}
const currentBindings = Array.from(bindings);
const parentBindings = this._parent && this._parent?._findByTagIndex(tag);
return this._mergeWithParent(currentBindings, parentBindings);
}

protected _mergeWithParent<ValueType>(
Expand Down
13 changes: 7 additions & 6 deletions packages/context/src/interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import assert from 'assert';
import debugFactory from 'debug';
import {Binding, BindingTemplate} from './binding';
import {bind} from './binding-decorator';
import {filterByTag} from './binding-filter';
import {BindingSpec} from './binding-inspector';
import {sortBindingsByPhase} from './binding-sorter';
import {Context} from './context';
Expand Down Expand Up @@ -47,12 +46,14 @@ export class InterceptedInvocationContext extends InvocationContext {
* ContextTags.GLOBAL_INTERCEPTOR)
*/
getGlobalInterceptorBindingKeys(): string[] {
const bindings: Readonly<Binding<Interceptor>>[] = this.find(
binding =>
filterByTag(ContextTags.GLOBAL_INTERCEPTOR)(binding) &&
// Only include interceptors that match the source type of the invocation
this.applicableTo(binding),
let bindings: Readonly<Binding<Interceptor>>[] = this.findByTag(
ContextTags.GLOBAL_INTERCEPTOR,
);
bindings = bindings.filter(binding =>
// Only include interceptors that match the source type of the invocation
this.applicableTo(binding),
);

this.sortGlobalInterceptorBindings(bindings);
const keys = bindings.map(b => b.key);
debug('Global interceptor binding keys:', keys);
Expand Down

0 comments on commit 98c881c

Please sign in to comment.