awx/awx/ui/client/features/output/engine.service.js
2018-05-14 11:17:54 -04:00

229 lines
5.8 KiB
JavaScript

const JOB_END = 'playbook_on_stats';
const MAX_LAG = 120;
function JobEventEngine ($q) {
this.init = ({ resource, scroll, page, onEventFrame, onStart, onStop }) => {
this.resource = resource;
this.scroll = scroll;
this.page = page;
this.lag = 0;
this.count = 0;
this.pageCount = 0;
this.chain = $q.resolve();
this.factors = this.getBatchFactors(this.resource.page.size);
this.state = {
started: false,
paused: false,
pausing: false,
resuming: false,
ending: false,
ended: false,
counting: false,
};
this.hooks = {
onEventFrame,
onStart,
onStop,
};
this.lines = {
used: [],
missing: [],
ready: false,
min: 0,
max: 0
};
};
this.setMinLine = min => {
if (min > this.lines.min) {
this.lines.min = min;
}
};
this.getBatchFactors = size => {
const factors = [1];
for (let i = 2; i <= size / 2; i++) {
if (size % i === 0) {
factors.push(i);
}
}
factors.push(size);
return factors;
};
this.getBatchFactorIndex = () => {
const index = Math.floor((this.lag / MAX_LAG) * this.factors.length);
return index > this.factors.length - 1 ? this.factors.length - 1 : index;
};
this.setBatchFrameCount = () => {
const index = this.getBatchFactorIndex();
this.framesPerRender = this.factors[index];
};
this.buffer = data => {
const pageAdded = this.page.addToBuffer(data);
this.pageCount++;
if (pageAdded) {
this.setBatchFrameCount();
if (this.isPausing()) {
this.pause(true);
} else if (this.isResuming()) {
this.resume(true);
}
}
};
this.checkLines = data => {
for (let i = data.start_line; i < data.end_line; i++) {
if (i > this.lines.max) {
this.lines.max = i;
}
this.lines.used.push(i);
}
const missing = [];
for (let i = this.lines.min; i < this.lines.max; i++) {
if (this.lines.used.indexOf(i) === -1) {
missing.push(i);
}
}
if (missing.length === 0) {
this.lines.ready = true;
this.lines.min = this.lines.max + 1;
this.lines.used = [];
} else {
this.lines.ready = false;
}
};
this.pushJobEvent = data => {
this.lag++;
this.chain = this.chain
.then(() => {
if (!this.isActive()) {
this.start();
} else if (data.event === JOB_END) {
if (this.isPaused()) {
this.end(true);
} else {
this.end();
}
}
this.checkLines(data);
this.buffer(data);
this.count++;
if (!this.isReadyToRender()) {
return $q.resolve();
}
const events = this.page.emptyBuffer();
this.count -= events.length;
return this.renderFrame(events);
})
.then(() => --this.lag);
return this.chain;
};
this.renderFrame = events => this.hooks.onEventFrame(events)
.then(() => {
if (this.isEnding()) {
const lastEvents = this.page.emptyBuffer();
if (lastEvents.length) {
return this.renderFrame(lastEvents);
}
this.end(true);
}
return $q.resolve();
});
this.resume = done => {
if (done) {
this.state.resuming = false;
this.state.paused = false;
} else if (!this.isTransitioning()) {
this.scroll.pause();
this.scroll.lock();
this.scroll.scrollToBottom();
this.state.resuming = true;
this.page.removeBookmark();
}
};
this.pause = done => {
if (done) {
this.state.pausing = false;
this.state.paused = true;
this.scroll.resume();
} else if (!this.isTransitioning()) {
this.scroll.pause();
this.scroll.unlock();
this.state.pausing = true;
this.page.setBookmark();
}
};
this.start = () => {
if (!this.state.ending && !this.state.ended) {
this.state.started = true;
this.scroll.pause();
this.scroll.lock();
this.hooks.onStart();
}
};
this.end = done => {
if (done) {
this.state.ending = false;
this.state.ended = true;
this.scroll.unlock();
this.scroll.resume();
this.hooks.onStop();
return;
}
this.state.ending = true;
};
this.isReadyToRender = () => this.isDone() ||
(!this.isPaused() && this.hasAllLines() && this.isBatchFull());
this.hasAllLines = () => this.lines.ready;
this.isBatchFull = () => this.count % this.framesPerRender === 0;
this.isPaused = () => this.state.paused;
this.isPausing = () => this.state.pausing;
this.isResuming = () => this.state.resuming;
this.isTransitioning = () => this.isActive() && (this.state.pausing || this.state.resuming);
this.isActive = () => this.state.started && !this.state.ended;
this.isEnding = () => this.state.ending;
this.isDone = () => this.state.ended;
}
JobEventEngine.$inject = ['$q'];
export default JobEventEngine;