Files
pathfinder/js/app/lib/cron.js
Mark Friedrich a5f29ee2eb - NEW "Thera connections" UI module, closed #829
- Upgraded "[_pathfinder_esi_](https://github.com/exodus4d/pathfinder_esi)" Web API client`v1.3.2` → `v2.0.0`
- Fixed a js bug where current active(selected) system becomes deselected after system was dragged on map
- Fixed a js bug where new auto mapped systems (e.g. after jump) were positioned outside current map scroll viewport
- Fixed a js bug where map sync failed after map tabs switch
- Fixed blurry map when map zoom was changed
- Fixed multiple minor JS bugs where map render/update failed
2020-03-02 16:42:36 +01:00

482 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
define([
'easyTimer',
'app/promises/promise.timeout',
], (easytimer, TimeoutPromise) => {
'use strict';
/*
Example1 run task every second ------------------------------------------------------------------------------------
let task1 = Cron.new('task1', {precision: 'seconds', interval: 1, timeout: 100});
task1.task = (timer) => {
console.info('task1 function():', timer.getTotalTimeValues());
return 'OK';
};
task1.start();
Example2 run task every 3 seconds ---------------------------------------------------------------------------------
let task1 = Cron.new('task1', {precision: 'seconds', interval: 3, timeout: 100});
task1.task = (timer) => {
console.info('task1 function():', timer.getTotalTimeValues());
return 'OK';
};
task1.start();
Example3 resolve Promise on run ----------------------------------------------------------------------------------
let task1 = Cron.new('task1', {precision: 'seconds', interval: 1, timeout: 100});
task1.task = (timer, task) => {
return new Promise((resolve, reject) => {
console.info('task1 Promise1():', timer.getTotalTimeValues(), task.get('interval'));
//task.set('interval', task.get('interval') + 1) // increase run interval every time by 1s
resolve('OK1');
}).then(payload => {
return new Promise((resolve, reject) => {
console.info('task2 Promise2():', timer.getTotalTimeValues(), payload);
resolve('OK2');
});
});
};
task1.start();
Example4 run task once at given Date() --------------------------------------------------------------------------
let dueDate = new Date();
dueDate.setSeconds(dueDate.getSeconds() + 5);
let task2 = Cron.new('task2', {precision: 'seconds', timeout: 100, dueDate: dueDate});
task2.task = () => 'OK task2';
task2.start();
*/
/**
* Task instances represent a task that should be executed at a given interval or dueDate
* -> Task´s are managed by CronManager()
* @type {Task}
*/
let Task = class Task {
/**
*
* @param {string} name
* @param {{}} config
* @param {CronManager} manager
*/
constructor(name, config, manager = null){
if(typeof name !== 'string'){
throw new TypeError('Task "name" must be instance of String, Type of "' + typeof name + '" given');
}
this._config = Object.assign({}, this.constructor.defaultConfig, config);
this._name = name; // unique name for identification
this._task = (timer, task) => {}; // task to run, instanceof Function, can also return a Promise
this._manager = manager; // reference to CronManager() that handles this task
this._runCount = 0; // counter for run() calls
this._runQueue = new Map(); // current run() processes. > 1 requires config.isParallel: true
this._lastTotalTimeValues = undefined; // time values of last run()
}
/**
* @returns {string}
*/
get name(){
return this._name;
}
/**
* @returns {(function())} task
*/
get task() {
return this._task;
}
/**
* @param {(function())} task
*/
set task(task){
if(task instanceof Function){
this._task = task;
}else{
throw new TypeError('Task "task" must be instance of "function", Type of "' + typeof task + '" given');
}
}
/**
* @returns {number}
*/
get runCount(){
return this._runCount;
}
/**
* @returns {string}
*/
get precision(){
return this.get('precision');
}
/**
* @returns {boolean}
*/
get paused(){
return this.get('paused');
}
/**
* @returns {boolean}
*/
get targetAchieved(){
return this.get('targetRunCount') ? this.runCount >= this.get('targetRunCount') : false;
}
/**
* @returns {number}
*/
get targetProgress(){
return parseFloat(
parseFloat(
(!this.get('targetRunCount') || !this.runCount) ?
0 :
(100 / this.get('targetRunCount') * this.runCount)
).toFixed(2));
}
/**
* @param option
* @returns {*}
*/
get(option){
return this._config[option];
}
/**
* @param {string} option
* @param {*} value
*/
set(option, value){
this._config[option] = value;
}
/**
* connect CronManager with instance
* @param {CronManager} manager
*/
connect(manager = this._manager){
if(manager instanceof CronManager){
if(manager !== this._manager){
// disconnect from current manager (if exists)
this.disconnect();
this._manager = manager;
}
this._manager.set(this);
}else{
throw new TypeError('Parameter must be instance of CronManager. Type of "' + typeof manager + '" given');
}
}
/**
* disconnect from CronManager
* @param {CronManager} manager
*/
disconnect(manager = this._manager){
if(manager instanceof CronManager){
if(this.isConnected(manager)){
this._manager.delete(this._name);
}
}else{
throw new TypeError('Parameter must be instance of CronManager. Type of "' + typeof manager + '" given');
}
}
/**
* checks if CronManager is connected with instance
* @param {CronManager} manager
* @returns {boolean}
*/
isConnected(manager = this._manager){
return (manager instanceof CronManager) &&
manager === this._manager &&
manager.has(this._name);
}
/**
* if task is currently running
* @returns {boolean}
*/
isRunning(){
return !!this._runQueue.size;
}
/**
* @param timer
* @returns {boolean}
*/
isDue(timer){
if(this.get('dueDate') instanceof Date){
// run once at dueDate
if(new Date().getTime() >= this.get('dueDate').getTime()){
return true;
}
}else{
// periodic execution
let totalTimeValues = timer.getTotalTimeValues();
let totalTimeValuePrecision = totalTimeValues[this.precision];
totalTimeValuePrecision -= this._lastTotalTimeValues ? this._lastTotalTimeValues[this.precision] : 0;
if(
this.get('interval') === 1 ||
totalTimeValuePrecision % this.get('interval') === 0
){
return true;
}
}
return false;
}
/**
* @param timer
*/
invoke(timer){
if(
!this.paused &&
this.isDue(timer) &&
(!this.isRunning() || this.get('isParallel'))
){
this.run(timer);
}
}
/**
* @param timer
*/
run(timer){
this._lastTotalTimeValues = Object.assign({}, timer.getTotalTimeValues());
let runId = 'run_' + (++this._runCount);
let runExec = resolve => {
resolve(this._task(timer, this));
};
let myProm = this.get('timeout') > 0 ? new TimeoutPromise(runExec, this.get('timeout')) : new Promise(runExec);
myProm.then(payload => {
// resolved within timeout -> wait for finally() block
}).catch(error => {
if(error instanceof Error){
// either timeout error or error from rejected deferredPromise
console.warn(error);
}
}).finally(() => {
// no matter if TimeoutPromise is resolved or rejected
// -> remove from _runQueue
this._runQueue.delete(runId);
if(this.get('dueDate') instanceof Date){
this.disconnect();
}
if(this.targetAchieved){
this.stop();
}
});
this._runQueue.set(runId, myProm);
}
// Task controls ----------------------------------------------------------------------------------------------
start(){
this.set('paused', false);
this.connect();
}
stop(){
this.reset();
this.disconnect();
}
pause(){
this.set('paused', true);
}
reset(){
this._runCount = 0;
}
};
Task.defaultConfig = {
precision: 'seconds', // updateEvent this tasked will be subscribed to
isParallel: false, // if true this task can run parallel, e.g. if prev execution has not finished
interval: 1, // relates to 'precision'. 'interval' = 3 and 'precision' = "seconds" -> run every 3 seconds
dueDate: undefined, // if Date() instance is set, task only runs once at dueDate
timeout: 50, // if > 0, execution time that exceeds timeout (ms) throw error
paused: false, // if true this task will not run() but will be invoce()´ed
targetRunCount: 0 // if > 0, task will stop if targetRunCount is reached
};
/**
* An instance of CronManager() handles multiple Task()´s
* -> Task()´s can be set()/delete() from CronManager() instance
* @type {CronManager}
*/
let CronManager = class CronManager {
/**
* @param {{}} config
*/
constructor(config){
this._config = Object.assign({}, this.constructor.defaultConfig, config);
this._timerConfig = Object.assign({}, this.constructor.defaultTimerConfig);
this._tasks = new Map();
this._timer = new easytimer.Timer();
// init Easytimer update events
this._config.precisions.map(precision => precision + 'Updated').forEach(eventName => {
this._timer.on(eventName, e => {
let precision = e.type.substring(0, e.type.indexOf('Updated'));
this.tasksByPrecision(precision).forEach(task => task.invoke(e.detail.timer));
});
});
this.debug = (msg,...data) => {
if(this._config.debug){
data = (data || []);
console.debug(msg, ...data);
}
};
}
/**
* @param {string} name
* @param {{}} config
* @returns {Task}
*/
new(name, config){
return new Task(name, config, this);
}
/**
* @param {Task} task
*/
set(task){
if(task instanceof Task){
// check for unique task name
if(!this.has(task.name)){
// connect new task
// -> must be before connect(this)! (prevents infinite loop)
this._tasks.set(task.name, task);
task.connect(this);
this.debug('SET/UPDATE task: %o config: %o', task.name, task);
// start timer (if it is not already running)
this.auto();
}
}else{
throw new TypeError('Parameter must be instance of Task');
}
}
/**
* @param {string} name
* @param {{}} config
*/
setNew(name, config){
this.set(this.new(name, config));
}
/**
* @param {string} name
* @returns {Task|undefined}
*/
get(name){
return this._tasks.get(name);
}
/**
* @param {string} name
* @returns {boolean}
*/
has(name){
return this._tasks.has(name);
}
/**
* @param {string} name
*/
delete(name){
if(this.has(name)){
let task = this._tasks.get(name);
// disconnect task
// -> must be before disconnect(this)! (prevents infinite loop)
this._tasks.delete(name);
task.disconnect(this);
this.debug('DELETE task: %o', name);
// stop timer (if no more tasks connected)
this.auto();
}
}
clear(){
this.debug('CLEAR all %o task(s)', this._tasks.size);
this._tasks.clear();
this.auto();
}
/**
* @param {string} precision
* @returns {Task[]}
*/
tasksByPrecision(precision){
let tasks = [];
this._tasks.forEach(task => {
if(precision === task.precision){
tasks.push(task);
}
});
return tasks;
}
// EasyTimer controls -----------------------------------------------------------------------------------------
start(){
this._timer.start(this._timerConfig);
}
stop(){
this._timer.stop();
}
pause(){
this._timer.pause();
}
reset(){
this._timer.reset();
}
auto(){
if(this._tasks.size){
if(!this._timer.isRunning()){
this.start();
this.debug('START [auto] timer. %o task(s) found.', this._tasks.size);
}
}else{
this.stop();
this.debug('STOP [auto] timer. No tasks set.');
}
}
};
CronManager.defaultConfig = {
precisions: [
'secondTenths',
'seconds',
'minutes',
'hours',
'days'
],
debug: false // debug output in console
};
CronManager.defaultTimerConfig = {
precision: 'secondTenths', // Timer update frequency. Values: 'secondTenths', 'seconds', 'minutes', 'hours'
countdown: false // If true, the timer is a countdown
};
return new CronManager({
debug: false
});
});