import clone from 'clone' import { EventEmitter } from 'events' // Queue Multiple Schedulers class UCIScheduleRunner extends EventEmitter { constructor(opts) { super(opts) this.name = opts.name || 'queue' this.schedules = [] if (opts.schedules) opts.schedules.forEach(sch => this.addSchedule(sch)) this.one = opts.one this._toID = 0 // id of timeout this._delayed = [] this._activeCount = 0 this._active = {} // object or lists of active schedules by timeout id this._pausedSchedules = [] this.init() } get countdown() { return (this.schedules[0]||{}).countdown } get names(){ return this.schedules.map(sch => { return {name: sch.name, countdown: sch.countdown, next: sch.nextDT, simultaneous:sch.simultaneous} }) } get nextName(){ return (this.schedules[0]||{}).name } init() { this.on('active', active=>{ if (!active && this._delayed.length) { const sch=this._delayed.shift() // console.log('a delayed action now proceeding====>', sch.name, sch.toid) start.call(this,sch) } }) } setTimeout () { // clearTimeout(this._timeout) const next = this.schedules[0] || {} this._toID++ console.log('+++++++++++++++++++++ next timeout',this._toID, 'in', Math.round(next.countdownMS/1000),'++++++++++++++++++++++++++') this._timeout = setTimeout(() => { console.log('**********************timeout triggered', this._toID, '********************************') let dur = 0 // don't mutate original queue, make copy for active list const queue = clone(this.schedules) this._active[this._toID]=[] queue.forEach(sch =>{ // add first and any others set for same time dur += sch.durationMS // console.log('check if active', sch.name, sch.countdownMS, dur) if (sch.countdownMS <= dur) this._active[this._toID].push(sch) if (sch.one) this.removeSchedule(sch.id) }) // this._active[this._toID].next = 0 this.trigger(this._active[this._toID].length,this._toID) this.emit('active',this._activeCount) if (!this.one) this.start() },next.countdownMS) } stop (now) { console.log('---------------------stopping runner------------------------') clearTimeout(this._timeout) if (now) { console.log('also stopping all in progress schedules') for (let list in this._active) { this._active[list].forEach(sch=>{ clearTimeout(sch._stopTimeout) sch.stopAction()} ) } } } start() { this.update() this.setTimeout() } trigger (count,toid) { // trigger all schedules actions that have same timestamp as first in queue if (count < 1 || count > this._active[toid].length) { // console.log('done: returning') return } // console.log(count,toid,this._active[toid]) const idx = this._active[toid].length-count const sch = this._active[toid][idx] sch.toid = toid // let next = this._active[toid].next // console.log(idx,sch) count-- if (idx===0 && !this._activeCount) start.call(this,sch) if (sch.simultaneous) start.call(this,sch) else { // console.log('adding schedule to delay',sch.name,sch.toid) this._delayed.push(sch) // console.log(this._delayed.length) } this.trigger(count,toid) } get nextTS() { return (this.schedules[0]||{}).nextTS } get nextDT(){ return (this.schedules[0]||{}).nextDT } // schedule instance addSchedule (sch) { if (getBaseClass(sch) === 'UCISchedule') { this.schedules.push(sch) this.sort() } else { console.log('ERROR: passed schedule was not a instance of UCISchedule') console.log(sch.name) } } // schedule id removeSchedule (id) { this.schedules = this.schedules.filter(a => a.id !== id) } getSchedule (id) { return this.schedules.find(a => a.id === id) } updateSchedule (id) { this.getSchedule(id).update() } enableSchedule(id,state) { if (state == null) state = true if (state) { const sch = this._pausedSchedules.find(a => a.id === id) if (sch) { this.addSchedule(sch) sch.enabled = true sch.emit('enabled',true) } } else { const sch = this.schedules.find(a => a.id === id) if (sch) { this._pausedSchedules.push(sch) this.removeSchedule(id) sch.enabled = false sch.emit('enabled',false) } } } update () { for(let sch of this.schedules) { // console.log('updating', sch.name) sch.update() } this.sort() } sort () { this.schedules.sort((a,b) => a.nextTS - b.nextTS) } } export default UCIScheduleRunner export { UCIScheduleRunner as Runner } function start (sch, toid) { toid = sch.toid || toid console.log('-----timeoutid',toid, 'start action-----') sch.startAction() this._activeCount++ this.emit('active',1) sch._stopTimeout = setTimeout(() =>{ // stop action timeout console.log('---timeoutid',toid, 'stop action----') sch.stopAction() this._activeCount-- // done remove from active list this._active[toid]=this._active[toid].filter(s=>s.id!==sch.id) if (!this._active[toid].length) { // none left on that list, remove it console.log('===========actions for timeout', toid, 'are complete===========') delete this._active[toid] } // console.log('schedule action complete', sch.name,toid, this._activeCount) this.emit('active',this._activeCount) },sch.durationMS) } function getBaseClass(targetClass){ if(targetClass instanceof Object){ let baseClass = targetClass while (baseClass){ const newBaseClass = Object.getPrototypeOf(baseClass) if(newBaseClass && newBaseClass !== Object && newBaseClass.name){ baseClass = newBaseClass }else{ break } } return baseClass.constructor.name } }