/** * Performance Optimization Techniques for Multiple AI Entities * * This implementation includes various optimization techniques to efficiently * manage multiple AI entities in a village simulation. */ class AIEntityManager { constructor() { this.entities = []; this.activeEntities = new Set(); // Set of currently active entities this.updateQueue = []; // Queue for entity updates this.maxUpdatesPerFrame = 10; // Maximum entities to update per frame this.spatialPartitioning = new SpatialPartitioning(100, 100, 10); // 100x100 grid with 10 unit cells } /** * Add an entity to the manager * @param {Object} entity - Entity to add */ addEntity(entity) { this.entities.push(entity); this.spatialPartitioning.insert(entity); } /** * Remove an entity from the manager * @param {Object} entity - Entity to remove */ removeEntity(entity) { const index = this.entities.indexOf(entity); if (index !== -1) { this.entities.splice(index, 1); } this.spatialPartitioning.remove(entity); this.activeEntities.delete(entity); } /** * Update all entities with optimization techniques * @param {number} deltaTime - Time since last frame */ update(deltaTime) { // Update spatial partitioning this.spatialPartitioning.update(); // Reset active entities this.activeEntities.clear(); // Determine active entities (those near the camera or player) this.determineActiveEntities(); // Update entity queue this.updateEntityQueue(); // Update entities const updatesThisFrame = Math.min(this.maxUpdatesPerFrame, this.updateQueue.length); for (let i = 0; i < updatesThisFrame; i++) { const entity = this.updateQueue.shift(); if (entity) { // Note: Entity updates are handled by the main system // This is just for optimization management this.updateQueue.push(entity); // Put back at end of queue } } } /** * Determine which entities are active based on proximity to camera */ determineActiveEntities() { // In a real implementation, this would use the camera position // For now, we'll mark all entities as active for (const entity of this.entities) { this.activeEntities.add(entity); } } /** * Update the entity queue with active entities */ updateEntityQueue() { // Add newly active entities to queue for (const entity of this.activeEntities) { if (!this.updateQueue.includes(entity)) { this.updateQueue.push(entity); } } // Remove inactive entities from queue this.updateQueue = this.updateQueue.filter(entity => this.activeEntities.has(entity)); } /** * Get nearby entities for a position * @param {Array} position - Position to check [x, y, z] * @param {number} radius - Radius to check * @returns {Array} Nearby entities */ getNearbyEntities(position, radius) { return this.spatialPartitioning.query(position, radius); } } /** * Spatial Partitioning for efficient entity queries */ class SpatialPartitioning { constructor(width, height, cellSize) { this.width = width; this.height = height; this.cellSize = cellSize; this.grid = new Map(); // Map of cell keys to arrays of entities this.entityToCell = new Map(); // Map of entities to their current cell } /** * Get cell key for a position * @param {Array} position - Position to get cell for [x, y, z] * @returns {string} Cell key */ getCellKey(position) { const col = Math.floor(position[0] / this.cellSize); const row = Math.floor(position[2] / this.cellSize); return `${col},${row}`; } /** * Insert an entity into the spatial partitioning * @param {Object} entity - Entity to insert */ insert(entity) { const key = this.getCellKey(entity.position); if (!this.grid.has(key)) { this.grid.set(key, []); } this.grid.get(key).push(entity); this.entityToCell.set(entity, key); } /** * Remove an entity from the spatial partitioning * @param {Object} entity - Entity to remove */ remove(entity) { const key = this.entityToCell.get(entity); if (key && this.grid.has(key)) { const cell = this.grid.get(key); const index = cell.indexOf(entity); if (index !== -1) { cell.splice(index, 1); } } this.entityToCell.delete(entity); } /** * Update an entity's position in the spatial partitioning * @param {Object} entity - Entity to update */ updateEntity(entity) { const oldKey = this.entityToCell.get(entity); const newKey = this.getCellKey(entity.position); if (oldKey !== newKey) { // Remove from old cell if (oldKey && this.grid.has(oldKey)) { const oldCell = this.grid.get(oldKey); const index = oldCell.indexOf(entity); if (index !== -1) { oldCell.splice(index, 1); } } // Add to new cell if (!this.grid.has(newKey)) { this.grid.set(newKey, []); } this.grid.get(newKey).push(entity); this.entityToCell.set(entity, newKey); } } /** * Update all entities in the spatial partitioning */ update() { for (const entity of this.entityToCell.keys()) { this.updateEntity(entity); } } /** * Query entities within a radius * @param {Array} position - Center position [x, y, z] * @param {number} radius - Query radius * @returns {Array} Entities within radius */ query(position, radius) { const results = []; const col = Math.floor(position[0] / this.cellSize); const row = Math.floor(position[2] / this.cellSize); const radiusCells = Math.ceil(radius / this.cellSize); // Check surrounding cells for (let r = row - radiusCells; r <= row + radiusCells; r++) { for (let c = col - radiusCells; c <= col + radiusCells; c++) { const key = `${c},${r}`; if (this.grid.has(key)) { const cell = this.grid.get(key); for (const entity of cell) { if (this.calculateDistance(position, entity.position) <= radius) { results.push(entity); } } } } } return results; } /** * Calculate distance between two positions * @param {Array} pos1 - First position [x, y, z] * @param {Array} pos2 - Second position [x, y, z] * @returns {number} Distance between positions */ calculateDistance(pos1, pos2) { return Math.sqrt( Math.pow(pos1[0] - pos2[0], 2) + Math.pow(pos1[1] - pos2[1], 2) + Math.pow(pos1[2] - pos2[2], 2) ); } } /** * Object pooling for AI entities to reduce garbage collection */ class ObjectPool { constructor(createFn, resetFn, initialSize = 10) { this.createFn = createFn; this.resetFn = resetFn; this.pool = []; // Pre-populate pool for (let i = 0; i < initialSize; i++) { this.pool.push(this.createFn()); } } /** * Get an object from the pool * @returns {Object} Object from pool or new object if pool is empty */ acquire() { if (this.pool.length > 0) { return this.pool.pop(); } return this.createFn(); } /** * Return an object to the pool * @param {Object} object - Object to return */ release(object) { this.resetFn(object); this.pool.push(object); } } export { AIEntityManager, SpatialPartitioning, ObjectPool };