Skip to content

Commit

Permalink
add tag info for ember-inspector
Browse files Browse the repository at this point in the history
  • Loading branch information
patricklx authored and NullVoxPopuli committed Oct 19, 2024
1 parent 34888e9 commit fe4a1af
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 4 deletions.
3 changes: 2 additions & 1 deletion packages/@glimmer/validator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ if (globalObj[GLIMMER_VALIDATOR_REGISTRATION] === true) {
globalObj[GLIMMER_VALIDATOR_REGISTRATION] = true;

export { debug } from './lib/debug';
export { dirtyTagFor, tagFor, type TagMeta, tagMetaFor } from './lib/meta';
export { dirtyTagFor, infoForTag, tagFor, type TagMeta, tagMetaFor } from './lib/meta';
export { trackedData } from './lib/tracked-data';
export * from './lib/tracked-utils';
export {
beginTrackFrame,
beginUntrackFrame,
Expand Down
8 changes: 7 additions & 1 deletion packages/@glimmer/validator/lib/meta.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { ConstantTag, UpdatableTag } from '@glimmer/interfaces';
import type { ConstantTag, Tag, UpdatableTag } from '@glimmer/interfaces';

import type { Indexable } from './utils';
import type { MonomorphicTagImpl } from './validators';

import { debug } from './debug';
import { unwrap } from './utils';
Expand Down Expand Up @@ -64,8 +65,13 @@ export function tagFor<T extends object>(

if (tag === undefined) {
tag = createUpdatableTag();
(tag as MonomorphicTagImpl).meta = { propertyKey: key, object: obj };
tags.set(key, tag);
}

return tag;
}

export function infoForTag(tag: Tag) {
return (tag as MonomorphicTagImpl).meta;
}
80 changes: 80 additions & 0 deletions packages/@glimmer/validator/lib/tracked-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import type { Tag } from '@glimmer/interfaces';

import type { MonomorphicTagImpl } from './validators';

import { infoForTag, tagFor } from './meta';
import { track } from './tracking';
import { validateTag, valueForTag } from './validators';

type Info = {
tag: MonomorphicTagImpl;
prevValue: number;
dependencies: {
object: object;
propertyKey: string;
changed: boolean;
}[];
};

export function getTrackedDependencies(obj: Record<string, any>, property: string, info?: Info) {
info = info || ({} as Info);
const tag = info?.tag || track(() => obj[property]);
const dependencies = [];
// do not include tracked properties from dependencies

const subtags = (Array.isArray(tag.subtag) ? [tag, ...tag.subtag] : [tag, tag.subtag]).filter(
(t) => !!t
) as Tag[];
for (const subtag of subtags) {
if (subtag === tag) continue;
dependencies.push({ ...infoForTag(subtag), tag: subtag });
if (subtag.subtag && !Array.isArray(subtag.subtag)) {
dependencies.push({ ...infoForTag(subtag.subtag) });
}
}

let maxRevision = valueForTag(tag);

const hasChange = (info.prevValue && maxRevision !== info.prevValue) || false;
let latestValue = info.prevValue || 0;

info.dependencies = dependencies.map((t) => {
if (t.tag.lastValue > latestValue) {
latestValue = t.tag.lastValue;
}
const changed = hasChange && t.tag.lastValue > info!.prevValue;
return { object: t.object, propertyKey: t.propertyKey, changed };
});

info.prevValue = maxRevision;

return info;
}

type TrackedInfo = {
changed: string[];
propertyInfo: Record<string, any>;
};

export function getChangedProperties(obj: object, trackedInfo?: TrackedInfo) {
trackedInfo = trackedInfo || ({} as TrackedInfo);
trackedInfo['changed'] = [];
trackedInfo.propertyInfo = trackedInfo.propertyInfo || {};
for (const name in obj) {
const tag = tagFor(obj, name);
const revision = valueForTag(tag);
let tagInfo = trackedInfo.propertyInfo?.[name] || {
tag: tag,
revision,
};
if (!tagInfo.tag) return;
trackedInfo.propertyInfo[name] = tagInfo;

const changed = !validateTag(tagInfo.tag, tagInfo.revision);
tagInfo.revision = revision;
if (changed) {
trackedInfo['changed'].push(name);
}
}
return trackedInfo;
}
3 changes: 2 additions & 1 deletion packages/@glimmer/validator/lib/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ function allowsCycles(tag: Tag): boolean {
}
}

class MonomorphicTagImpl<T extends MonomorphicTagId = MonomorphicTagId> {
export class MonomorphicTagImpl<T extends MonomorphicTagId = MonomorphicTagId> {
static combine(this: void, tags: Tag[]): Tag {
switch (tags.length) {
case 0:
Expand All @@ -112,6 +112,7 @@ class MonomorphicTagImpl<T extends MonomorphicTagId = MonomorphicTagId> {
private isUpdating = false;
public subtag: Tag | Tag[] | null = null;
private subtagBufferCache: Revision | null = null;
public meta: any = null;

[TYPE]: T;

Expand Down
11 changes: 10 additions & 1 deletion packages/@glimmer/validator/test/meta-test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { dirtyTagFor, tagFor, validateTag, valueForTag } from '@glimmer/validator';
import { dirtyTagFor, infoForTag, tagFor, validateTag, valueForTag } from '@glimmer/validator';

import { module, test } from './-utils';

Expand All @@ -18,4 +18,13 @@ module('@glimmer/validator: meta', () => {

assert.notOk(validateTag(tag, snapshot));
});

test('it can provide the object and property for the tag given object', (assert) => {
let obj = {};
let tag = tagFor(obj, 'foo');

let info = infoForTag(tag)!;
assert.strictEqual(info.object, obj);
assert.strictEqual(info.propertyKey, 'foo');
});
});
99 changes: 99 additions & 0 deletions packages/@glimmer/validator/test/tracked-utils-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { getChangedProperties, getTrackedDependencies, trackedData } from '@glimmer/validator';

import { module, test } from './-utils';

module('@glimmer/validator: tracked-utils', () => {
class TestObject {
declare item1: string;
declare item2: string;
item3 = '';
constructor() {}

get getterWithTracked() {
return this.item1 + ' world' + this.item2;
}
}

{
const { getter, setter } = trackedData<TestObject, 'item1'>('item1', () => '');
Object.defineProperty(TestObject.prototype, 'item1', {
enumerable: true,
get(this) {
return getter(this);
},
set(this, v) {
return setter(this, v);
},
});
}
{
const { getter, setter } = trackedData<TestObject, 'item2'>('item2', () => '');
Object.defineProperty(TestObject.prototype, 'item2', {
enumerable: true,
get(this) {
return getter(this);
},
set(this, v) {
return setter(this, v);
},
});
}

test('it can detect changed properties', (assert) => {
const obj = new TestObject();
let trackedInfo = getChangedProperties(obj);
assert.deepEqual(trackedInfo?.changed, []);

obj.item1 = 'hello';

assert.deepEqual(getChangedProperties(obj, trackedInfo)?.changed, ['item1']);
assert.deepEqual(getChangedProperties(obj, trackedInfo)?.changed, []);

obj.item1 = 'hi';
obj.item2 = 'hi';
assert.deepEqual(getChangedProperties(obj, trackedInfo)?.changed, ['item1', 'item2']);
});

test('it can detect tracked dependencies', (assert) => {
const obj = new TestObject();
let info = getTrackedDependencies(obj, 'getterWithTracked');
assert.deepEqual(info.dependencies, [
{
changed: false,
object: obj,
propertyKey: 'item1',
},
{
changed: false,
object: obj,
propertyKey: 'item2',
},
]);

obj.item1 = 'hi';
assert.deepEqual(getTrackedDependencies(obj, 'getterWithTracked', info).dependencies, [
{
changed: true,
object: obj,
propertyKey: 'item1',
},
{
changed: false,
object: obj,
propertyKey: 'item2',
},
]);
assert.deepEqual(getTrackedDependencies(obj, 'getterWithTracked', info).dependencies, [
{
changed: false,
object: obj,
propertyKey: 'item1',
},
{
changed: false,
object: obj,
propertyKey: 'item2',
},
]);
});
});

0 comments on commit fe4a1af

Please sign in to comment.