Skip to content

Commit

Permalink
Re-use objects created by getData(): 50% speedup in expression evalua…
Browse files Browse the repository at this point in the history
…tion
  • Loading branch information
LeaVerou committed Jan 17, 2018
1 parent 07c0844 commit 04a6ea7
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 41 deletions.
30 changes: 19 additions & 11 deletions src/collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,22 +68,29 @@ var _ = Mavo.Collection = $.Class({
var env = {
context: this,
options: o,
data: []
data: this.liveData
};

this.children.forEach(item => {
if (env.options.live) {
this.proxyCache = {};
}

for (var i = 0, j = 0; item = this.children[i]; i++) {
if (!item.deleted || env.options.live) {
var itemData = item.getData(env.options);

if (env.options.live || Mavo.value(itemData) !== null) {
env.data.push(itemData);
env.data[j] = itemData;
j++;
}
}
});
}

env.data.length = j;

if (!this.mutable) {
// If immutable, drop nulls
env.data = env.data.filter(item => Mavo.value(item) !== null);
Mavo.filter(env.data, item => Mavo.value(item) !== null);

if (env.options.live && env.data.length === 1) {
// If immutable with only 1 item, return the item
Expand All @@ -96,18 +103,13 @@ var _ = Mavo.Collection = $.Class({
}
}

if (env.options.live && Array.isArray(env.data)) {
env.data[Mavo.toNode] = this;
env.data = this.relativizeData(env.data);
}

if (!env.options.live) {
env.data = Mavo.subset(this.data, this.inPath, env.data);
}

Mavo.hooks.run("node-getdata-end", env);

return env.data;
return (env.options.live? env.data[Mavo.toProxy] : env.data) || env.data;
},

// Create item but don't insert it anywhere
Expand Down Expand Up @@ -437,6 +439,8 @@ var _ = Mavo.Collection = $.Class({
}
}
}

this.createLiveData(data|| []);
},

find: function(property, o = {}) {
Expand Down Expand Up @@ -619,6 +623,10 @@ var _ = Mavo.Collection = $.Class({
});

return button;
},

liveData: function() {
return this.createLiveData([]);
}
},

Expand Down
41 changes: 24 additions & 17 deletions src/group.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ var _ = Mavo.Group = $.Class({
}
});

this.childrenNames = Object.keys(this.children);

var vocabElement = (this.isRoot? this.element.closest("[vocab]") : null) || this.element;
this.vocab = vocabElement.getAttribute("vocab");

