Medieval-Village-AI / src /ai /optimization.js
6rz6
Add Medieval Village AI Emulator
a32dc8b
/**
* 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 };