- 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
/**
* @classdesc
* Object pooling - a technique that might speed up your game if used properly.<br>
* If some of your classes will be instantiated and removed a lot at a time, it is a
* good idea to add the class to this object pool. A separate pool for that class
* will be created, which will reuse objects of the class. That way they won't be instantiated
* each time you need a new one (slowing your game), but stored into that pool and taking one
* already instantiated when you need it.<br><br>
* This technique is also used by the engine to instantiate objects defined in the map,
* which means, that on level loading the engine will try to instantiate every object
* found in the map, based on the user defined name in each Object Properties<br>
* <img src="images/object_properties.png"/><br>
* @see pool the default global instance of ObjectPool
*/
class ObjectPool {
constructor() {
this.objectClass = {};
this.instance_counter = 0;
}
/**
* register an object to the pool. <br>
* Pooling must be set to true if more than one such objects will be created. <br>
* (Note: for an object to be poolable, it must implements a `onResetEvent` method)
* @param {string} className - as defined in the Name field of the Object Properties (in Tiled)
* @param {object} classObj - corresponding Class to be instantiated
* @param {boolean} [recycling=false] - enables object recycling for the specified class
* @example
* // implement CherryEntity
* class Cherry extends Sprite {
* onResetEvent() {
* // reset object mutable properties
* this.lifeBar = 100;
* }
* };
* // add our users defined entities in the object pool and enable object recycling
* me.pool.register("cherrysprite", Cherry, true);
*/
register(className, classObj, recycling = false) {
if (typeof (classObj) !== "undefined") {
this.objectClass[className] = {
"class" : classObj,
"pool" : (recycling ? [] : undefined)
};
} else {
throw new Error("Cannot register object '" + className + "', invalid class");
}
}
/**
* Pull a new instance of the requested object (if added into the object pool)
* @param {string} name - as used in {@link pool.register}
* @param {...*} [args] - arguments to be passed when instantiating/reinitializing the object
* @returns {object} the instance of the requested object
* @example
* me.pool.register("bullet", BulletEntity, true);
* me.pool.register("enemy", EnemyEntity, true);
* // ...
* // when we need to manually create a new bullet:
* let bullet = me.pool.pull("bullet", x, y, direction);
* // ...
* // params aren't a fixed number
* // when we need new enemy we can add more params, that the object construct requires:
* let enemy = me.pool.pull("enemy", x, y, direction, speed, power, life);
* // ...
* // when we want to destroy existing object, the remove
* // function will ensure the object can then be reallocated later
* me.game.world.removeChild(enemy);
* me.game.world.removeChild(bullet);
*/
pull(name, ...args) {
let className = this.objectClass[name];
if (className) {
let proto = className["class"],
poolArray = className.pool,
obj;
if (poolArray && ((obj = poolArray.pop()))) {
// poolable object must implement a `onResetEvent` method
obj.onResetEvent.apply(obj, args);
this.instance_counter--;
} else {
// create a new instance
obj = new (proto.bind.apply(proto, [ proto, ...args ]))();
if (poolArray) {
obj.className = name;
}
}
return obj;
}
throw new Error("Cannot instantiate object of type '" + name + "'");
}
/**
* purge the object pool from any inactive object <br>
* Object pooling must be enabled for this function to work<br>
* note: this will trigger the garbage collector
*/
purge() {
for (let className in this.objectClass) {
if (this.objectClass[className]) {
this.objectClass[className].pool = [];
}
}
this.instance_counter = 0;
}
/**
* Push back an object instance into the object pool <br>
* Object pooling for the object class must be enabled,
* and object must have been instantiated using {@link pool#pull},
* otherwise this function won't work
* @throws will throw an error if the object cannot be recycled
* @param {object} obj - instance to be recycled
* @param {boolean} [throwOnError=true] - throw an exception if the object cannot be recycled
* @returns {boolean} true if the object was successfully recycled in the object pool
*/
push(obj, throwOnError = true) {
if (!this.poolable(obj)) {
if (throwOnError === true ) {
throw new Error("me.pool: object " + obj + " cannot be recycled");
} else {
return false;
}
}
// store back the object instance for later recycling
this.objectClass[obj.className].pool.push(obj);
this.instance_counter++;
return true;
}
/**
* Check if an object with the provided name is registered
* @param {string} name - of the registered object class
* @returns {boolean} true if the classname is registered
*/
exists(name) {
return name in this.objectClass;
}
/**
* Check if an object is poolable
* (was properly registered with the recycling feature enable)
* @see register
* @param {object} obj - object to be checked
* @returns {boolean} true if the object is poolable
* @example
* if (!me.pool.poolable(myCherryEntity)) {
* // object was not properly registered
* }
*/
poolable(obj) {
let className = obj.className;
return (typeof className !== "undefined") &&
(typeof obj.onResetEvent === "function") &&
(className in this.objectClass) &&
(typeof this.objectClass[className].pool !== "undefined");
}
/**
* returns the amount of object instance currently in the pool
* @returns {number} amount of object instance
*/
getInstanceCount() {
return this.instance_counter;
}
}
let pool = new ObjectPool();
/**
* a default global ObjectPool instance
* @namespace pool
* @see ObjectPool
* @example
* // register our bullet object into the object pool
* pool.register("bullet", BulletEntity, true);
* // ...
* // when we need to manually create a new bullet:
* let bullet = pool.pull("bullet", x, y, direction, velocity);
* // ...
* // when we want to destroy existing object, the remove
* // function will ensure the object can then be reallocated later
* game.world.removeChild(bullet);
*/
export default pool;