feat(ChangeDetector): implement enabling/disabling records

This commit is contained in:
vsavkin 2014-11-14 15:35:41 -08:00
parent 8dfbc242af
commit daf8f72b74
5 changed files with 238 additions and 26 deletions

View File

@ -1,6 +1,6 @@
import {ProtoWatchGroup, WatchGroup} from './watch_group'; import {ProtoWatchGroup, WatchGroup} from './watch_group';
import {ProtoRecord, Record} from './record'; import {ProtoRecord, Record} from './record';
import {FIELD, int} from 'facade/lang'; import {FIELD, int, isPresent} from 'facade/lang';
export * from './record'; export * from './record';
export * from './watch_group' export * from './watch_group'
@ -12,11 +12,10 @@ export class ChangeDetector {
} }
detectChanges():int { detectChanges():int {
var record:Record = this._rootWatchGroup.headRecord;
var count:int = 0; var count:int = 0;
for (record = this._rootWatchGroup.headRecord; for (var record = this._rootWatchGroup.headEnabledRecord;
record != null; isPresent(record);
record = record.next) { record = record.nextEnabled) {
if (record.check()) { if (record.check()) {
count++; count++;
} }

View File

@ -65,6 +65,12 @@ export class Record {
@FIELD('final protoRecord:ProtoRecord') @FIELD('final protoRecord:ProtoRecord')
@FIELD('next:Record') @FIELD('next:Record')
@FIELD('prev:Record') @FIELD('prev:Record')
/// This reference can change.
@FIELD('nextEnabled:Record')
/// This reference can change.
@FIELD('prevEnabled:Record')
@FIELD('dest:Record') @FIELD('dest:Record')
@FIELD('previousValue') @FIELD('previousValue')
@ -86,6 +92,9 @@ export class Record {
this.next = null; this.next = null;
this.prev = null; this.prev = null;
this.nextEnabled = null;
this.prevEnabled = null;
this.disabled = false;
this.dest = null; this.dest = null;
this.previousValue = null; this.previousValue = null;
@ -163,9 +172,11 @@ export class Record {
return FunctionWrapper.apply(this.context, this.args); return FunctionWrapper.apply(this.context, this.args);
case MODE_STATE_INVOKE_PURE_FUNCTION: case MODE_STATE_INVOKE_PURE_FUNCTION:
this.watchGroup.disableRecord(this);
return FunctionWrapper.apply(this.funcOrValue, this.args); return FunctionWrapper.apply(this.funcOrValue, this.args);
case MODE_STATE_CONST: case MODE_STATE_CONST:
this.watchGroup.disableRecord(this);
return this.funcOrValue; return this.funcOrValue;
case MODE_STATE_MARKER: case MODE_STATE_MARKER:
@ -184,10 +195,12 @@ export class Record {
updateArg(value, position:int) { updateArg(value, position:int) {
this.args[position] = value; this.args[position] = value;
this.watchGroup.enableRecord(this);
} }
updateContext(value) { updateContext(value) {
this.context = value; this.context = value;
this.watchGroup.enableRecord(this);
} }
} }

View File

@ -56,21 +56,11 @@ export class ProtoWatchGroup {
} }
_createRecords(watchGroup:WatchGroup, formatters:Map) { _createRecords(watchGroup:WatchGroup, formatters:Map) {
var tail, prevRecord; for (var proto = this.headRecord; proto != null; proto = proto.next) {
watchGroup.headRecord = tail = new Record(watchGroup, this.headRecord, formatters); var record = new Record(watchGroup, proto, formatters);
this.headRecord.recordInConstruction = watchGroup.headRecord; proto.recordInConstruction = record;
watchGroup.addRecord(record);
for (var proto = this.headRecord.next; proto != null; proto = proto.next) {
prevRecord = tail;
tail = new Record(watchGroup, proto, formatters);
proto.recordInConstruction = tail;
tail.prev = prevRecord;
prevRecord.next = tail;
} }
watchGroup.tailRecord = tail;
} }
_setDestination() { _setDestination() {
@ -95,9 +85,70 @@ export class WatchGroup {
this.dispatcher = dispatcher; this.dispatcher = dispatcher;
this.headRecord = null; this.headRecord = null;
this.tailRecord = null; this.tailRecord = null;
this.headEnabledRecord = null;
this.tailEnabledRecord = null;
this.context = null; this.context = null;
} }
addRecord(record:Record) {
if (isPresent(this.tailRecord)) {
this.tailRecord.next = record;
this.tailRecord.nextEnabled = record;
record.prev = this.tailRecord;
record.prevEnabled = this.tailRecord;
this.tailRecord = this.tailEnabledRecord = record;
} else {
this.headRecord = this.tailRecord = record;
this.headEnabledRecord = this.tailEnabledRecord = record;
}
}
disableRecord(record:Record) {
var prev = record.prevEnabled;
var next = record.nextEnabled;
record.disabled = true;
if (isPresent(prev)) {
prev.nextEnabled = next;
} else {
this.headEnabledRecord = next;
}
if (isPresent(next)) {
next.prevEnabled = prev;
} else {
this.tailEnabledRecord = prev;
}
}
enableRecord(record:Record) {
if (!record.disabled) return;
var prev = record.prev;
while (prev != null && prev.disabled) prev = prev.prev;
var next = record.next;
while (next != null && next.disabled) next = next.next;
record.disabled = false;
record.prevEnabled = prev;
record.nextEnabled = next;
if (isPresent(prev)) {
prev.nextEnabled = record;
} else {
this.headEnabledRecord = record;
}
if (isPresent(next)) {
next.prevEnabled = record;
} else {
this.tailEnabledRecord = record;
}
}
insertChildGroup(newChild:WatchGroup, insertAfter:WatchGroup) { insertChildGroup(newChild:WatchGroup, insertAfter:WatchGroup) {
throw 'not implemented'; throw 'not implemented';
} }

View File

@ -152,13 +152,36 @@ export function main() {
expect(executeWatch('m', '1 > 2 ? 1 : 2')).toEqual(['m=2']); expect(executeWatch('m', '1 > 2 ? 1 : 2')).toEqual(['m=2']);
}); });
it("should support formatters", () => { describe("formatters", () => {
var formatters = MapWrapper.createFromPairs([ it("should support formatters", () => {
["uppercase", (v) => v.toUpperCase()], var formatters = MapWrapper.createFromPairs([
["wrap", (v, before, after) => `${before}${v}${after}`] ['uppercase', (v) => v.toUpperCase()],
]); ['wrap', (v, before, after) => `${before}${v}${after}`]]);
expect(executeWatch('str', '"aBc" | uppercase', null, formatters)).toEqual(['str=ABC']); expect(executeWatch('str', '"aBc" | uppercase', null, formatters)).toEqual(['str=ABC']);
expect(executeWatch('str', '"b" | wrap:"a":"c"', null, formatters)).toEqual(['str=abc']); expect(executeWatch('str', '"b" | wrap:"a":"c"', null, formatters)).toEqual(['str=abc']);
});
it("should rerun formatters only when arguments change", () => {
var counter = 0;
var formatters = MapWrapper.createFromPairs([
['formatter', (_) => {counter += 1; return 'value'}]
]);
var person = new Person('Jim');
var c = createChangeDetector('formatter', 'name | formatter', person, formatters);
var cd = c['changeDetector'];
cd.detectChanges();
expect(counter).toEqual(1);
cd.detectChanges();
expect(counter).toEqual(1);
person.name = 'bob';
cd.detectChanges();
expect(counter).toEqual(2);
});
}); });
}); });
}); });

