@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
269 lines (201 loc) • 6.53 kB
JavaScript
import { assert } from "../../../../core/assert.js";
import { BitSet } from "../../../../core/binary/BitSet.js";
import { BehaviorStatus } from "../BehaviorStatus.js";
import { CompositeBehavior } from "./CompositeBehavior.js";
/**
*
* @enum {number}
*/
export const ParallelBehaviorPolicy = {
RequireOne: 0,
RequireAll: 1
};
/**
* Executes all contained behaviors in parallel.
*
* @example
* ParallelBehavior.from([
* ActionBehavior.from(()=> console.log("A")),
* ActionBehavior.from(()=> console.log("B")),
* ActionBehavior.from(()=> console.log("C")),
* ]); // will print A, B and C in console in a single tick
*
* @author Alex Goldring
* @copyright Company Named Limited (c) 2025
*/
export class ParallelBehavior extends CompositeBehavior {
/**
*
* @param {ParallelBehaviorPolicy} successPolicy
* @param {ParallelBehaviorPolicy} failurePolicy
*/
constructor(successPolicy, failurePolicy) {
super();
assert.enum(successPolicy, ParallelBehaviorPolicy, 'successPolicy');
assert.enum(failurePolicy, ParallelBehaviorPolicy, 'failurePolicy');
/**
* @private
* @type {ParallelBehaviorPolicy}
*/
this.successPolicy = successPolicy;
/**
* @private
* @type {ParallelBehaviorPolicy}
*/
this.failurePolicy = failurePolicy;
/**
* Which child behaviors are currently running?
* @readonly
* @private
* @type {BitSet}
*/
this.activeSet = new BitSet();
/**
* @private
* @type {number}
*/
this.successCount = 0;
/**
* @private
* @type {number}
*/
this.failureCount = 0;
}
/**
*
* @return {ParallelBehaviorPolicy}
*/
getSuccessPolicy() {
return this.successPolicy;
}
/**
*
* @param {ParallelBehaviorPolicy|number} policy
*/
setSuccessPolicy(policy) {
assert.enum(policy, ParallelBehaviorPolicy, 'policy');
this.successPolicy = policy;
}
/**
*
* @return {ParallelBehaviorPolicy}
*/
getFailurePolicy() {
return this.failurePolicy;
}
/**
*
* @param {ParallelBehaviorPolicy|number} policy
*/
setFailurePolicy(policy) {
assert.enum(policy, ParallelBehaviorPolicy, 'policy');
this.failurePolicy = policy;
}
/**
*
* @param {number} timeDelta
* @returns {BehaviorStatus|number}
*/
tick(timeDelta) {
const activeSet = this.activeSet;
/**
*
* @type {Behavior[]}
*/
const children = this.__children;
const numChildren = children.length;
let i;
for (i = 0; i < numChildren; i++) {
if (!activeSet.get(i)) {
continue;
}
const child = children[i];
const status = child.tick(timeDelta);
if (status === BehaviorStatus.Succeeded) {
activeSet.set(i, false);
this.successCount++;
child.finalize();
if (this.successPolicy === ParallelBehaviorPolicy.RequireOne) {
this.__finalizeActiveChildren();
return BehaviorStatus.Succeeded;
}
} else if (status === BehaviorStatus.Failed) {
activeSet.set(i, false);
this.failureCount++;
child.finalize();
if (this.failurePolicy === ParallelBehaviorPolicy.RequireOne) {
this.__finalizeActiveChildren();
return BehaviorStatus.Failed;
} else if (this.successPolicy === ParallelBehaviorPolicy.RequireAll) {
this.__finalizeActiveChildren();
return BehaviorStatus.Failed;
}
}
}
if (this.successCount === numChildren && this.successPolicy === ParallelBehaviorPolicy.RequireAll) {
return BehaviorStatus.Succeeded;
} else if (this.failureCount === numChildren && this.failurePolicy === ParallelBehaviorPolicy.RequireAll) {
return BehaviorStatus.Failed;
} else if ((this.failureCount + this.successCount) === numChildren) {
// this should never happen, we should fail before this
return BehaviorStatus.Failed;
} else {
return BehaviorStatus.Running;
}
}
initialize(context) {
this.successCount = 0;
this.failureCount = 0;
const children = this.__children;
const numChildren = children.length;
for (let i = 0; i < numChildren; i++) {
const behavior = children[i];
behavior.initialize(context);
this.activeSet.set(i, true);
}
super.initialize(context);
}
/**
*
* @private
*/
__finalizeActiveChildren() {
const children = this.__children;
const activeSet = this.activeSet;
for (let i = activeSet.nextSetBit(0); i !== -1; i = activeSet.nextSetBit(i + 1)) {
const behavior = children[i];
behavior.finalize();
}
}
finalize() {
//finalize remaining active behaviours
this.__finalizeActiveChildren();
super.finalize();
}
/**
* Default policies are fine for most use cases.
* @param {Behavior[]} children
* @param {ParallelBehaviorPolicy} [success] how should successful completion be determined?
* @param {ParallelBehaviorPolicy} [failure] how should failing completion be determined
* @returns {ParallelBehavior}
*/
static from(
children,
success = ParallelBehaviorPolicy.RequireAll,
failure = ParallelBehaviorPolicy.RequireOne
) {
const r = new ParallelBehavior(success, failure);
r.addChildren(children);
return r;
}
}
/**
* @readonly
* @type {boolean}
*/
ParallelBehavior.prototype.isParallelBehavior = true;
/**
* @readonly
* @type {string}
*/
ParallelBehavior.typeName = "ParallelBehavior";