Change baking to not send all inputs at once.

Add input debouncer.
Remove old HTML comments and unnecessary CSS
(Step is currently a bit broken!)
This commit is contained in:
j433866 2019-05-20 16:53:56 +01:00
parent c50f5769db
commit cb86cb1882
9 changed files with 214 additions and 110 deletions

View File

@ -157,8 +157,6 @@ class App {
action: "autobake",
data: this.manager.input.getActiveTab()
});
this.manager.controls.toggleBakeButtonFunction(false, true);
} else {
this.manager.controls.showStaleIndicator();
}

View File

@ -60,7 +60,7 @@ class ControlsWaiter {
if (btnBake.textContent.indexOf("Bake") > 0) {
this.app.manager.input.bakeAll();
} else if (btnBake.textContent.indexOf("Cancel") > 0) {
this.manager.worker.cancelBake();
this.manager.worker.cancelBake(false, true);
}
}
@ -72,12 +72,14 @@ class ControlsWaiter {
if (this.manager.worker.step) {
// Step has already been clicked so get the data from the output
const activeTab = this.manager.input.getActiveTab();
this.manager.worker.queueInput({
input: this.manager.output.getOutput(activeTab, true),
inputNum: activeTab
});
this.manager.worker.loadingOutputs++;
this.app.progress = this.manager.output.outputs[activeTab].progress;
this.app.bake(true);
this.manager.worker.queueInput({
input: this.manager.output.getOutput(activeTab, true),
inputNum: activeTab,
bakeId: this.manager.worker.bakeId
});
} else {
// First click of step, so get the output from the inputWorker
this.manager.input.inputWorker.postMessage({

View File

@ -50,6 +50,7 @@ class InputWaiter {
this.workerId = 0;
this.maxWorkers = navigator.hardwareConcurrency || 4;
this.maxTabs = 4;
this.inputTimeout = null;
}
/**
@ -266,8 +267,8 @@ class InputWaiter {
case "queueInput":
this.manager.worker.queueInput(r.data);
break;
case "bake":
this.app.bake(r.data);
case "bakeAllInputs":
this.manager.worker.bakeAllInputs(r.data);
break;
case "displayTabSearchResults":
this.displayTabSearchResults(r.data);
@ -332,11 +333,11 @@ class InputWaiter {
const lines = inputData.input.length < (this.app.options.ioDisplayThreshold * 1024) ?
inputData.input.count("\n") + 1 : null;
this.setInputInfo(inputData.input.length, lines);
if (!silent) window.dispatchEvent(this.manager.statechange);
} else {
this.setFile(inputData);
}
if (!silent) window.dispatchEvent(this.manager.statechange);
}.bind(this));
}
@ -452,6 +453,8 @@ class InputWaiter {
silent: false
}
});
window.dispatchEvent(this.manager.statechange);
}
}
@ -541,6 +544,38 @@ class InputWaiter {
}
/**
* Debouncer to stop functions from being executed multiple times in a
* short space of time
* https://davidwalsh.name/javascript-debounce-function
*
* @param {function} func - The function to be executed after the debounce time
* @param {number} wait - The time (ms) to wait before executing the function
* @param {array} args - Array of arguments to be passed to func
* @returns {function}
*/
debounce(func, wait, args) {
return function() {
const context = this,
later = function() {
this.inputTimeout = null;
func.apply(context, args);
};
clearTimeout(this.inputTimeout);
this.inputTimeout = setTimeout(later, wait);
}.bind(this);
}
/**
* Handler for input change events.
* Debounces the input so we don't call autobake too often.
*
* @param {event} e
*/
debounceInputChange(e) {
this.debounce(this.inputChange.bind(this), 100, [e])();
}
/**
* Handler for input change events.
* Updates the value stored in the inputWorker
@ -589,7 +624,7 @@ class InputWaiter {
// and manually fire inputChange()
e.preventDefault();
document.getElementById("input-text").value += pastedData;
this.inputChange(e);
this.debounceInputChange(e);
} else {
e.preventDefault();
e.stopPropagation();
@ -1027,7 +1062,8 @@ class InputWaiter {
* Resets the input, output and info areas, and creates a new inputWorker
*/
clearAllIoClick() {
this.manager.worker.cancelBake();
this.manager.worker.cancelBake(true, true);
this.manager.worker.loaded = false;
this.manager.output.removeAllOutputs();
this.manager.output.terminateZipWorker();

View File

@ -57,6 +57,9 @@ self.addEventListener("message", function(e) {
case "bakeAll":
self.bakeAllInputs();
break;
case "bakeNext":
self.bakeInput(r.data.inputNum, r.data.bakeId);
break;
case "getLoadProgress":
self.getLoadProgress(r.data);
break;
@ -142,53 +145,60 @@ self.getLoadProgress = function(inputNum) {
self.autoBake = function(inputNum, step=false) {
const input = self.getInputObj(inputNum);
if (input) {
let inputData = input.data;
if (typeof inputData !== "string") {
inputData = inputData.fileBuffer;
}
self.postMessage({
action: "queueInput",
action: "bakeAllInputs",
data: {
input: inputData,
inputNum: parseInt(inputNum, 10),
override: false
nums: [parseInt(inputNum, 10)],
step: step
}
});
self.postMessage({
action: "bake",
data: step
});
}
};
/**
* Fired when we want to bake all inputs (bake button clicked)
* Queues all of the loaded inputs and sends a bake command
* Sends a list of inputNums to the workerwaiter
*/
self.bakeAllInputs = function() {
const inputNums = Object.keys(self.inputs);
const inputNums = Object.keys(self.inputs),
nums = [];
for (let i = 0; i < inputNums.length; i++) {
if (self.inputs[inputNums[i]].status === "loaded") {
let inputData = self.inputs[inputNums[i]].data;
if (typeof inputData !== "string") {
inputData = inputData.fileBuffer;
}
self.postMessage({
action: "queueInput",
data: {
input: inputData,
inputNum: inputNums[i],
override: false
}
});
nums.push(parseInt(inputNums[i], 10));
}
}
self.postMessage({
action: "bake",
data: false
action: "bakeAllInputs",
data: {
nums: nums,
step: false
}
});
};
/**
* Gets the data for the provided inputNum and sends it to the WorkerWaiter
*
* @param {number} inputNum
* @param {number} bakeId
*/
self.bakeInput = function(inputNum, bakeId) {
const inputObj = self.getInputObj(inputNum);
if (inputObj === null || inputObj === undefined) return;
if (inputObj.status !== "loaded") return;
let inputData = inputObj.data;
if (typeof inputData !== "string") inputData = inputData.fileBuffer;
self.postMessage({
action: "queueInput",
data: {
input: inputData,
inputNum: inputNum,
bakeId: bakeId
}
});
};
/**
@ -789,6 +799,10 @@ self.removeInput = function(removeInputData) {
delete self.inputs[inputNum];
if (Object.keys(self.inputs).length === 0) {
self.addInput(true, "string");
}
if (refreshTabs) {
self.refreshTabs(inputNum, "left");
}

View File

@ -144,7 +144,7 @@ class Manager {
this.addDynamicListener("textarea.arg", "drop", this.recipe.textArgDrop, this.recipe);
// Input
this.addMultiEventListener("#input-text", "keyup", this.input.inputChange, this.input);
this.addMultiEventListener("#input-text", "keyup", this.input.debounceInputChange, this.input);
this.addMultiEventListener("#input-text", "paste", this.input.inputPaste, this.input);
document.getElementById("reset-layout").addEventListener("click", this.app.resetLayout.bind(this.app));
document.getElementById("clr-io").addEventListener("click", this.input.clearAllIoClick.bind(this.input));

View File

@ -22,10 +22,13 @@ class WorkerWaiter {
this.app = app;
this.manager = manager;
this.loaded = false;
this.chefWorkers = [];
this.maxWorkers = navigator.hardwareConcurrency || 4;
this.inputs = [];
this.inputNums = [];
this.totalOutputs = 0;
this.loadingOutputs = 0;
this.bakeId = 0;
}
@ -47,14 +50,6 @@ class WorkerWaiter {
* @returns {number} The index of the created worker
*/
addChefWorker() {
// First find if there are any inactive workers, as this will be
// more efficient than creating a new one
for (let i = 0; i < this.chefWorkers.length; i++) {
if (!this.chefWorkers[i].active) {
return i;
}
}
if (this.chefWorkers.length === this.maxWorkers) {
// Can't create any more workers
return -1;
@ -88,6 +83,22 @@ class WorkerWaiter {
return this.chefWorkers.indexOf(newWorkerObj);
}
/**
* Gets an inactive ChefWorker to be used for baking
*
* @param {boolean} [setActive=true] - If true, set the worker status to active
* @returns {number} - The index of the ChefWorker
*/
getInactiveChefWorker(setActive=true) {
for (let i = 0; i < this.chefWorkers.length; i++) {
if (!this.chefWorkers[i].active) {
this.chefWorkers[i].active = setActive;
return i;
}
}
return -1;
}
/**
* Removes a ChefWorker
*
@ -172,7 +183,12 @@ class WorkerWaiter {
case "workerLoaded":
this.app.workerLoaded = true;
log.debug("ChefWorker loaded.");
this.app.loaded();
if (!this.loaded) {
this.app.loaded();
this.loaded = true;
} else {
this.bakeNextInput(this.getInactiveChefWorker(false));
}
break;
case "statusMessage":
// Status message should be done per output
@ -227,7 +243,7 @@ class WorkerWaiter {
* Get the progress of the ChefWorkers
*/
getBakeProgress() {
const pendingInputs = this.inputs.length;
const pendingInputs = this.inputNums.length + this.loadingOutputs + this.inputs.length;
let bakingInputs = 0;
for (let i = 0; i < this.chefWorkers.length; i++) {
@ -249,12 +265,17 @@ class WorkerWaiter {
/**
* Cancels the current bake by terminating and removing all ChefWorkers
*
* @param {boolean} [silent=false] - If true, don't set the output
* @param {boolean} killAll - If true, kills all chefWorkers regardless of status
*/
cancelBake() {
cancelBake(silent, killAll) {
for (let i = this.chefWorkers.length - 1; i >= 0; i--) {
const inputNum = this.chefWorkers[i].inputNum;
this.removeChefWorker(this.chefWorkers[i]);
this.manager.output.updateOutputStatus("inactive", inputNum);
if (this.chefWorkers[i].active || killAll) {
const inputNum = this.chefWorkers[i].inputNum;
this.removeChefWorker(this.chefWorkers[i]);
this.manager.output.updateOutputStatus("inactive", inputNum);
}
}
this.setBakingStatus(false);
@ -262,23 +283,32 @@ class WorkerWaiter {
this.manager.output.updateOutputStatus("inactive", this.inputs[i].inputNum);
}
for (let i = 0; i < this.inputNums.length; i++) {
this.manager.output.updateOutputStatus("inactive", this.inputNums[i]);
}
this.inputs = [];
this.inputNums = [];
this.totalOutputs = 0;
this.manager.output.set(this.manager.output.getActiveTab());
if (!silent) this.manager.output.set(this.manager.output.getActiveTab());
}
/**
* Handle a worker completing baking
*
* @param {object} workerObj - Object containing the worker information
* @param {ChefWorker} workerObj.worker - The actual worker object
* @param {number} workerObj.inputNum - The inputNum of the input being baked by the worker
* @param {boolean} workerObj.active - If true, the worker is currrently baking an input
*/
workerFinished(workerObj) {
const workerIdx = this.chefWorkers.indexOf(workerObj);
this.chefWorkers[workerIdx].active = false;
if (this.inputs.length > 0) {
this.bakeNextInput(this.chefWorkers.indexOf(workerObj));
} else {
this.bakeNextInput(workerIdx);
} else if (this.inputNums.length === 0 && this.loadingOutputs === 0) {
// The ChefWorker is no longer needed
log.debug("No more inputs to bake. Closing ChefWorker.");
workerObj.active = false;
this.removeChefWorker(workerObj);
log.debug("No more inputs to bake.");
const progress = this.getBakeProgress();
if (progress.total === progress.baked) {
this.bakingComplete();
@ -313,15 +343,15 @@ class WorkerWaiter {
}
/**
* Bakes the next input
* Bakes the next input and tells the inputWorker to load the next input
*
* @param {number} workerIdx
* @param {number} workerIdx - The index of the worker to bake with
*/
bakeNextInput(workerIdx) {
if (this.inputs.length === 0) return;
if (workerIdx === -1) return;
if (!this.chefWorkers[workerIdx]) return;
this.chefWorkers[workerIdx].active = true;
const nextInput = this.inputs.splice(0, 1)[0];
if (typeof nextInput.inputNum === "string") nextInput.inputNum = parseInt(nextInput.inputNum, 10);
@ -330,7 +360,6 @@ class WorkerWaiter {
this.manager.output.updateOutputStatus("baking", nextInput.inputNum);
this.chefWorkers[workerIdx].inputNum = nextInput.inputNum;
this.chefWorkers[workerIdx].active = true;
const input = nextInput.input;
if (input instanceof ArrayBuffer || ArrayBuffer.isView(input)) {
this.chefWorkers[workerIdx].worker.postMessage({
@ -359,6 +388,17 @@ class WorkerWaiter {
}
});
}
if (this.inputNums.length > 0) {
this.manager.input.inputWorker.postMessage({
action: "bakeNext",
data: {
inputNum: this.inputNums.splice(0, 1)[0],
bakeId: this.bakeId
}
});
this.loadingOutputs++;
}
}
/**
@ -370,10 +410,6 @@ class WorkerWaiter {
* @param {boolean} step
*/
bake(recipeConfig, options, progress, step) {
for (let i = this.chefWorkers.length - 1; i >= 0; i--) {
this.removeChefWorker(this.chefWorkers[i]);
}
this.setBakingStatus(true);
this.manager.recipe.updateBreakpointIndicator(false);
this.bakeStartTime = new Date().getTime();
@ -383,15 +419,6 @@ class WorkerWaiter {
this.progress = progress;
this.step = step;
let numWorkers = this.maxWorkers;
if (this.inputs.length < numWorkers) {
numWorkers = this.inputs.length;
}
for (let i = 0; i < numWorkers; i++) {
const workerIdx = this.addChefWorker();
if (workerIdx === -1) break;
this.bakeNextInput(workerIdx);
}
this.displayProgress();
}
@ -401,28 +428,65 @@ class WorkerWaiter {
* @param {object} inputData
* @param {string | ArrayBuffer} inputData.input
* @param {number} inputData.inputNum
* @param {boolean} inputData.override
* @param {number} inputData.bakeId
*/
queueInput(inputData) {
for (let i = 0; i < this.chefWorkers; i++) {
if (this.chefWorkers[i].inputNum === inputData.inputNum) {
this.chefWorkers[i].worker.terminate();
this.chefWorkers.splice(i, 1);
this.bakeNextInput(this.addChefWorker());
this.bakingInputs--;
break;
this.loadingOutputs--;
if (this.app.baking && inputData.bakeId === this.bakeId) {
this.inputs.push(inputData);
this.bakeNextInput(this.getInactiveChefWorker(true));
}
}
/**
* Queues a list of inputNums to be baked by ChefWorkers, and begins baking
*
* @param {object} inputData
* @param {number[]} inputData.nums
* @param {boolean} inputData.step
*/
bakeAllInputs(inputData) {
if (this.app.baking) return;
const inputNums = inputData.nums;
const step = inputData.step;
// Use cancelBake to clear out the inputs
this.cancelBake(true, false);
this.inputNums = inputNums;
this.totalOutputs = inputNums.length;
let inactiveWorkers = 0;
for (let i = 0; i < this.chefWorkers.length; i++) {
if (!this.chefWorkers[i].active) {
inactiveWorkers++;
}
}
this.manager.output.updateOutputMessage(`Input ${inputData.inputNum} has not been baked yet.`, inputData.inputNum, false);
this.manager.output.updateOutputStatus("pending", inputData.inputNum);
let numWorkers = (inputNums.length > this.maxWorkers) ? this.maxWorkers : inputNums.length;
numWorkers -= inactiveWorkers;
if (inputData.override) {
this.totalOutputs = 1;
this.inputs = [inputData];
} else {
this.totalOutputs++;
this.inputs.push(inputData);
for (let i = 0; i < numWorkers; i++) {
this.addChefWorker();
}
this.app.bake(step);
for (let i = 0; i < numWorkers + inactiveWorkers; i++) {
this.manager.input.inputWorker.postMessage({
action: "bakeNext",
data: {
inputNum: this.inputNums.splice(0, 1)[0],
bakeId: this.bakeId
}
});
this.loadingOutputs++;
}
for (let i = 0; i < this.inputNums.length; i++) {
this.manager.output.updateOutputMessage(`Input ${inputNums[i]} has not been baked yet.`, inputNums[i], false);
this.manager.output.updateOutputStatus("pending", inputNums[i]);
}
}
@ -433,9 +497,11 @@ class WorkerWaiter {
* @param {Object[]} [recipeConfig]
*/
silentBake(recipeConfig) {
// If there aren't any active ChefWorkers, addChefWorker will
// return an inactive worker instead of creating a new one
const workerId = this.addChefWorker();
// If there aren't any active ChefWorkers, try to add one
let workerId = this.getInactiveChefWorker();
if (workerId === -1) {
workerId = this.addChefWorker();
}
if (workerId === -1) return;
this.chefWorkers[workerId].worker.postMessage({
action: "silentBake",
@ -487,7 +553,6 @@ class WorkerWaiter {
*/
displayProgress() {
const progress = this.getBakeProgress();
if (progress.total === progress.baked) return;
const percentComplete = ((progress.pending + progress.baking) / progress.total) * 100;

View File

@ -218,15 +218,6 @@
<div class="title no-select">
<label for="input-text">Input</label>
<span class="float-right">
<!-- <button type="button" class="btn btn-primary bmd-btn-icon input-tab-buttons" id="btn-previous-input-tab" data-toggle="tooltip" title="Go to the previous tab">
<i class="material-icons">keyboard_arrow_left</i>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon input-tab-buttons" id="btn-go-to-input-tab" data-toggle="tooltip" title="Go to a specific tab">
<i class="material-icons">more_horiz</i>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon input-tab-buttons" id="btn-next-input-tab" data-toggle="tooltip" title="Go to the next tab">
<i class="material-icons">keyboard_arrow_right</i>
</button> -->
<button type="button" class="btn btn-primary bmd-btn-icon" id="btn-new-tab" data-toggle="tooltip" title="Add a new input tab">
<i class="material-icons">add</i>
</button>

View File

@ -58,7 +58,6 @@
border-bottom: 1px solid var(--primary-border-colour);
border-left: 1px solid var(--primary-border-colour);
height: var(--tab-height);
width: calc(100% - 75px);
clear: none;
}

View File

@ -82,7 +82,6 @@ module.exports = {
browser
.useCss()
.setValue("#input-text", "Don't Panic.")
.waitForElementNotVisible("#stale-indicator", 1000)
.click("#bake");
// Check output