Skip to content

Commit

Permalink
Implemented fastArray and peek, see #69
Browse files Browse the repository at this point in the history
  • Loading branch information
mweststrate committed Dec 14, 2015
1 parent 94151c2 commit 3530aed
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 35 deletions.
6 changes: 5 additions & 1 deletion src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ export function map<V>(initialValues?: KeyValueMap<V>, valueModifier?: Function)
return new ObservableMap(initialValues, valueModifier);
}

export function fastArray<V>(initialValues?: V[]): IObservableArray<V> {
return createObservableArray(initialValues, ValueMode.Flat, false, null);
}

/**
* Can be used in combination with makeReactive / extendReactive.
* Enforces that a reference to 'value' is stored as property,
Expand Down Expand Up @@ -554,7 +558,7 @@ export function makeChildObservable(value, parentMode:ValueMode, context) {
}

if (Array.isArray(value))
return createObservableArray(<[]> value.slice(), childMode, context);
return createObservableArray(<[]> value, childMode, true, context);
if (isPlainObject(value))
return extendObservableHelper(value, value, childMode, context);
return value;
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export {
// deprecated, add warning?
isObservable as isReactive,
map,
fastArray,
observable as makeReactive,
extendObservable as extendReactive,
autorunUntil as observeUntil,
Expand Down
1 change: 1 addition & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface IObservableArray<T> extends IObservable, Array<T> {
spliceWithArray(index: number, deleteCount?: number, newItems?: T[]): T[];
observe(listener: (changeData: IArrayChange<T>|IArraySplice<T>)=>void, fireImmediately?: boolean): Lambda;
clear(): T[];
peek(): T[];
replace(newItems: T[]): T[];
find(predicate: (item: T,index: number,array: IObservableArray<T>)=>boolean,thisArg?: any,fromIndex?: number): T;
remove(value: T): boolean;
Expand Down
40 changes: 25 additions & 15 deletions src/observablearray.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ StubArray.prototype = [];
export class ObservableArrayAdministration<T> extends DataNode {
values: T[];
changeEvent: SimpleEventEmitter;
lastKnownLength = 0;

constructor(private array: ObservableArray<T>, public mode:ValueMode, context: IContextInfoStruct) {
constructor(public array: ObservableArray<T>, public mode:ValueMode, public supportEnumerable:boolean, context: IContextInfoStruct) {
super(context ? context : { name: undefined, object: undefined });
if (!this.context.object)
this.context.object = array;
Expand All @@ -45,18 +46,22 @@ export class ObservableArrayAdministration<T> extends DataNode {

// adds / removes the necessary numeric properties to this object
updateLength(oldLength:number, delta:number) {
if (oldLength !== this.lastKnownLength)
throw new Error("[mobservable] Modification exception: the internal structure of an observable array was changed. Did you use peek() to change it?");
this.lastKnownLength += delta;
if (delta < 0) {
//checkIfStateIsBeingModifiedDuringView(this.context);
//for(var i = oldLength + delta; i < oldLength; i++)
// delete this.array[i]; // bit faster but mem inefficient:
//Object.defineProperty(this, <string><any> i, notEnumerableProp);
checkIfStateIsBeingModifiedDuringView(this.context);
if (this.supportEnumerable)
for(var i = oldLength + delta; i < oldLength; i++)
delete this.array[i]; // bit faster but mem inefficient:
} else if (delta > 0) {
//checkIfStateIsBeingModifiedDuringView(this.context);
checkIfStateIsBeingModifiedDuringView(this.context);
if (oldLength + delta > OBSERVABLE_ARRAY_BUFFER_SIZE)
reserveArrayBuffer(oldLength + delta);
// funny enough, this is faster than slicing ENUMERABLE_PROPS into defineProperties, and faster as a temporarily map
//for (var i = oldLength, end = oldLength + delta; i < end; i++)
// Object.defineProperty(this.array, <string><any> i, ENUMERABLE_PROPS[i])
if (this.supportEnumerable)
for (var i = oldLength, end = oldLength + delta; i < end; i++)
Object.defineProperty(this.array, <string><any> i, ENUMERABLE_PROPS[i])
}
}

Expand Down Expand Up @@ -94,10 +99,10 @@ export class ObservableArrayAdministration<T> extends DataNode {

makeReactiveArrayItem(value) {
assertUnwrapped(value, "Array values cannot have modifiers");
return makeChildObservable(value, this.mode, null /* TODO: enable again: {
return makeChildObservable(value, this.mode, {
object: this.context.object,
name: this.context.name + "[x]"
}*/);
});
}

