2014-10-02 15:14:32 +02:00
|
|
|
import {ProtoWatchGroup, WatchGroup} from './watch_group';
|
2014-09-28 13:55:01 -07:00
|
|
|
import {FIELD} from 'facade/lang';
|
2014-10-02 15:14:32 +02:00
|
|
|
import {FieldGetterFactory} from './facade';
|
2014-09-26 11:20:08 -07:00
|
|
|
|
2014-09-30 15:50:20 -07:00
|
|
|
/**
|
2014-10-02 15:14:32 +02:00
|
|
|
* For now we are dropping expression coalescence. We can always add it later, but
|
|
|
|
* real world numbers show that it does not provide significant benefits.
|
2014-09-30 15:50:20 -07:00
|
|
|
*/
|
2014-09-26 11:20:08 -07:00
|
|
|
export class ProtoRecord {
|
2014-09-30 15:50:20 -07:00
|
|
|
@FIELD('final watchGroup:wg.ProtoWatchGroup')
|
2014-09-26 11:20:08 -07:00
|
|
|
@FIELD('final fieldName:String')
|
|
|
|
/// order list of all records. Including head/tail markers
|
|
|
|
@FIELD('next:ProtoRecord')
|
|
|
|
@FIELD('prev:ProtoRecord')
|
2014-10-02 15:14:32 +02:00
|
|
|
// Opaque data which will be the target of notification.
|
|
|
|
// If the object is instance of Record, than it it is directly processed
|
2014-09-30 15:50:20 -07:00
|
|
|
// Otherwise it is the context used by WatchGroupDispatcher.
|
|
|
|
@FIELD('memento')
|
2014-10-02 15:14:32 +02:00
|
|
|
constructor(watchGroup:ProtoWatchGroup, fieldName:string, dispatchMemento) {
|
2014-09-26 11:20:08 -07:00
|
|
|
this.watchGroup = watchGroup;
|
|
|
|
this.fieldName = fieldName;
|
2014-10-02 15:14:32 +02:00
|
|
|
this.dispatchMemento = dispatchMemento;
|
2014-09-28 16:29:11 -07:00
|
|
|
this.next = null;
|
|
|
|
this.prev = null;
|
2014-10-02 12:27:01 -07:00
|
|
|
}
|
2014-09-26 11:20:08 -07:00
|
|
|
}
|
2014-09-25 16:53:32 -07:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Represents a Record for keeping track of changes. A change is a difference between previous
|
2014-09-28 13:55:01 -07:00
|
|
|
* and current value.
|
|
|
|
*
|
2014-09-25 16:53:32 -07:00
|
|
|
* By default changes are detected using dirty checking, but a notifier can be present which can
|
|
|
|
* notify the records of changes by means other than dirty checking. For example Object.observe
|
|
|
|
* or events on DOM elements.
|
2014-09-28 13:55:01 -07:00
|
|
|
*
|
|
|
|
* DESIGN NOTES:
|
|
|
|
* - No inheritance allowed so that code is monomorphic for performance.
|
2014-09-25 16:53:32 -07:00
|
|
|
* - Atomic watch operations
|
|
|
|
* - Defaults to dirty checking
|
|
|
|
* - Keep this object as lean as possible. (Lean in number of fields)
|
|
|
|
*/
|
2014-09-19 16:38:37 -07:00
|
|
|
export class Record {
|
2014-09-26 11:20:08 -07:00
|
|
|
@FIELD('final watchGroup:WatchGroup')
|
|
|
|
@FIELD('final protoRecord:ProtoRecord')
|
2014-09-25 16:53:32 -07:00
|
|
|
/// order list of all records. Including head/tail markers
|
|
|
|
@FIELD('_next:Record')
|
|
|
|
@FIELD('_prev:Record')
|
2014-09-28 13:55:01 -07:00
|
|
|
/// next record to dirty check
|
2014-09-25 16:53:32 -07:00
|
|
|
@FIELD('_checkNext:Record')
|
|
|
|
@FIELD('_checkPrev:Record')
|
|
|
|
// next notifier
|
|
|
|
@FIELD('_notifierNext:Record')
|
2014-09-26 11:20:08 -07:00
|
|
|
|
|
|
|
@FIELD('_mode:int')
|
|
|
|
@FIELD('_context')
|
|
|
|
@FIELD('_getter')
|
|
|
|
@FIELD('_arguments')
|
|
|
|
@FIELD('previousValue')
|
2014-09-30 15:50:20 -07:00
|
|
|
constructor(watchGroup/*:wg.WatchGroup*/, protoRecord:ProtoRecord) {
|
2014-09-26 11:20:08 -07:00
|
|
|
this.protoRecord = protoRecord;
|
|
|
|
this.watchGroup = watchGroup;
|
2014-09-28 16:29:11 -07:00
|
|
|
this.next = null;
|
|
|
|
this.prev = null;
|
2014-10-02 15:14:32 +02:00
|
|
|
this.checkNext = null;
|
|
|
|
this.checkPrev = null;
|
|
|
|
this.notifierNext = null;
|
|
|
|
|
|
|
|
this.mode = MODE_STATE_MARKER;
|
|
|
|
this.context = null;
|
|
|
|
this.getter = null;
|
|
|
|
this.arguments = null;
|
2014-09-26 11:20:08 -07:00
|
|
|
this.previousValue = null;
|
2014-10-02 15:14:32 +02:00
|
|
|
this.currentValue = null;
|
2014-09-25 16:53:32 -07:00
|
|
|
}
|
|
|
|
|
2014-10-02 15:14:32 +02:00
|
|
|
check():boolean {
|
|
|
|
var mode = this.mode;
|
2014-09-25 16:53:32 -07:00
|
|
|
var state = mode & MODE_MASK_STATE;
|
|
|
|
var notify = mode & MODE_MASK_NOTIFY;
|
2014-10-02 15:14:32 +02:00
|
|
|
var newValue;
|
2014-09-25 16:53:32 -07:00
|
|
|
switch (state) {
|
2014-09-28 13:55:01 -07:00
|
|
|
case MODE_STATE_MARKER:
|
2014-09-25 16:53:32 -07:00
|
|
|
return false;
|
|
|
|
case MODE_STATE_PROPERTY:
|
2014-10-02 15:14:32 +02:00
|
|
|
newValue = this.getter(this.context);
|
2014-09-25 16:53:32 -07:00
|
|
|
break;
|
|
|
|
case MODE_STATE_INVOKE_CLOSURE:
|
2014-10-02 15:14:32 +02:00
|
|
|
newValue = this.context(this.arguments);
|
2014-09-25 16:53:32 -07:00
|
|
|
break;
|
|
|
|
case MODE_STATE_INVOKE_METHOD:
|
2014-10-02 15:14:32 +02:00
|
|
|
newValue = this.getter(this.context, this.arguments);
|
2014-09-25 16:53:32 -07:00
|
|
|
break;
|
|
|
|
case MODE_STATE_MAP:
|
2014-10-02 15:14:32 +02:00
|
|
|
throw 'not implemented';
|
2014-09-25 16:53:32 -07:00
|
|
|
case MODE_STATE_LIST:
|
2014-10-02 15:14:32 +02:00
|
|
|
throw 'not implemented';
|
|
|
|
default:
|
|
|
|
throw 'not implemented';
|
2014-09-25 16:53:32 -07:00
|
|
|
}
|
2014-10-02 15:14:32 +02:00
|
|
|
|
|
|
|
|
|
|
|
var previousValue = this.currentValue;
|
|
|
|
if (previousValue === this) {
|
|
|
|
// When the record is checked for the first time we should always notify
|
|
|
|
this.currentValue = newValue;
|
|
|
|
this.previousValue = previousValue = null;
|
|
|
|
} else {
|
|
|
|
this.currentValue = newValue;
|
|
|
|
this.previousValue = previousValue;
|
|
|
|
|
|
|
|
if (isSame(previousValue, newValue)) return false;
|
|
|
|
|
|
|
|
// In Dart, we can have `str1 !== str2` but `str1 == str2`
|
|
|
|
if (previousValue instanceof String &&
|
|
|
|
newValue instanceof String &&
|
|
|
|
previousValue == newValue) {
|
|
|
|
return false
|
|
|
|
}
|
2014-09-25 16:53:32 -07:00
|
|
|
}
|
2014-10-02 15:14:32 +02:00
|
|
|
|
|
|
|
|
|
|
|
// todo(vicb): compute this info only once in ctor ? (add a bit in mode not to grow the mem req)
|
|
|
|
if (this.protoRecord.dispatchMemento === null) {
|
|
|
|
// forward propagate to the next record
|
2014-09-30 15:50:20 -07:00
|
|
|
} else {
|
2014-10-02 15:14:32 +02:00
|
|
|
// notify through dispatcher
|
|
|
|
this.watchGroup.dispatcher.onRecordChange(this, this.protoRecord.dispatchMemento);
|
2014-09-30 15:50:20 -07:00
|
|
|
}
|
2014-10-02 15:14:32 +02:00
|
|
|
|
2014-09-25 16:53:32 -07:00
|
|
|
return true;
|
|
|
|
}
|
2014-10-02 15:14:32 +02:00
|
|
|
|
|
|
|
setContext(context) {
|
|
|
|
// use `this` as a marker for a fresh record
|
|
|
|
this.currentValue = this;
|
|
|
|
this.mode = MODE_STATE_PROPERTY;
|
|
|
|
this.context = context;
|
|
|
|
var factory = new FieldGetterFactory();
|
|
|
|
this.getter = factory.getter(context, this.protoRecord.fieldName);
|
|
|
|
}
|
|
|
|
|
2014-09-25 16:53:32 -07:00
|
|
|
}
|
|
|
|
|
2014-10-02 15:14:32 +02:00
|
|
|
// The mode is divided into two parts. Which notification mechanism
|
2014-09-25 16:53:32 -07:00
|
|
|
// to use and which dereference mode to execute.
|
|
|
|
|
|
|
|
// We use dirty checking aka no notification
|
2014-09-26 11:20:08 -07:00
|
|
|
const MODE_MASK_NOTIFY = 0xFF00;
|
2014-09-25 16:53:32 -07:00
|
|
|
// Encodes the state of dereference
|
2014-09-26 11:20:08 -07:00
|
|
|
const MODE_MASK_STATE = 0x00FF;
|
2014-09-25 16:53:32 -07:00
|
|
|
|
2014-09-26 11:20:08 -07:00
|
|
|
const MODE_PLUGIN_DIRTY_CHECK = 0x0000;
|
|
|
|
const MODE_STATE_MARKER = 0x0000;
|
2014-09-25 16:53:32 -07:00
|
|
|
|
|
|
|
/// _context[_protoRecord.propname] => _getter(_context)
|
2014-09-26 11:20:08 -07:00
|
|
|
const MODE_STATE_PROPERTY = 0x0001;
|
2014-09-25 16:53:32 -07:00
|
|
|
/// _context(_arguments)
|
2014-09-26 11:20:08 -07:00
|
|
|
const MODE_STATE_INVOKE_CLOSURE = 0x0002;
|
2014-09-25 16:53:32 -07:00
|
|
|
/// _getter(_context, _arguments)
|
2014-09-26 11:20:08 -07:00
|
|
|
const MODE_STATE_INVOKE_METHOD = 0x0003;
|
2014-09-25 16:53:32 -07:00
|
|
|
|
|
|
|
/// _context is Map => _previousValue is MapChangeRecord
|
2014-09-26 11:20:08 -07:00
|
|
|
const MODE_STATE_MAP = 0x0004;
|
2014-09-25 16:53:32 -07:00
|
|
|
/// _context is Array/List/Iterable => _previousValue = ListChangeRecord
|
2014-09-26 11:20:08 -07:00
|
|
|
const MODE_STATE_LIST = 0x0005;
|
2014-09-25 16:53:32 -07:00
|
|
|
|
|
|
|
function isSame(a, b) {
|
2014-10-02 15:14:32 +02:00
|
|
|
if (a === b) return true;
|
|
|
|
if ((a !== a) && (b !== b)) return true;
|
|
|
|
return false;
|
2014-09-28 13:55:01 -07:00
|
|
|
}
|