@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
273 lines (213 loc) • 6.24 kB
JavaScript
import { assert } from "../../../core/assert.js";
import Signal from "../../../core/events/signal/Signal.js";
import { noop } from "../../../core/function/noop.js";
import { max2 } from "../../../core/math/max2.js";
import { min2 } from "../../../core/math/min2.js";
import AnimationTrackPlayback from "../../animation/keyed2/AnimationTrackPlayback.js";
/**
* @template T
*/
class EmitterRecord {
/**
*
* @param {T} object
*/
constructor(object) {
/**
*
* @type {T}
*/
this.object = object;
/**
*
* @type {AnimationTrackPlayback}
*/
this.playback = null;
}
}
/**
* @template T,O
*/
export class AnimatedObjectEmitter {
constructor() {
/**
*
* @type {AnimationTrack}
*/
this.animation = null;
/**
*
* @type {Function}
*/
this.animationUpdater = null;
/**
*
* @type {Function}
*/
this.objectFactory = null;
/**
*
* @type {Function}
*/
this.objectInitializer = noop;
/**
*
* @type {Function}
*/
this.objectFinalizer = noop;
/**
*
* @type {EmitterRecord<T>[]}
*/
this.elements = [];
/**
* Maximum number of elements to be displayed simultaneously, beyond this number animation speed dialation starts
* @type {number}
*/
this.rushThreshold = Number.POSITIVE_INFINITY;
/**
* Minimum time that must pass between objects being emitted (in Seconds)
* Objects spawned more often than that will be buffered and released at regular intervals
* @type {number}
*/
this.spawnDelay = 0;
/**
*
* @type {number}
* @private
*/
this.__spawnTimeBudget = 0;
/**
*
* @type {O[]}
* @private
*/
this.__spawnBuffer = [];
this.on = {
spanwed: new Signal(),
removed: new Signal()
};
/**
* Number of active objects
* @type {number}
*/
this.liveCount = 0;
}
/**
*
* @param {AnimationTrack} animationTrack
* @param {function} updater
*/
setAnimation(animationTrack, updater) {
this.animation = animationTrack;
this.animationUpdater = updater;
}
/**
*
* @param {function(T)} init
*/
setInitializer(init) {
this.objectInitializer = init;
}
/**
*
* @param {function(T)} fin
*/
setFinalizer(fin) {
this.objectFinalizer = fin;
}
/**
* @param {O} options
*/
spawn(options) {
assert.notEqual(options, undefined, 'options is undefined');
this.liveCount++;
if (this.__spawnTimeBudget < this.spawnDelay) {
this.__spawnBuffer.push(options);
} else {
this.spawnImmediate(options);
this.__spawnTimeBudget = 0;
}
}
spawnImmediate(options) {
assert.notEqual(options, undefined, 'options is undefined');
assert.notEqual(options, null, 'options is null');
const object = this.objectFactory(options);
const playback = new AnimationTrackPlayback(this.animation, this.animationUpdater, object);
assert.notEqual(object, undefined, 'object is undefined');
playback.reset();
playback.update();
this.objectInitializer(object);
const self = this;
playback.on.ended.add(function () {
if (!self.remove(object)) {
console.error('Playback ended, and object not found', object, self);
}
});
const item = new EmitterRecord(object);
item.playback = playback;
this.elements.push(item);
this.on.spanwed.dispatch(object);
return item;
}
trySpawnDeferred() {
const spawnBuffer = this.__spawnBuffer;
let spawnBufferSize = spawnBuffer.length;
if (spawnBufferSize === 0) {
return;
}
const d = this.__spawnTimeBudget;
let i = 0;
const n = min2(
Math.floor(d / this.spawnDelay),
spawnBufferSize
);
const itemsToSpawn = spawnBuffer.splice(0, n);
for (i = 0; i < n; i++) {
const opt = itemsToSpawn[i];
const item = this.spawnImmediate(opt);
// advance animation
item.playback.advance((n - (i + 1)) * this.spawnDelay);
}
this.__spawnTimeBudget %= this.spawnDelay;
}
/**
*
* @param {T} object
*/
remove(object) {
const elements = this.elements;
for (let i = 0, l = elements.length; i < l; i++) {
/**
*
* @type {EmitterRecord<T>}
*/
const element = elements[i];
if (element.object === object) {
elements.splice(i, 1);
this.liveCount--;
this.objectFinalizer(object);
this.on.removed.send1(object);
return true;
}
}
return false;
}
/**
*
* @param {number} timeDelta
*/
tick(timeDelta) {
const elements = this.elements;
const numElements = elements.length;
for (let i = numElements - 1; i >= 0; i--) {
const el = elements[i];
const playback = el.playback;
const adjustedSpeed = max2(1, (numElements - i) / this.rushThreshold);
const speedAdjustedDelta = adjustedSpeed * timeDelta;
playback.advance(speedAdjustedDelta);
}
this.__spawnTimeBudget += timeDelta;
this.trySpawnDeferred();
}
}