private notifyChildUpdate(index:number, oldValue:T) {
Expand All @@ -122,17 +127,16 @@ export class ObservableArrayAdministration<T> extends DataNode {
}
}

export function createObservableArray<T>(initialValues:T[], mode:ValueMode, context: IContextInfoStruct): IObservableArray<T> {
return <IObservableArray<T>><any> new ObservableArray(initialValues, mode, context);
export function createObservableArray<T>(initialValues:T[], mode:ValueMode, supportEnumerable:boolean, context: IContextInfoStruct): IObservableArray<T> {
return <IObservableArray<T>><any> new ObservableArray(initialValues, mode, supportEnumerable, context);
}

export class ObservableArray<T> extends StubArray {
$mobservable:ObservableArrayAdministration<T>;


constructor(initialValues:T[], mode:ValueMode, context: IContextInfoStruct) {
constructor(initialValues:T[], mode:ValueMode, supportEnumerable:boolean, context: IContextInfoStruct) {
super();
let adm = new ObservableArrayAdministration(this, mode, context);
let adm = new ObservableArrayAdministration(this, mode, supportEnumerable, context);
Object.defineProperty(this, "$mobservable", {
enumerable: false,
configurable: false,
Expand Down Expand Up @@ -167,6 +171,11 @@ export class ObservableArray<T> extends StubArray {
return this.$mobservable.values.slice();
}

peek(): T[] {
this.$mobservable.notifyObserved();
return this.$mobservable.values;
}

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
find(predicate:(item:T,index:number,array:ObservableArray<T>)=>boolean, thisArg?, fromIndex=0):T {
this.$mobservable.notifyObserved();
Expand Down Expand Up @@ -248,6 +257,7 @@ makeNonEnumerable(ObservableArray.prototype, [
"find",
"observe",
"pop",
"peek",
"push",
"remove",
"replace",
Expand Down
2 changes: 1 addition & 1 deletion src/observablemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class ObservableMap<V> {
$mobservable = {};
private _data: { [key:string]: ObservableValue<V> } = {};
private _hasMap: { [key:string]: ObservableValue<boolean> } = {}; // hasMap, not hashMap >-).
private _keys: IObservableArray<string> = <any> new ObservableArray(null, ValueMode.Reference, {
private _keys: IObservableArray<string> = <any> new ObservableArray(null, ValueMode.Reference, false, {
name: ".keys()",
object: this
});
Expand Down
56 changes: 45 additions & 11 deletions test/array.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@ test('test1', function(t) {
try {
var a = observable([]);
t.equal(a.length, 0);
// t.deepEqual(Object.keys(a), []);
t.deepEqual(Object.keys(a), []);
t.deepEqual(a.slice(), []);

a.push(1);
t.equal(a.length, 1);
// t.deepEqual(Object.keys(a), ["0"]);
t.deepEqual(Object.keys(a), ["0"]);
t.deepEqual(a.slice(), [1]);

a[1] = 2;
t.equal(a.length, 2);
// t.deepEqual(Object.keys(a), ["0", "1"]);
t.deepEqual(Object.keys(a), ["0", "1"]);
t.deepEqual(a.slice(), [1,2]);

var sum = observable(function() {
Expand All @@ -40,13 +40,13 @@ test('test1', function(t) {

a[1] = 3;
t.equal(a.length, 2);
// t.deepEqual(Object.keys(a), ["0", "1"]);
t.deepEqual(Object.keys(a), ["0", "1"]);
t.deepEqual(a.slice(), [1,3]);
t.equal(sum(), 4);

a.splice(1,1,4,5);
t.equal(a.length, 3);
// t.deepEqual(Object.keys(a), ["0", "1", "2"]);
t.deepEqual(Object.keys(a), ["0", "1", "2"]);
t.deepEqual(a.slice(), [1,4,5]);
t.equal(sum(), 10);

Expand Down Expand Up @@ -104,16 +104,16 @@ test('enumerable', function(t) {
}

var ar = mobservable.observable([1,2,3]);
// t.deepEqual(getKeys(ar), ['0','1','2']);
t.deepEqual(getKeys(ar), ['0','1','2']);

ar.push(5,6);
// t.deepEqual(getKeys(ar), ['0','1','2','3','4']);
t.deepEqual(getKeys(ar), ['0','1','2','3','4']);

ar.pop();
// t.deepEqual(getKeys(ar), ['0','1','2','3']);
t.deepEqual(getKeys(ar), ['0','1','2','3']);

ar.shift();
// t.deepEqual(getKeys(ar), ['0','1','2']);
t.deepEqual(getKeys(ar), ['0','1','2']);
t.end();
})

Expand Down Expand Up @@ -252,7 +252,7 @@ test('array modification functions', function(t) {
var res1 = a[f](4);
var res2 = b[f](4);
t.deepEqual(res1, res2);
t.deepEqual(a, b.slice());
t.deepEqual(a, b);
});
});
t.end();
Expand All @@ -268,7 +268,7 @@ test('array write functions', function(t) {
var res1 = a[f](4);
var res2 = b[f](4);
t.deepEqual(res1, res2);
t.deepEqual(a, b.slice());
t.deepEqual(a, b);
});
});
t.end();
Expand All @@ -282,6 +282,28 @@ test('array modification2', function(t) {
for (var i = 0; i < inputs.length; i++)
for (var j = 0; j< inputs.length; j++)
for (var k = 0; k < arrays.length; k++)
for (var l = 0; l < arrays.length; l++) {
var msg = ["array mod: [", arrays[k].toString(),"] i: ",inputs[i]," d: ", inputs[j]," [", arrays[l].toString(),"]"].join(' ');
var a1 = arrays[k].slice();
a2.replace(a1);
var res1 = a1.splice.apply(a1, [inputs[i], inputs[j]].concat(arrays[l]));
var res2 = a2.splice.apply(a2, [inputs[i], inputs[j]].concat(arrays[l]));
t.deepEqual(a1.slice(), a2, "values wrong: " + msg);
t.deepEqual(res1, res2, "results wrong: " + msg);
t.equal(a1.length, a2.length, "length wrong: " + msg);
}

t.end();
})

test('fastArray modifications', function(t) {

var a2 = mobservable.fastArray([]);
var inputs = [undefined, -10, -4, -3, -1, 0, 1, 3, 4, 10];
var arrays = [[], [1], [1,2,3,4], [1,2,3,4,5,6,7,8,9,10,11],[1,undefined],[undefined]]
for (var i = 0; i < inputs.length; i++)
for (var j = 0; j< inputs.length; j++)
for (var k = 0; k < arrays.length; k++)
for (var l = 0; l < arrays.length; l++) {
var msg = ["array mod: [", arrays[k].toString(),"] i: ",inputs[i]," d: ", inputs[j]," [", arrays[l].toString(),"]"].join(' ');
var a1 = arrays[k].slice();
Expand All @@ -304,4 +326,16 @@ test('is array', function(t) {
t.equal(typeof x === "array", false);
t.equal(Array.isArray(x), false);
t.end();
})

test('peek', function(t) {
var x = mobservable.observable([1, 2, 3]);
t.deepEqual(x.peek(), [1, 2, 3]);
t.equal(x.$mobservable.values, x.peek());

x.peek().push(4); //noooo!
t.throws(function() {
x.push(5); // detect alien change
}, "modification exception");
t.end();
})
21 changes: 14 additions & 7 deletions test/perf/perf.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ test('array reduce', function(t) {
var start = now();

for(var i = 0; i < 1000; i++)
ar.push(i);
ar.push(i);

t.equal(499500, sum());
t.equal(1001, aCalc);
Expand Down Expand Up @@ -330,19 +330,26 @@ test('order system batched lazy', function(t) {
order_system_helper(t, true, false);
})

function test_array_creation(t, amount, size) {
test('create array', function(t) {
var a = [];
for(var i = 0; i < size; i++)
for(var i = 0; i < 1000; i++)
a.push(i);
var start = now();
for(var i = 0; i < amount; i++)
for(var i = 0; i < 1000; i++)
observable(a);
console.log('\n Created in ' + (now() - start) + 'ms.');
t.end();
};
})

test('create array', function(t) {
test_array_creation(t, 1000, 1000);
test('create array (fast)', function(t) {
var a = [];
for(var i = 0; i < 1000; i++)
a.push(i);
var start = now();
for(var i = 0; i < 1000; i++)
mobservable.fastArray(a);
console.log('\n Created in ' + (now() - start) + 'ms.');
t.end();
})

function now() {
Expand Down

0 comments on commit 3530aed

Please sign in to comment.