View File

@ -0,0 +1,126 @@
import {ddescribe, describe, it, iit, xit, expect} from 'test_lib/test_lib';
import {List, ListWrapper, MapWrapper} from 'facade/collection';
import {Parser} from 'change_detection/parser/parser';
import {Lexer} from 'change_detection/parser/lexer';
import {ClosureMap} from 'change_detection/parser/closure_map';
import {
ChangeDetector,
ProtoWatchGroup,
WatchGroup,
WatchGroupDispatcher,
ProtoRecord
} from 'change_detection/change_detector';
import {Record} from 'change_detection/record';
export function main() {
function createRecord(wg) {
return new Record(wg, new ProtoRecord(null, null, null, null, null), null);
}
describe('watch group', () => {
describe("adding records", () => {
it("should add a record", () => {
var wg = new WatchGroup(null, null);
var record = createRecord(wg);
wg.addRecord(record);
expect(wg.headRecord).toBe(record);
expect(wg.tailRecord).toBe(record);
expect(wg.headEnabledRecord).toBe(record);
expect(wg.tailEnabledRecord).toBe(record);
});
it("should add multiple records", () => {
var wg = new WatchGroup(null, null);
var record1 = createRecord(wg);
var record2 = createRecord(wg);
wg.addRecord(record1);
wg.addRecord(record2);
expect(wg.headRecord).toBe(record1);
expect(wg.tailRecord).toBe(record2);
expect(wg.headEnabledRecord).toBe(record1);
expect(wg.tailEnabledRecord).toBe(record2);
expect(record1.next).toBe(record2);
expect(record2.prev).toBe(record1);
});
});
describe("enabling/disabling records", () => {
it("should disable a single record", () => {
var wg = new WatchGroup(null, null);
var record = createRecord(wg);
wg.addRecord(record);
wg.disableRecord(record);
expect(wg.headEnabledRecord).toBeNull();
expect(wg.tailEnabledRecord).toBeNull();
});
it("should enable a single record", () => {
var wg = new WatchGroup(null, null);
var record = createRecord(wg);
wg.addRecord(record);
wg.disableRecord(record);
wg.enableRecord(record);
expect(wg.headEnabledRecord).toBe(record);
expect(wg.tailEnabledRecord).toBe(record);
});
it("should disable a record", () => {
var wg = new WatchGroup(null, null);
var record1 = createRecord(wg);
var record2 = createRecord(wg);
var record3 = createRecord(wg);
var record4 = createRecord(wg);
wg.addRecord(record1);
wg.addRecord(record2);
wg.addRecord(record3);
wg.addRecord(record4);
wg.disableRecord(record2);
wg.disableRecord(record3);
expect(record2.disabled).toBeTruthy();
expect(wg.headEnabledRecord).toBe(record1);
expect(wg.tailEnabledRecord).toBe(record4);
expect(record1.nextEnabled).toBe(record4);
expect(record4.prevEnabled).toBe(record1);
});
it("should enable a record", () => {
var wg = new WatchGroup(null, null);
var record1 = createRecord(wg);
var record2 = createRecord(wg);
var record3 = createRecord(wg);
var record4 = createRecord(wg);
wg.addRecord(record1);
wg.addRecord(record2);
wg.addRecord(record3);
wg.addRecord(record4);
wg.disableRecord(record2);
wg.disableRecord(record3);
wg.enableRecord(record2);
wg.enableRecord(record3);
expect(record1.nextEnabled).toBe(record2);
expect(record2.nextEnabled).toBe(record3);
expect(record3.nextEnabled).toBe(record4);
expect(record4.prevEnabled).toBe(record3);
});
})
});
}