Spaces:
Running
Running
| /** | |
| * 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 }; |