@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
255 lines (202 loc) • 6.05 kB
JavaScript
import { assert } from "../../assert.js";
import LineBuilder from "../../codegen/LineBuilder.js";
import { array_push_if_unique } from "../../collection/array/array_push_if_unique.js";
import Signal from "../../events/signal/Signal.js";
import { objectKeyByValue } from "../../model/object/objectKeyByValue.js";
import ObservedInteger from "../../model/ObservedInteger.js";
import Task from "./Task.js";
import TaskState from "./TaskState.js";
class TaskGroup {
/**
*
* @param {(Task|TaskGroup)[]} subtasks
* @param {string} [name]
* @constructor
*/
constructor(subtasks, name = 'Unnamed') {
assert.isArray(subtasks, 'subtasks');
assert.isString(name, 'name');
/**
*
* @type {string}
*/
this.name = name;
/**
*
* @type {(Task|TaskGroup)[]}
*/
this.children = subtasks;
this.on = {
started: new Signal(),
completed: new Signal(),
failed: new Signal()
};
/**
*
* @type {ObservedInteger}
*/
this.state = new ObservedInteger(TaskState.INITIAL);
}
/**
* Time in milliseconds that the task has been executing for, suspended time does not count
* @returns {number}
*/
getExecutedCpuTime() {
let result = 0;
const children = this.children;
const n = children.length;
for (let i = 0; i < n; i++) {
const child = children[i];
const time = child.getExecutedCpuTime();
result += time;
}
return result;
}
/**
*
* @param {Task|TaskGroup} child
* @returns {boolean}
*/
addChild(child) {
return array_push_if_unique(this.children, child);
}
/**
*
* @param {(Task|TaskGroup)[]} children
*/
addChildren(children) {
const n = children.length;
for (let i = 0; i < n; i++) {
const child = children[i];
this.addChild(child);
}
}
/**
*
* @param {Task|TaskGroup} dependency
*/
addDependency(dependency) {
if (dependency.isTaskGroup) {
this.addDependencies(dependency.children);
} else {
const children = this.children;
const n = children.length;
for (let i = 0; i < n; i++) {
const child = children[i];
child.addDependency(dependency);
}
}
}
addDependencies(dependencies) {
const n = dependencies.length;
for (let i = 0; i < n; i++) {
const dependency = dependencies[i];
this.addDependency(dependency);
}
}
/**
*
* @returns {number}
*/
getEstimatedDuration() {
let result = 0;
const children = this.children;
const n = children.length;
for (let i = 0; i < n; i++) {
const child = children[i];
const childDuration = child.getEstimatedDuration();
if (!(isNaN(childDuration) || childDuration < 0)) {
result += childDuration;
}
}
return result;
}
/**
* Dumps task group tree along with state of each task
* @returns {string}
*/
getVerboseStatusMessage() {
const b = new LineBuilder();
/**
*
* @param {Task|TaskGroup} t
*/
function addTask(t) {
if (t.isTaskGroup) {
b.add(`group ['${t.name}']`)
b.indent();
for (let i = 0; i < t.children.length; i++) {
addTask(t.children[i]);
}
b.dedent();
} else {
b.add(`task ['${t.name}'] ${objectKeyByValue(TaskState, t.state.getValue())}`)
}
}
addTask(this);
return b.build();
}
computeProgress() {
const children = this.children;
const numChildren = children.length;
let progressSum = 0;
let progressTotal = 0;
for (let i = 0; i < numChildren; i++) {
/**
*
* @type {Task|TaskGroup}
*/
const child = children[i];
const estimatedDuration = child.getEstimatedDuration();
if (isNaN(estimatedDuration)) {
//Duration is not a number, ignore this child
continue;
}
const childProgress = computeTaskProgress(child);
assert.ok(childProgress >= 0 && childProgress <= 1, `Expected progress to be between 0 and 1, instead was '${childProgress}' for child '${child.name}'`);
progressSum += childProgress * estimatedDuration;
progressTotal += estimatedDuration;
}
if (progressTotal === 0) {
return 0;
} else {
return progressSum / progressTotal;
}
}
/**
*
* @param resolve
* @param reject
*/
join(resolve, reject) {
Task.join(this, resolve, reject);
}
/**
*
* @returns {Promise<unknown>}
*/
promise() {
return new Promise((resolve, reject) => this.join(resolve, reject));
}
}
/**
* @readonly
* @type {boolean}
*/
TaskGroup.prototype.isTaskGroup = true;
/**
*
* @param {Task|TaskGroup} child
* @returns {number}
*/
function computeTaskProgress(child) {
if (child.isTask) {
const state = child.state.getValue();
if (state === TaskState.INITIAL || state === TaskState.READY) {
// not started yet
return 0;
}
}
return child.computeProgress();
}
export default TaskGroup;