import K from 'kefir'; import * as easings from '@/util/easing.js'; import {TAU} from '@/util/math.js'; export default class Emitter { constructor(ecs) { this.ecs = ecs; this.scheduled = []; } async configure(entityId, {fields, shape}) { const entity = this.ecs.get(entityId); if (shape) { switch (shape.type) { case 'circle': { const r = Math.random() * TAU; entity.Position.x += Math.cos(r) * shape.payload.radius; entity.Position.y += Math.sin(r) * shape.payload.radius; break; } case 'filledCircle': { const r = Math.random() * TAU; entity.Position.x += Math.cos(r) * Math.random() * shape.payload.radius; entity.Position.y += Math.sin(r) * Math.random() * shape.payload.radius; break; } case 'filledRect': { entity.Position.x += Math.random() * shape.payload.width - (shape.payload.width / 2); entity.Position.y += Math.random() * shape.payload.height - (shape.payload.height / 2); break; } } } if (fields) { for (const {easing = 'linear', path, value} of fields) { let walk = entity; const pathCopy = path.slice(0); const final = pathCopy.pop(); for (const key of pathCopy) { walk = walk[key]; } const c = Math.random() * (value.length - 1); const i = Math.floor(c); walk[final] = easings[easing](c - i, value[i], value[i + 1] - value[i], 1); } } return entity; } emit(particle) { particle = { ...particle, entity: { Position: {}, Sprite: {}, ...particle.entity, }, } let {count = 1} = particle; const {entity, frequency = 0, spurt = 1, ttl = 0} = particle; if (ttl > 0) { count = Math.floor(ttl / frequency); } const specifications = Array(count); for (let i = 0; i < count * spurt; ++i) { specifications[i] = entity; } const stream = K.stream(async (emitter) => { const entityIds = await this.ecs.createManyDetached(specifications); if (0 === frequency) { this.ecs.attach(entityIds); for (const entityId of entityIds) { emitter.emit(this.configure(entityId, particle)); } emitter.end(); return; } const it = entityIds.values(); const batch = new Set(); for (let i = 0; i < spurt; ++i) { batch.add(it.next().value); } this.ecs.attach(batch); for (const entityId of batch) { emitter.emit(this.configure(entityId, particle)); } count -= 1; if (0 === count) { emitter.end(); return; } let accumulated = 0; const scheduled = (elapsed) => { accumulated += elapsed; while (accumulated > frequency && count > 0) { const batch = new Set(); for (let i = 0; i < spurt; ++i) { batch.add(it.next().value); } this.ecs.attach(batch); for (const entityId of batch) { emitter.emit(this.configure(entityId, particle)); } accumulated -= frequency; count -= 1; } if (0 === count) { this.scheduled.splice(this.scheduled.indexOf(scheduled), 1); emitter.end(); } }; this.scheduled.push(scheduled); }); return stream; } tick(elapsed) { for (const ticker of this.scheduled) { ticker(elapsed); } } }