Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 | 30x 30x 30x 30x 30x 1270x 1270x 563x 3x 560x 560x 560x 563x 563x 10x 9x 9x 9x 22x 22x 22x 22x 22x 22x 22x 5x 16x 16x 13x 13x 13x 3x 12x 1x 2x 1x 1x 1x | /**
* ParticlePool - Object pooling for particle systems
*
* Reuses particle objects instead of creating/destroying them
* to minimize garbage collection and improve performance.
*
* Features:
* - Pre-allocated particle pool
* - Acquire/release pattern for reuse
* - Automatic cleanup of expired particles
* - Memory-efficient for 1000+ particles
*
* @module components/shared/three/effects/ParticlePool
* @category Performance
* @korean 입자풀
*/
import * as THREE from "three";
/**
* Individual particle state
*/
export interface Particle {
/** Unique identifier */
id: string;
/** Current position in 3D space */
position: THREE.Vector3;
/** Current velocity vector */
velocity: THREE.Vector3;
/** Birth time in seconds */
startTime: number;
/** Particle lifetime in seconds */
lifetime: number;
/** Particle size */
size: number;
/** Particle color */
color: THREE.Color;
/** Whether particle is alive */
alive: boolean;
}
/**
* Configuration for particle pool
*/
export interface ParticlePoolConfig {
/** Maximum pool size */
readonly maxSize: number;
/** Default particle lifetime in seconds */
readonly defaultLifetime: number;
/** Default particle size */
readonly defaultSize: number;
/** Default particle color (hex value) */
readonly defaultColor?: number;
}
/**
* ParticlePool class for efficient particle management
*
* @example
* ```typescript
* const pool = new ParticlePool({ maxSize: 1000, defaultLifetime: 2.0, defaultSize: 0.1 });
*
* // Acquire particle from pool
* const particle = pool.acquire();
* if (particle) {
* particle.position.set(0, 0, 0);
* particle.velocity.set(1, 2, 0);
* particle.color.setHex(0x00ffff);
* }
*
* // Update pool (call every frame)
* pool.update(deltaTime);
*
* // Pool automatically releases expired particles
* ```
*/
export class ParticlePool {
private readonly pool: Particle[] = [];
private readonly active: Set<Particle> = new Set();
private readonly config: ParticlePoolConfig;
private nextId = 0;
constructor(config: ParticlePoolConfig) {
this.config = config;
// Pre-allocate particle objects
for (let i = 0; i < config.maxSize; i++) {
this.pool.push(this.createParticle());
}
}
/**
* Create a new particle object
*/
private createParticle(): Particle {
return {
id: `particle-${this.nextId++}`,
position: new THREE.Vector3(),
velocity: new THREE.Vector3(),
startTime: 0,
lifetime: this.config.defaultLifetime,
size: this.config.defaultSize,
color: new THREE.Color(this.config.defaultColor ?? 0xffffff),
alive: false,
};
}
/**
* Acquire a particle from the pool
*
* @param currentTimeSeconds Optional simulation time in seconds.
* If omitted, falls back to performance.now() / 1000.
* Using simulation time allows correct behavior during pause,
* slow-motion, or other time-manipulation scenarios.
* @returns Particle from pool or null if exhausted
*/
acquire(currentTimeSeconds?: number): Particle | null {
if (this.pool.length === 0) {
return null;
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- checked length > 0 above
const particle = this.pool.pop()!;
particle.alive = true;
particle.startTime =
currentTimeSeconds ?? performance.now() / 1000;
this.active.add(particle);
return particle;
}
/**
* Release a particle back to the pool
*/
release(particle: Particle): void {
if (!this.active.has(particle)) return;
this.active.delete(particle);
this.resetParticle(particle);
this.pool.push(particle);
}
/**
* Reset particle to default state
*/
private resetParticle(particle: Particle): void {
particle.position.set(0, 0, 0);
particle.velocity.set(0, 0, 0);
particle.startTime = 0;
particle.lifetime = this.config.defaultLifetime;
particle.size = this.config.defaultSize;
particle.color.setHex(this.config.defaultColor ?? 0xffffff);
particle.alive = false;
}
/**
* Update all active particles
* Automatically releases expired particles
*
* Optimized single-pass update to avoid per-frame temporary array allocations
*/
update(currentTime: number): void {
// Single-pass update and release to avoid creating temporary arrays
for (const particle of this.active) {
const age = currentTime - particle.startTime;
if (age >= particle.lifetime) {
// Directly release expired particle without creating a temporary array
this.active.delete(particle);
this.resetParticle(particle);
this.pool.push(particle);
}
}
}
/**
* Get all active particles
*/
getActive(): readonly Particle[] {
return Array.from(this.active);
}
/**
* Get pool statistics
*/
getStats(): {
total: number;
active: number;
available: number;
} {
return {
total: this.pool.length + this.active.size,
active: this.active.size,
available: this.pool.length,
};
}
/**
* Clear all particles and reset pool
*/
clear(): void {
// Release all active particles
const activeArray = Array.from(this.active);
activeArray.forEach((particle) => this.release(particle));
}
/**
* Dispose of pool resources
*/
dispose(): void {
this.clear();
this.pool.length = 0;
this.active.clear();
}
}
export default ParticlePool;
|