File size: 8,340 Bytes
a32dc8b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
/**
 * 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 };