Expand Down Expand Up @@ -72,11 +74,9 @@ var _ = Mavo.Group = $.Class({
return env.data;
}

env.data = (this.data? Mavo.clone(Mavo.subset(this.data, this.inPath)) : {}) || {};

var properties = Object.keys(this.children);
env.data = this.liveData;

if (properties.length == 1 && properties[0] == this.property) {
if (this.childrenNames.length == 1 && this.childrenNames[0] == this.property) {
// {foo: {foo: 5}} should become {foo: 5}
var options = $.extend($.extend({}, env.options), {forceObjects: true});
env.data = this.children[this.property].getData(options);
Expand All @@ -87,22 +87,25 @@ var _ = Mavo.Group = $.Class({

if (obj.saved || env.options.live) {
var data = obj.getData(env.options);
}

if (data === null && !env.options.live) {
delete env.data[obj.property];
}
else {
env.data[obj.property] = data;
}
if (env.options.live || obj.saved && Mavo.value(data) !== null) {
env.data[obj.property] = data;
}
else {
delete env.data[obj.property];
}
}
}

if (!env.options.live) { // Stored data again
if (env.options.live) {
this.proxyCache = {};
}
else { // Stored data again
// If storing, use the rendered data too
env.data = Mavo.subset(this.data, this.inPath, env.data);

if (!properties.length && !this.isRoot) {
if (!this.childrenNames.length && !this.isRoot) {
// Avoid {} in the data
env.data = null;
}
Expand All @@ -117,14 +120,10 @@ var _ = Mavo.Group = $.Class({
}
}
}
else if (env.data) {
env.data[Mavo.toNode] = this;
env.data = this.relativizeData(env.data);
}

Mavo.hooks.run("node-getdata-end", env);

return env.data;
return (env.options.live? env.data[Mavo.toProxy] : env.data) || env.data;
},

/**
Expand Down Expand Up @@ -251,6 +250,14 @@ var _ = Mavo.Group = $.Class({
}
}
}

this.createLiveData(data);
},

lazy: {
liveData: function() {
return this.createLiveData();
}
},

static: {
Expand Down
3 changes: 2 additions & 1 deletion src/mavo.js
Original file line number Diff line number Diff line change
Expand Up @@ -683,7 +683,8 @@ var _ = self.Mavo = $.Class({

lazy: {
locale: () => document.documentElement.lang || "en-GB",
toNode: () => Symbol("toNode")
toNode: () => Symbol("toNode"),
toProxy: () => Symbol("toProxy")
}
}
});
Expand Down
31 changes: 20 additions & 11 deletions src/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -363,22 +363,20 @@ var _ = Mavo.Node = $.Class({
},

relativizeData: self.Proxy? function(data, options = {live: true}) {
var cache = {};

return new Proxy(data, {
get: (data, property, proxy) => {
if (property in data) {
return data[property];
}

// Checking if property is in proxy might add it to the cache
if (property in proxy && property in cache) {
return cache[property];
if (property in proxy && property in this.proxyCache) {
return this.proxyCache[property];
}
},

has: (data, property) => {
if (property in data || property in cache) {
if (property in data || property in this.proxyCache) {
return true;
}

Expand All @@ -387,16 +385,16 @@ var _ = Mavo.Node = $.Class({
// Special values
switch (property) {
case "$index":
cache[property] = this.index || 0;
this.proxyCache[property] = this.index || 0;
return true; // if index is 0 it's falsy and has would return false!
case "$next":
case "$previous":
if (this.closestCollection) {
cache[property] = this.closestCollection.getData(options)[this.index + (property == "$next"? 1 : -1)];
this.proxyCache[property] = this.closestCollection.getData(options)[this.index + (property == "$next"? 1 : -1)];
return true;
}

cache[property] = null;
this.proxyCache[property] = null;
return false;
}

Expand All @@ -412,14 +410,14 @@ var _ = Mavo.Node = $.Class({
ret = ret.getData(options);
}

cache[property] = ret;
this.proxyCache[property] = ret;

return true;
}

// Does it reference another Mavo?
if (property in Mavo.all && Mavo.all[property].root) {
return cache[property] = Mavo.all[property].root.getData(options);
if (property in Mavo.all && isNaN(property) && Mavo.all[property].root) {
return this.proxyCache[property] = Mavo.all[property].root.getData(options);
}

return false;
Expand All @@ -432,6 +430,13 @@ var _ = Mavo.Node = $.Class({
});
} : data => data,

createLiveData: function(obj = {}) {
this.liveData = obj;
this.liveData[Mavo.toNode] = this;
this.liveData[Mavo.toProxy] = this.relativizeData(this.liveData);
return this.liveData;
},

pathFrom: function(node) {
var path = this.path;
var nodePath = node.path;
Expand Down Expand Up @@ -537,6 +542,10 @@ var _ = Mavo.Node = $.Class({
}

return ret;
},

proxyCache: function() {
return {};
}
},

Expand Down
9 changes: 8 additions & 1 deletion src/primitive.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,12 +219,19 @@ var _ = Mavo.Primitive = $.Class({
[Mavo.toNode]: this
});

env.data[Mavo.toProxy] = this.relativizeData(env.data);

if (this.collection) {
// Turn primitive collection items into objects, so we can have $index etc, and their property
// name etc resolve relative to them, not their parent group
env.data[this.property] = env.data;
env.data = this.relativizeData(env.data);
}

Mavo.hooks.run("node-getdata-end", env);

this.proxyCache = {};

return env.data[Mavo.toProxy];
}
}
else if (this.inPath.length) {
Expand Down
13 changes: 13 additions & 0 deletions src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ var _ = $.extend(Mavo, {
return arr === undefined? [] : Array.isArray(arr)? arr : [arr];
},

// Delete an element from an array
// @param all {Boolean} Delete more than one?
delete: (arr, element, all) => {
do {
var index = arr && arr.indexOf(element);
Expand Down Expand Up @@ -136,6 +138,17 @@ var _ = $.extend(Mavo, {
return new Set([...(set1 || []), ...(set2 || [])]);
},

// Filter an array in place
// TODO add index to callback
filter: (arr, callback) => {
for (var i=0; i<arr.length; i++) {
if (!callback(arr[i])) {
arr.splice(i, 1);
i--;
}
}
},

/**
* DOM element utilities
*/
Expand Down

0 comments on commit 04a6ea7

Please sign in to comment.