forked from zhurui/management
345 lines
11 KiB
JavaScript
345 lines
11 KiB
JavaScript
|
|
||
|
/*
|
||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||
|
* or more contributor license agreements. See the NOTICE file
|
||
|
* distributed with this work for additional information
|
||
|
* regarding copyright ownership. The ASF licenses this file
|
||
|
* to you under the Apache License, Version 2.0 (the
|
||
|
* "License"); you may not use this file except in compliance
|
||
|
* with the License. You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing,
|
||
|
* software distributed under the License is distributed on an
|
||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||
|
* KIND, either express or implied. See the License for the
|
||
|
* specific language governing permissions and limitations
|
||
|
* under the License.
|
||
|
*/
|
||
|
|
||
|
var _util = require("zrender/lib/core/util");
|
||
|
|
||
|
var assert = _util.assert;
|
||
|
var isArray = _util.isArray;
|
||
|
|
||
|
var _config = require("../config");
|
||
|
|
||
|
var __DEV__ = _config.__DEV__;
|
||
|
|
||
|
/*
|
||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||
|
* or more contributor license agreements. See the NOTICE file
|
||
|
* distributed with this work for additional information
|
||
|
* regarding copyright ownership. The ASF licenses this file
|
||
|
* to you under the Apache License, Version 2.0 (the
|
||
|
* "License"); you may not use this file except in compliance
|
||
|
* with the License. You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing,
|
||
|
* software distributed under the License is distributed on an
|
||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||
|
* KIND, either express or implied. See the License for the
|
||
|
* specific language governing permissions and limitations
|
||
|
* under the License.
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* @param {Object} define
|
||
|
* @return See the return of `createTask`.
|
||
|
*/
|
||
|
function createTask(define) {
|
||
|
return new Task(define);
|
||
|
}
|
||
|
/**
|
||
|
* @constructor
|
||
|
* @param {Object} define
|
||
|
* @param {Function} define.reset Custom reset
|
||
|
* @param {Function} [define.plan] Returns 'reset' indicate reset immediately.
|
||
|
* @param {Function} [define.count] count is used to determin data task.
|
||
|
* @param {Function} [define.onDirty] count is used to determin data task.
|
||
|
*/
|
||
|
|
||
|
|
||
|
function Task(define) {
|
||
|
define = define || {};
|
||
|
this._reset = define.reset;
|
||
|
this._plan = define.plan;
|
||
|
this._count = define.count;
|
||
|
this._onDirty = define.onDirty;
|
||
|
this._dirty = true; // Context must be specified implicitly, to
|
||
|
// avoid miss update context when model changed.
|
||
|
|
||
|
this.context;
|
||
|
}
|
||
|
|
||
|
var taskProto = Task.prototype;
|
||
|
/**
|
||
|
* @param {Object} performArgs
|
||
|
* @param {number} [performArgs.step] Specified step.
|
||
|
* @param {number} [performArgs.skip] Skip customer perform call.
|
||
|
* @param {number} [performArgs.modBy] Sampling window size.
|
||
|
* @param {number} [performArgs.modDataCount] Sampling count.
|
||
|
*/
|
||
|
|
||
|
taskProto.perform = function (performArgs) {
|
||
|
var upTask = this._upstream;
|
||
|
var skip = performArgs && performArgs.skip; // TODO some refactor.
|
||
|
// Pull data. Must pull data each time, because context.data
|
||
|
// may be updated by Series.setData.
|
||
|
|
||
|
if (this._dirty && upTask) {
|
||
|
var context = this.context;
|
||
|
context.data = context.outputData = upTask.context.outputData;
|
||
|
}
|
||
|
|
||
|
if (this.__pipeline) {
|
||
|
this.__pipeline.currentTask = this;
|
||
|
}
|
||
|
|
||
|
var planResult;
|
||
|
|
||
|
if (this._plan && !skip) {
|
||
|
planResult = this._plan(this.context);
|
||
|
} // Support sharding by mod, which changes the render sequence and makes the rendered graphic
|
||
|
// elements uniformed distributed when progress, especially when moving or zooming.
|
||
|
|
||
|
|
||
|
var lastModBy = normalizeModBy(this._modBy);
|
||
|
var lastModDataCount = this._modDataCount || 0;
|
||
|
var modBy = normalizeModBy(performArgs && performArgs.modBy);
|
||
|
var modDataCount = performArgs && performArgs.modDataCount || 0;
|
||
|
|
||
|
if (lastModBy !== modBy || lastModDataCount !== modDataCount) {
|
||
|
planResult = 'reset';
|
||
|
}
|
||
|
|
||
|
function normalizeModBy(val) {
|
||
|
!(val >= 1) && (val = 1); // jshint ignore:line
|
||
|
|
||
|
return val;
|
||
|
}
|
||
|
|
||
|
var forceFirstProgress;
|
||
|
|
||
|
if (this._dirty || planResult === 'reset') {
|
||
|
this._dirty = false;
|
||
|
forceFirstProgress = reset(this, skip);
|
||
|
}
|
||
|
|
||
|
this._modBy = modBy;
|
||
|
this._modDataCount = modDataCount;
|
||
|
var step = performArgs && performArgs.step;
|
||
|
|
||
|
if (upTask) {
|
||
|
this._dueEnd = upTask._outputDueEnd;
|
||
|
} // DataTask or overallTask
|
||
|
else {
|
||
|
this._dueEnd = this._count ? this._count(this.context) : Infinity;
|
||
|
} // Note: Stubs, that its host overall task let it has progress, has progress.
|
||
|
// If no progress, pass index from upstream to downstream each time plan called.
|
||
|
|
||
|
|
||
|
if (this._progress) {
|
||
|
var start = this._dueIndex;
|
||
|
var end = Math.min(step != null ? this._dueIndex + step : Infinity, this._dueEnd);
|
||
|
|
||
|
if (!skip && (forceFirstProgress || start < end)) {
|
||
|
var progress = this._progress;
|
||
|
|
||
|
if (isArray(progress)) {
|
||
|
for (var i = 0; i < progress.length; i++) {
|
||
|
doProgress(this, progress[i], start, end, modBy, modDataCount);
|
||
|
}
|
||
|
} else {
|
||
|
doProgress(this, progress, start, end, modBy, modDataCount);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this._dueIndex = end; // If no `outputDueEnd`, assume that output data and
|
||
|
// input data is the same, so use `dueIndex` as `outputDueEnd`.
|
||
|
|
||
|
var outputDueEnd = this._settedOutputEnd != null ? this._settedOutputEnd : end;
|
||
|
this._outputDueEnd = outputDueEnd;
|
||
|
} else {
|
||
|
// (1) Some overall task has no progress.
|
||
|
// (2) Stubs, that its host overall task do not let it has progress, has no progress.
|
||
|
// This should always be performed so it can be passed to downstream.
|
||
|
this._dueIndex = this._outputDueEnd = this._settedOutputEnd != null ? this._settedOutputEnd : this._dueEnd;
|
||
|
}
|
||
|
|
||
|
return this.unfinished();
|
||
|
};
|
||
|
|
||
|
var iterator = function () {
|
||
|
var end;
|
||
|
var current;
|
||
|
var modBy;
|
||
|
var modDataCount;
|
||
|
var winCount;
|
||
|
var it = {
|
||
|
reset: function (s, e, sStep, sCount) {
|
||
|
current = s;
|
||
|
end = e;
|
||
|
modBy = sStep;
|
||
|
modDataCount = sCount;
|
||
|
winCount = Math.ceil(modDataCount / modBy);
|
||
|
it.next = modBy > 1 && modDataCount > 0 ? modNext : sequentialNext;
|
||
|
}
|
||
|
};
|
||
|
return it;
|
||
|
|
||
|
function sequentialNext() {
|
||
|
return current < end ? current++ : null;
|
||
|
}
|
||
|
|
||
|
function modNext() {
|
||
|
var dataIndex = current % winCount * modBy + Math.ceil(current / winCount);
|
||
|
var result = current >= end ? null : dataIndex < modDataCount ? dataIndex // If modDataCount is smaller than data.count() (consider `appendData` case),
|
||
|
// Use normal linear rendering mode.
|
||
|
: current;
|
||
|
current++;
|
||
|
return result;
|
||
|
}
|
||
|
}();
|
||
|
|
||
|
taskProto.dirty = function () {
|
||
|
this._dirty = true;
|
||
|
this._onDirty && this._onDirty(this.context);
|
||
|
};
|
||
|
|
||
|
function doProgress(taskIns, progress, start, end, modBy, modDataCount) {
|
||
|
iterator.reset(start, end, modBy, modDataCount);
|
||
|
taskIns._callingProgress = progress;
|
||
|
|
||
|
taskIns._callingProgress({
|
||
|
start: start,
|
||
|
end: end,
|
||
|
count: end - start,
|
||
|
next: iterator.next
|
||
|
}, taskIns.context);
|
||
|
}
|
||
|
|
||
|
function reset(taskIns, skip) {
|
||
|
taskIns._dueIndex = taskIns._outputDueEnd = taskIns._dueEnd = 0;
|
||
|
taskIns._settedOutputEnd = null;
|
||
|
var progress;
|
||
|
var forceFirstProgress;
|
||
|
|
||
|
if (!skip && taskIns._reset) {
|
||
|
progress = taskIns._reset(taskIns.context);
|
||
|
|
||
|
if (progress && progress.progress) {
|
||
|
forceFirstProgress = progress.forceFirstProgress;
|
||
|
progress = progress.progress;
|
||
|
} // To simplify no progress checking, array must has item.
|
||
|
|
||
|
|
||
|
if (isArray(progress) && !progress.length) {
|
||
|
progress = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
taskIns._progress = progress;
|
||
|
taskIns._modBy = taskIns._modDataCount = null;
|
||
|
var downstream = taskIns._downstream;
|
||
|
downstream && downstream.dirty();
|
||
|
return forceFirstProgress;
|
||
|
}
|
||
|
/**
|
||
|
* @return {boolean}
|
||
|
*/
|
||
|
|
||
|
|
||
|
taskProto.unfinished = function () {
|
||
|
return this._progress && this._dueIndex < this._dueEnd;
|
||
|
};
|
||
|
/**
|
||
|
* @param {Object} downTask The downstream task.
|
||
|
* @return {Object} The downstream task.
|
||
|
*/
|
||
|
|
||
|
|
||
|
taskProto.pipe = function (downTask) {
|
||
|
// If already downstream, do not dirty downTask.
|
||
|
if (this._downstream !== downTask || this._dirty) {
|
||
|
this._downstream = downTask;
|
||
|
downTask._upstream = this;
|
||
|
downTask.dirty();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
taskProto.dispose = function () {
|
||
|
if (this._disposed) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
this._upstream && (this._upstream._downstream = null);
|
||
|
this._downstream && (this._downstream._upstream = null);
|
||
|
this._dirty = false;
|
||
|
this._disposed = true;
|
||
|
};
|
||
|
|
||
|
taskProto.getUpstream = function () {
|
||
|
return this._upstream;
|
||
|
};
|
||
|
|
||
|
taskProto.getDownstream = function () {
|
||
|
return this._downstream;
|
||
|
};
|
||
|
|
||
|
taskProto.setOutputEnd = function (end) {
|
||
|
// This only happend in dataTask, dataZoom, map, currently.
|
||
|
// where dataZoom do not set end each time, but only set
|
||
|
// when reset. So we should record the setted end, in case
|
||
|
// that the stub of dataZoom perform again and earse the
|
||
|
// setted end by upstream.
|
||
|
this._outputDueEnd = this._settedOutputEnd = end;
|
||
|
}; ///////////////////////////////////////////////////////////
|
||
|
// For stream debug (Should be commented out after used!)
|
||
|
// Usage: printTask(this, 'begin');
|
||
|
// Usage: printTask(this, null, {someExtraProp});
|
||
|
// function printTask(task, prefix, extra) {
|
||
|
// window.ecTaskUID == null && (window.ecTaskUID = 0);
|
||
|
// task.uidDebug == null && (task.uidDebug = `task_${window.ecTaskUID++}`);
|
||
|
// task.agent && task.agent.uidDebug == null && (task.agent.uidDebug = `task_${window.ecTaskUID++}`);
|
||
|
// var props = [];
|
||
|
// if (task.__pipeline) {
|
||
|
// var val = `${task.__idxInPipeline}/${task.__pipeline.tail.__idxInPipeline} ${task.agent ? '(stub)' : ''}`;
|
||
|
// props.push({text: 'idx', value: val});
|
||
|
// } else {
|
||
|
// var stubCount = 0;
|
||
|
// task.agentStubMap.each(() => stubCount++);
|
||
|
// props.push({text: 'idx', value: `overall (stubs: ${stubCount})`});
|
||
|
// }
|
||
|
// props.push({text: 'uid', value: task.uidDebug});
|
||
|
// if (task.__pipeline) {
|
||
|
// props.push({text: 'pid', value: task.__pipeline.id});
|
||
|
// task.agent && props.push(
|
||
|
// {text: 'stubFor', value: task.agent.uidDebug}
|
||
|
// );
|
||
|
// }
|
||
|
// props.push(
|
||
|
// {text: 'dirty', value: task._dirty},
|
||
|
// {text: 'dueIndex', value: task._dueIndex},
|
||
|
// {text: 'dueEnd', value: task._dueEnd},
|
||
|
// {text: 'outputDueEnd', value: task._outputDueEnd}
|
||
|
// );
|
||
|
// if (extra) {
|
||
|
// Object.keys(extra).forEach(key => {
|
||
|
// props.push({text: key, value: extra[key]});
|
||
|
// });
|
||
|
// }
|
||
|
// var args = ['color: blue'];
|
||
|
// var msg = `%c[${prefix || 'T'}] %c` + props.map(item => (
|
||
|
// args.push('color: black', 'color: red'),
|
||
|
// `${item.text}: %c${item.value}`
|
||
|
// )).join('%c, ');
|
||
|
// console.log.apply(console, [msg].concat(args));
|
||
|
// // console.log(this);
|
||
|
// }
|
||
|
|
||
|
|
||
|
exports.createTask = createTask;
|