From c2c3f84a0aa9c2b41b9ef7c93622edf3dc4aef24 Mon Sep 17 00:00:00 2001 From: Olivier Biot Date: Sun, 18 Aug 2024 10:05:09 +0800 Subject: [PATCH] convert `Particle` to TS and the new pooling system also fix the legacy pool system not reporting instances from the new pool implementation (temporary while the 2 co-exists) --- packages/melonjs/src/index.js | 2 +- packages/melonjs/src/particles/emitter.js | 6 +- .../particles/{particle.js => particle.ts} | 79 +++++++++++++------ packages/melonjs/src/pool.ts | 10 +++ packages/melonjs/src/system/legacy_pool.js | 4 +- 5 files changed, 74 insertions(+), 27 deletions(-) rename packages/melonjs/src/particles/{particle.js => particle.ts} (70%) diff --git a/packages/melonjs/src/index.js b/packages/melonjs/src/index.js index eceba0951..c44623d46 100644 --- a/packages/melonjs/src/index.js +++ b/packages/melonjs/src/index.js @@ -48,7 +48,7 @@ import Container from "./renderable/container.js"; import World from "./physics/world.js"; import ParticleEmitterSettings from "./particles/settings.js"; import ParticleEmitter from "./particles/emitter.js"; -import Particle from "./particles/particle.js"; +import Particle from "./particles/particle.ts"; import Entity from "./renderable/entity/entity.js"; import Application from "./application/application.js"; diff --git a/packages/melonjs/src/particles/emitter.js b/packages/melonjs/src/particles/emitter.js index 56b53b29e..9e20e5d67 100644 --- a/packages/melonjs/src/particles/emitter.js +++ b/packages/melonjs/src/particles/emitter.js @@ -1,4 +1,4 @@ -import pool from "../system/legacy_pool.js"; +import { particlePool } from "./particle.ts"; import ParticleEmitterSettings from "./settings.js"; import { randomFloat } from "./../math/math.ts"; import Container from "./../renderable/container.js"; @@ -142,7 +142,7 @@ export default class ParticleEmitter extends Container { addParticles(count) { for (let i = 0; i < count; i++) { // Add particle to the container - this.addChild(pool.pull("Particle", this), this.pos.z); + this.addChild(particlePool.get(this), this.pos.z); } this.isDirty = true; } @@ -252,7 +252,7 @@ export default class ParticleEmitter extends Container { super.destroy(arguments); // clean emitter specific Properties if (typeof this._defaultParticle !== "undefined") { - pool.push(this._defaultParticle); + this._defaultParticle.destroy(); this._defaultParticle = undefined; } this.settings.image = undefined; diff --git a/packages/melonjs/src/particles/particle.js b/packages/melonjs/src/particles/particle.ts similarity index 70% rename from packages/melonjs/src/particles/particle.js rename to packages/melonjs/src/particles/particle.ts index ba4ca1103..e883be4c2 100644 --- a/packages/melonjs/src/particles/particle.js +++ b/packages/melonjs/src/particles/particle.ts @@ -1,7 +1,12 @@ -import timer from "./../system/timer.ts"; -import { randomFloat, clamp } from "./../math/math.ts"; -import Renderable from "./../renderable/renderable.js"; -import { vector2dPool } from "../math/vector2d.ts"; +import timer from "../system/timer.ts"; +import { randomFloat, clamp } from "../math/math.ts"; +import Renderable from "../renderable/renderable.js"; +import { Vector2d, vector2dPool } from "../math/vector2d.ts"; +import { createPool } from "../pool.ts"; +import ParticleEmitter from "./emitter.js"; +import CanvasRenderer from "../video/canvas/canvas_renderer.js"; +import WebGLRenderer from "../video/webgl/webgl_renderer.js"; +import Container from "../renderable/container.js"; /** * @import ParticleEmitter from "./emitter.js"; @@ -11,10 +16,24 @@ import { vector2dPool } from "../math/vector2d.ts"; * Single Particle Object. */ export default class Particle extends Renderable { + vel: Vector2d; + image: any; + life: number; + startLife: number; + startScale: number; + endScale: number; + gravity: number; + wind: number; + followTrajectory: boolean; + onlyInViewport: boolean; + _deltaInv: number; + _angle: number; + alive: boolean; + /** - * @param {ParticleEmitter} emitter - the particle emitter + * @param emitter - the particle emitter */ - constructor(emitter) { + constructor(emitter: ParticleEmitter) { // Call the super constructor super( emitter.getRandomPointX(), @@ -22,20 +41,19 @@ export default class Particle extends Renderable { emitter.settings.image.width, emitter.settings.image.height, ); + // particle velocity + this.vel = vector2dPool.get(); this.onResetEvent(emitter, true); } /** * @ignore */ - onResetEvent(emitter, newInstance = false) { - if (newInstance === false) { + onResetEvent(emitter: ParticleEmitter, newInstance: boolean = false) { + if (!newInstance) { this.pos.set(emitter.getRandomPointX(), emitter.getRandomPointY()); this.resize(emitter.settings.image.width, emitter.settings.image.height); this.currentTransform.identity(); - } else { - // particle velocity - this.vel = vector2dPool.get(); } this.image = emitter.settings.image; @@ -44,12 +62,10 @@ export default class Particle extends Renderable { this.alwaysUpdate = true; if (typeof emitter.settings.tint === "string") { - this.tint.parseCSS(emitter.settings.tint); + this.tint.parseCSS(emitter.settings.tint as any); } - if (emitter.settings.textureAdditive === true) { - this.blendMode = "additive"; - } + this.blendMode = emitter.settings.textureAdditive ? "additive" : "normal"; if (emitter.settings.blendMode !== "normal") { this.blendMode = emitter.settings.blendMode; @@ -106,28 +122,34 @@ export default class Particle extends Renderable { // Set the start particle rotation as defined in emitter // if the particle not follow trajectory if (!emitter.settings.followTrajectory) { - this.angle = randomFloat( + this._angle = randomFloat( emitter.settings.minRotation, emitter.settings.maxRotation, ); } + + this.alive = true; } /** * Update the Particle
* This is automatically called by the game manager {@link game} * @ignore - * @param {number} dt - time since the last update in milliseconds + * @param dt - time since the last update in milliseconds */ - update(dt) { + override update(dt: number) { // move things forward independent of the current frame rate const skew = dt * this._deltaInv; // Decrease particle life this.life = this.life > dt ? this.life - dt : 0; - if (this.life <= 0) { - this.ancestor.removeChild(this); + if (this.alive && this.life <= 0) { + const parent = this.ancestor as Container; + // use true for keepalive since we recycle the instance directly here after + parent.removeChild(this, true); + particlePool.release(this); + this.alive = false; return false; } @@ -154,7 +176,7 @@ export default class Particle extends Renderable { // If necessary update the rotation of particle in accordance the particle trajectory const angle = this.followTrajectory ? Math.atan2(this.vel.y, this.vel.x) - : this.angle; + : this._angle; this.pos.x += this.vel.x * skew; this.pos.y += this.vel.y * skew; @@ -173,9 +195,22 @@ export default class Particle extends Renderable { /** * @ignore */ - draw(renderer) { + override draw(renderer: CanvasRenderer | WebGLRenderer) { const w = this.width; const h = this.height; renderer.drawImage(this.image, 0, 0, w, h, -w / 2, -h / 2, w, h); } } + +export const particlePool = createPool( + (emitter) => { + const instance = new Particle(emitter); + + return { + instance, + reset(emitter) { + instance.onResetEvent(emitter, false); + }, + }; + }, +); diff --git a/packages/melonjs/src/pool.ts b/packages/melonjs/src/pool.ts index 212d4d847..4e1e3dc1f 100644 --- a/packages/melonjs/src/pool.ts +++ b/packages/melonjs/src/pool.ts @@ -9,6 +9,7 @@ import { matrix2dPool } from "./math/matrix2d"; import { matrix3dPool } from "./math/matrix3d"; import { vector2dPool } from "./math/vector2d"; import { vector3dPool } from "./math/vector3d"; +import { particlePool } from "./particles/particle"; import { boundsPool } from "./physics/bounds"; import { tweenPool } from "./tweens/tween"; @@ -26,6 +27,7 @@ const pools = { roundedRectangle: roundedRectanglePool, ellipse: ellipsePool, tween: tweenPool, + particle: particlePool, } as const; type PoolKey = keyof typeof pools; @@ -34,4 +36,12 @@ export const getPool = (key: K): (typeof pools)[K] => { return pools[key]; }; +export const getTotalPoolSize = (): number => { + let totalSize = 0; + for (const key in pools) { + totalSize += pools[key as PoolKey].size(); + } + return totalSize; +}; + export { createPool } from "./system/pool"; diff --git a/packages/melonjs/src/system/legacy_pool.js b/packages/melonjs/src/system/legacy_pool.js index c93da1b1c..f62619336 100644 --- a/packages/melonjs/src/system/legacy_pool.js +++ b/packages/melonjs/src/system/legacy_pool.js @@ -1,3 +1,5 @@ +import { getTotalPoolSize } from "../pool"; + /** * Object pooling - a technique that might speed up your game if used properly.
* If some of your classes will be instantiated and removed a lot at a time, it is a @@ -167,7 +169,7 @@ class ObjectPool { * @returns {number} amount of object instance */ getInstanceCount() { - return this.instance_counter; + return this.instance_counter + getTotalPoolSize(); } }