var classUtil = require("./utils/class_util");
var Eventful = require("./event/Eventful");
var env = require("./utils/env");
var timsort = require("./utils/timsort");
/* eslint-disable no-unused-vars */
/**
* @class qrenderer.core.Storage
* Global storage, has 3 core features:
* - Store and manage all the objects in QuarkRenderer instance.
* - Manage the displaylist.
* - Cooperate with Painter to render the elements. Painter will get the display list from Storage, in this case Storage is used as a transfer
* station, we can skip the drawing process for those elements that do NOT need to be rendered, this will help us improve the performance.
*
*
* 全局仓库,有3个主要核心功能:
* - 存储和管理 QuarkRenderer 中的所有对象。
* - 管理显示列表。
* - 与 Painter 配合,渲染元素。Painter 会从 Storage 中获取显示列表进行绘图,利用 Storage 作为中转站,对于不需要刷新的对象可以不绘制,从而可以提升整体性能。
* @docauthor 大漠穷秋 damoqiongqiu@126.com
*/
/**
* @method constructor Storage
*/
var Storage = function Storage() {
// jshint ignore:line
/**
* @private
* This is used to store the elements displayed in the canvas itself, not nested in other element.
*
*
* 存储直接放在画布上的对象,而不是嵌套在其它元素中的对象。
* @property _roots
*/
this._roots = new Map();
/**
* @private
* The display list.
*
* 显示列表。
* @property _displayList
*/
this._displayList = [];
/**
* @private
* Length of display list.
*
*
* 显示列表的长度。
*
* @property _displayListLen
*/
this._displayListLen = 0;
classUtil.inheritProperties(this, Eventful);
};
Storage.prototype = {
constructor: Storage,
/**
* @method traverse
* @param {Function} cb
* @param {Object} context
*/
traverse: function traverse(cb, context) {
this._roots.forEach(function (el, id, map) {
el.traverse(cb, context);
});
},
/**
* @method getDisplayList
* Get the display list.
*
*
* 获取显示列表。
*
* @param {Boolean} [needUpdate=false]
* Wether update the list before returnning.
*
*
* 是否在返回前更新该数组。
* @param {Boolean} [includeIgnore=false]
* Wether include the ignore array, this is valid when needUpdate is true.
*
*
* 是否包含 ignore 的数组, 在 needUpdate 为 true 的时候有效。
* @return {Array<Displayable>}
*/
getDisplayList: function getDisplayList(needUpdate, includeIgnore) {
includeIgnore = includeIgnore || false;
if (needUpdate) {
this.updateDisplayList(includeIgnore);
}
return this._displayList;
},
/**
* @method updateDisplayList
* Update the display list, will be invoked before each redering process.
* This method will traverse the whole tree using deep first method, update all the transformations of Groups and Shapes,
* save all the visiable Shapes into an array. Finally, the array is sorted by the piority, zlevel > z > insert order.
*
*
* 更新显示列表,每次绘制前都会调用。
* 该方法会先深度优先遍历整个树,更新所有 Group 和 Shape 的变换并且把所有可见的Shape保存到数组中,
* 最后根据优先级排序得到绘制队列,zlevel > z > 插入顺序。
* @param {Boolean} [includeIgnore=false]
* Wether include the ignore array.
*
*
* 是否包含 ignore 的数组。
*/
updateDisplayList: function updateDisplayList(includeIgnore) {
var _this = this;
this._displayListLen = 0;
var displayList = this._displayList;
this._roots.forEach(function (el, id, map) {
_this._updateAndAddDisplayable(el, null, includeIgnore); //recursive update
});
displayList.length = this._displayListLen;
env.canvasSupported && timsort(displayList, this.displayableSortFunc);
},
displayableSortFunc: function displayableSortFunc(a, b) {
if (a.qlevel === b.qlevel) {
if (a.z === b.z) {
// if (a.z2 === b.z2) {
// // FIXME Slow has renderidx compare
// // http://stackoverflow.com/questions/20883421/sorting-in-javascript-should-every-compare-function-have-a-return-0-statement
// // https://github.com/v8/v8/blob/47cce544a31ed5577ffe2963f67acb4144ee0232/src/js/array.js#L1012
// return a.__renderidx - b.__renderidx;
// }
return a.z2 - b.z2;
}
return a.z - b.z;
}
return a.qlevel - b.qlevel;
},
/**
* @method _updateAndAddDisplayable
* @param {*} el
* @param {*} clipPaths
* @param {*} includeIgnore
*/
_updateAndAddDisplayable: function _updateAndAddDisplayable(el, clipPaths, includeIgnore) {
if (el.ignore && !includeIgnore) {
return;
}
if (el.__dirty) {
el.composeParentTransform();
}
var userSetClipPath = el.clipPath;
if (userSetClipPath) {
// FIXME:performance issue here
if (clipPaths) {
clipPaths = clipPaths.slice();
} else {
clipPaths = [];
}
var currentClipPath = userSetClipPath;
var parentClipPath = el; // Recursively add clip path
while (currentClipPath) {
// clipPath 的变换是基于使用这个 clipPath 的元素
currentClipPath.parent = parentClipPath;
currentClipPath.composeParentTransform();
clipPaths.push(currentClipPath);
parentClipPath = currentClipPath;
currentClipPath = currentClipPath.clipPath;
}
}
el.__clipPaths = clipPaths;
this._displayList[this._displayListLen++] = el;
if (el.type === 'group') {
var children = el.children;
for (var i = 0; i < children.length; i++) {
var child = children[i]; // Force to mark as dirty if group is dirty
// FIXME: __dirtyPath ?
if (el.__dirty) {
child.__dirty = true;
}
this._updateAndAddDisplayable(child, clipPaths, includeIgnore);
}
}
},
/**
* @method addToRoot
* Add element to root.
*
*
* 添加元素到根节点。
* @param {Element} el
*/
addToRoot: function addToRoot(el) {
if (el.__storage === this) {
return;
}
this.trigger("beforeAddToRoot", el);
el.trigger("beforeAddToRoot", el);
this.addToStorage(el);
},
/**
* @method delFromRoot
* Delete element from root.
*
*
* 删除指定元素。
* @param {string|Array.<String>} [el] 如果为空清空整个Storage
*/
delFromRoot: function delFromRoot(el) {
var _this2 = this;
if (el == null) {
// Clear all.
this._roots.forEach(function (el, id, map) {
_this2.delFromStorage(el);
});
this._roots = new Map();
this._displayList = [];
this._displayListLen = 0;
return;
}
if (el.forEach) {
// Array like.
el.forEach(function (item, index) {
_this2.delFromRoot(item);
});
return;
}
this.delFromStorage(el);
},
/**
* @method addToStorage
* Add element to Storage.
*
*
* 把元素添加到 Storage 中。
* @param {Element} el
*/
addToStorage: function addToStorage(el) {
this._roots.set(el.id, el);
this.trigger("addToStorage", el);
el.trigger("addToStorage", this);
return this;
},
getElement: function getElement(id) {
return this._roots.get(id);
},
/**
* @method delFromStorage
* Delete element from Storage.
*
*
* 从 Storage 中删除元素。
* @param {Element} el
*/
delFromStorage: function delFromStorage(el) {
if (this._roots.get(el.id)) {
this._roots["delete"](el.id);
this.trigger("delFromStorage", el);
el.trigger("delFromStorage", this);
}
return this;
},
/**
* @method dispose
* Clear and dispose Storage.
*
*
* 清空并且释放 Storage。
*/
dispose: function dispose() {
this._renderList = null;
this._roots = null;
}
};
classUtil.mixin(Storage, Eventful);
var _default = Storage;
module.exports = _default;