Skip to content

Commit

Permalink
[BUGFIX beta] Autotrack Modifiers and Helpers
Browse files Browse the repository at this point in the history
This PR adds autotracking for modifiers and class based helpers.
Modifiers can opt out of autotracking via a new capabilities flag on
modifier managers.
  • Loading branch information
Chris Garrett committed Aug 14, 2019
1 parent e971418 commit 98b4aa9
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 13 deletions.
55 changes: 45 additions & 10 deletions packages/@ember/-internals/glimmer/lib/modifiers/custom.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { track } from '@ember/-internals/metal';
import { Factory } from '@ember/-internals/owner';
import { assert } from '@ember/debug';
import { Dict, Opaque, Simple } from '@glimmer/interfaces';
import { CONSTANT_TAG, Tag } from '@glimmer/reference';
import { combine, CONSTANT_TAG, createUpdatableTag, Tag, update } from '@glimmer/reference';
import { Arguments, CapturedArguments, ModifierManager } from '@glimmer/runtime';

export interface CustomModifierDefinitionState<ModifierInstance> {
Expand All @@ -9,11 +11,24 @@ export interface CustomModifierDefinitionState<ModifierInstance> {
delegate: ModifierManagerDelegate<ModifierInstance>;
}

export interface Capabilities {}
export interface OptionalCapabilities {
disableLifecycleTracking?: boolean;
}

export interface Capabilities {
disableLifecycleTracking: boolean;
}

// Currently there are no capabilities for modifiers
export function capabilities(_managerAPI: string, _optionalFeatures?: {}): Capabilities {
return {};
export function capabilities(
managerAPI: '3.13',
optionalFeatures: OptionalCapabilities
): Capabilities {
assert('Invalid modifier manager compatibility specified', managerAPI === '3.13');

return {
disableLifecycleTracking: Boolean(optionalFeatures.disableLifecycleTracking),
};
}

export class CustomModifierDefinition<ModifierInstance> {
Expand All @@ -39,6 +54,8 @@ export class CustomModifierDefinition<ModifierInstance> {
}

export class CustomModifierState<ModifierInstance> {
public tag = createUpdatableTag();

constructor(
public element: Simple.Element,
public delegate: ModifierManagerDelegate<ModifierInstance>,
Expand Down Expand Up @@ -109,18 +126,36 @@ class InteractiveCustomModifierManager<ModifierInstance>
return new CustomModifierState(element, definition.delegate, instance, capturedArgs);
}

getTag({ args }: CustomModifierState<ModifierInstance>): Tag {
return args.tag;
getTag({ args, tag }: CustomModifierState<ModifierInstance>): Tag {
return combine([tag, args.tag]);
}

install(state: CustomModifierState<ModifierInstance>) {
let { element, args, delegate, modifier } = state;
delegate.installModifier(modifier, element, args.value());
let { element, args, delegate, modifier, tag } = state;

let tracked = track(() => {
delegate.installModifier(modifier, element, args.value());
});

let { capabilities } = delegate;

if (capabilities === undefined || capabilities.disableLifecycleTracking !== true) {
update(tag, tracked);
}
}

update(state: CustomModifierState<ModifierInstance>) {
let { args, delegate, modifier } = state;
delegate.updateModifier(modifier, args.value());
let { args, delegate, modifier, tag } = state;

let tracked = track(() => {
delegate.updateModifier(modifier, args.value());
});

let { capabilities } = delegate;

if (capabilities === undefined || capabilities.disableLifecycleTracking !== true) {
update(tag, tracked);
}
}

getDestructor(state: CustomModifierState<ModifierInstance>) {
Expand Down
11 changes: 9 additions & 2 deletions packages/@ember/-internals/glimmer/lib/utils/references.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,16 +379,18 @@ export class ClassBasedHelperReference extends CachedReference {
return new ClassBasedHelperReference(instance, args);
}

private computeTag = createUpdatableTag();
public tag: Tag;

constructor(private instance: HelperInstance, private args: CapturedArguments) {
super();
this.tag = combine([instance[RECOMPUTE_TAG], args.tag]);
this.tag = combine([instance[RECOMPUTE_TAG], args.tag, this.computeTag]);
}

compute(): Opaque {
let {
instance,
computeTag,
args: { positional, named },
} = this;

Expand All @@ -400,7 +402,12 @@ export class ClassBasedHelperReference extends CachedReference {
debugFreeze(namedValue);
}

return instance.compute(positionalValue, namedValue);
let computedValue;
let trackedTag = track(() => (computedValue = instance.compute(positionalValue, namedValue)));

update(computeTag, trackedTag);

return computedValue;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { moduleFor, RenderingTestCase, runTask } from 'internal-test-helpers';

import { Object as EmberObject } from '@ember/-internals/runtime';
import { setModifierManager } from '@ember/-internals/glimmer';
import { set } from '@ember/-internals/metal';
import { set, tracked } from '@ember/-internals/metal';

class ModifierManagerTest extends RenderingTestCase {}

Expand Down Expand Up @@ -176,6 +176,64 @@ moduleFor(

runTask(() => set(this.context, 'truthy', 'true'));
}

'@test lifecycle hooks are autotracked by default'(assert) {
let TrackedClass = EmberObject.extend({
count: tracked({ value: 0 }),
});

let trackedOne = TrackedClass.create();
let trackedTwo = TrackedClass.create();

let insertCount = 0;
let updateCount = 0;

let ModifierClass = setModifierManager(
owner => {
return new CustomModifierManager(owner);
},
EmberObject.extend({
didInsertElement() {},
didUpdate() {},
willDestroyElement() {},
})
);

this.registerModifier(
'foo-bar',
ModifierClass.extend({
didInsertElement() {
// track the count of the first item
trackedOne.count;
insertCount++;
},

didUpdate() {
// track the count of the second item
trackedTwo.count;
updateCount++;
},
})
);

this.render('<h1 {{foo-bar truthy}}>hello world</h1>');
this.assertHTML(`<h1>hello world</h1>`);

assert.equal(insertCount, 1);
assert.equal(updateCount, 0);

runTask(() => trackedTwo.count++);
assert.equal(updateCount, 0);

runTask(() => trackedOne.count++);
assert.equal(updateCount, 1);

runTask(() => trackedOne.count++);
assert.equal(updateCount, 1);

runTask(() => trackedTwo.count++);
assert.equal(updateCount, 2);
}
}
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,46 @@ if (EMBER_METAL_TRACKED_PROPERTIES) {

assert.strictEqual(computeCount, 2, 'compute is called exactly 2 times');
}

'@test class based helpers are autotracked'(assert) {
let computeCount = 0;

let TrackedClass = EmberObject.extend({
value: tracked({ value: 'bob' }),
});

let trackedInstance = TrackedClass.create();

this.registerComponent('person', {
ComponentClass: Component.extend(),
template: strip`{{hello-world}}`,
});

this.registerHelper('hello-world', {
compute() {
computeCount++;
return `${trackedInstance.value}-value`;
},
});

this.render('<Person/>');

this.assertText('bob-value');

assert.strictEqual(computeCount, 1, 'compute is called exactly 1 time');

runTask(() => this.rerender());

this.assertText('bob-value');

assert.strictEqual(computeCount, 1, 'compute is called exactly 1 time');

runTask(() => (trackedInstance.value = 'sal'));

this.assertText('sal-value');

assert.strictEqual(computeCount, 2, 'compute is called exactly 2 times');
}
}
);
}

0 comments on commit 98b4aa9

Please sign in to comment.