182 lines
5.4 KiB
JavaScript
Raw Normal View History

2014-09-28 20:02:32 -07:00
//import {ProtoWatchGroup, WatchGroup} from './watch_group';
2014-09-26 11:20:08 -07:00
export class ProtoRecord {
@FIELD('final watchGroup:ProtoWatchGroup')
@FIELD('final fieldName:String')
/// order list of all records. Including head/tail markers
@FIELD('next:ProtoRecord')
@FIELD('prev:ProtoRecord')
/// next record to dirty check
@FIELD('_checkNext:ProtoRecord')
@FIELD('_checkPrev:ProtoRecord')
// next notifier
@FIELD('_notifierNext:ProtoRecord')
// Opeque data which will be presented to WatchGroupDispatcher
@FIELD('dispatcherContext')
// IF we detect change, we have to update the _context of the
// next record.
@FIELD('_updateContext:ProtoRecord')
// May be removed if we don't support coelsence.
@FIELD('_updateContextNext:ProtoRecord')
@FIELD('_clone')
2014-09-28 20:02:32 -07:00
constructor(watchGroup/*:ProtoWatchGroup*/, fieldName:String) {
2014-09-26 11:20:08 -07:00
this.watchGroup = watchGroup;
this.fieldName = fieldName;
2014-09-28 16:29:11 -07:00
this.next = null;
this.prev = null;
2014-09-26 11:20:08 -07:00
this._checkNext = null;
this._checkPrev = null;
this._notifierNext = null;
this.dispatcherContext = null;
this._updateContext = null;
this._updateContextNext = null;
this._clone = null;
}
2014-09-28 20:02:32 -07:00
instantiate(watchGroup/*:WatchGroup*/):Record {
2014-09-26 11:20:08 -07:00
var record = this._clone = new Record(watchGroup, this);
2014-09-28 16:29:11 -07:00
record.prev = this.prev._clone;
2014-09-26 11:20:08 -07:00
record._checkPrev = this._checkPrev._clone;
return _clone;
}
instantiateComplete():Record {
var record = this._clone;
2014-09-28 16:29:11 -07:00
record.next = this.next._clone;
2014-09-26 11:20:08 -07:00
record._checkNext = this._checkNext._clone;
this._clone = null;
2014-09-28 16:29:11 -07:00
return this.next;
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
* and current value.
*
* 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.
*
* DESIGN NOTES:
* - No inheritance allowed so that code is monomorphic for performance.
* - Atomic watch operations
* - Defaults to dirty checking
* - Keep this object as lean as possible. (Lean in number of fields)
*
* MEMORY COST: 13 Words;
*/
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')
/// next record to dirty check
@FIELD('_checkNext:Record')
@FIELD('_checkPrev:Record')
// next notifier
@FIELD('_notifierNext:Record')
// notifier context will be present to the notifier to release
// the object from notification/watching.
@FIELD('_notifierContext')
// IF we detect change, we have to update the _context of the
// next record.
@FIELD('_updateContext:Record')
// May be removed if we don't support coelsence.
@FIELD('_updateContextNext:Record')
2014-09-26 11:20:08 -07:00
@FIELD('_mode:int')
@FIELD('_context')
@FIELD('_getter')
@FIELD('_arguments')
@FIELD('currentValue')
@FIELD('previousValue')
2014-09-28 20:02:32 -07:00
constructor(watchGroup/*: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-09-26 11:20:08 -07:00
this._checkNext = null;
this._checkPrev = null;
this._notifierNext = null;
this._notifierContext = null;
this._updateContext = null;
this._updateContextNext = null;
this._mode = MODE_STATE_MARKER;
this._context = null;
this._getter = null;
this._arguments = null;
this.currentValue = null;
this.previousValue = null;
2014-09-25 16:53:32 -07:00
}
check():bool {
2014-09-26 11:20:08 -07:00
var mode = this._mode;
2014-09-25 16:53:32 -07:00
var state = mode & MODE_MASK_STATE;
var notify = mode & MODE_MASK_NOTIFY;
var currentValue;
switch (state) {
case MODE_STATE_MARKER:
return false;
case MODE_STATE_PROPERTY:
currentValue = this._getter(this._context);
break;
case MODE_STATE_INVOKE_CLOSURE:
currentValue = this._context(this._arguments);
break;
case MODE_STATE_INVOKE_METHOD:
currentValue = this._getter(this._context, this._arguments);
break;
case MODE_STATE_MAP:
case MODE_STATE_LIST:
}
2014-09-26 11:20:08 -07:00
var previousValue = this.previousValue;
2014-09-25 16:53:32 -07:00
if (isSame(previousValue, currentValue)) return false;
if (previousValue instanceof String && currentValue instanceof String
&& previousValue == currentValue) {
2014-09-26 11:20:08 -07:00
this.previousValue = currentValue;
2014-09-25 16:53:32 -07:00
return false
}
2014-09-26 11:20:08 -07:00
this.previousValue = currentValue;
this.watchGroup.dispatcher.onRecordChange(this, this.protoRecord.dispatcherContext);
2014-09-25 16:53:32 -07:00
return true;
}
}
// The mode is devided into two partes. Which notification mechanism
// 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) {
if (a === b) {
return true;
} else if ((a !== a) && (b !== b)) {
return true;
} else {
return false;
}
}