1 /** 2 * The Render Engine 3 * BaseObject 4 * 5 * @fileoverview An object that has functionality to assist in keeping memory 6 * usage down and to minimize the effect of the JavaScript garbage 7 * collector. 8 * 9 * @author: Brett Fattori (brettf@renderengine.com) 10 * @author: $Author: bfattori $ 11 * @version: $Revision: 1555 $ 12 * 13 * Copyright (c) 2011 Brett Fattori (brettf@renderengine.com) 14 * 15 * Permission is hereby granted, free of charge, to any person obtaining a copy 16 * of this software and associated documentation files (the "Software"), to deal 17 * in the Software without restriction, including without limitation the rights 18 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 * copies of the Software, and to permit persons to whom the Software is 20 * furnished to do so, subject to the following conditions: 21 * 22 * The above copyright notice and this permission notice shall be included in 23 * all copies or substantial portions of the Software. 24 * 25 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 31 * THE SOFTWARE. 32 * 33 */ 34 "use strict"; 35 36 // The class this file defines and its required classes 37 R.Engine.define({ 38 "class":"R.engine.PooledObject", 39 "requires":[] 40 }); 41 42 /** 43 * @class Pooled objects are created as needed, and reused from a static pool 44 * of all objects, organized by classname. When an object is created, if one 45 * is not available in the pool, a new object is created. When the object 46 * is destroyed, it is returned to the pool so it can be used again. This has the 47 * effect of minimizing the requirement for garbage collection, reducing 48 * cycles needed to clean up dead objects. 49 * 50 * @param name {String} The name of the object within the engine. 51 * @constructor 52 * @description Create an instance of this object, assigning a global identifies to it. 53 */ 54 R.engine.PooledObject = Base.extend(/** @scope R.engine.PooledObject.prototype */{ 55 56 // The Id assigned by the engine 57 id:-1, 58 59 // The name of the object 60 name:"", 61 62 _destroyed:false, 63 64 /** @private */ 65 constructor:function (name) { 66 this._destroyed = false; 67 this.name = name; 68 this.id = R.Engine.create(this); 69 }, 70 71 /** 72 * When a pooled object is destroyed, its <tt>release()</tt> method will be called 73 * so it has a chance to clean up instance variables before being put back into 74 * the pool for reuse. The variables should be returned to an "uninitialized" state. 75 */ 76 release:function () { 77 this.name = ""; 78 this.id = -1; 79 }, 80 81 /** 82 * Destroy this object instance (remove it from the Engine). The object's <tt>release()</tt> 83 * method is called after destruction so it can be returned to the pool of objects 84 * to be used again. 85 */ 86 destroy:function () { 87 // Clean up the engine reference to this object 88 R.Engine.destroy(this); 89 90 this._destroyed = true; 91 R.engine.PooledObject.returnToPool(this); 92 R.debug.Metrics.add("poolLoad", Math.floor((R.engine.PooledObject.poolSize / R.engine.PooledObject.poolNew) * 100), false, "#%"); 93 94 // Reset any variables on the object after putting 95 // it back in the pool. 96 this.release(); 97 }, 98 99 /** 100 * Get the managed Id of this object within the Engine. 101 * 102 * @return {String} 103 */ 104 getId:function () { 105 return this.id; 106 }, 107 108 /** 109 * Get the original name this object was created with. 110 * 111 * @return {String} The name used when creating this object 112 */ 113 getName:function () { 114 return this.name; 115 }, 116 117 /** 118 * Set the name of the object. 119 * @param name {String} The name for the object 120 */ 121 setName:function (name) { 122 this.name = name; 123 }, 124 125 /** 126 * Returns an object that assigns getter and setter methods 127 * for exposed properties of an object. 128 * @return {Object} An object which contains getter and setter methods. 129 */ 130 getProperties:function () { 131 var self = this; 132 var prop = {}; 133 134 // [getter, setter, editableFlag] 135 136 return $.extend(prop, { 137 "Id":[function () { 138 return self.getId(); 139 }, 140 null, false], 141 "Name":[function () { 142 return self.getName(); 143 }, 144 function (i) { 145 self.setName(i); 146 }, true] 147 }); 148 }, 149 150 /** 151 * Write out the Id of the object and its class name 152 * @return {String} 153 */ 154 toString:function () { 155 return this.getId() + " [" + this.constructor.getClassName() + "]"; 156 }, 157 158 /** 159 * Serialize the object to XML. 160 * @return {String} 161 */ 162 toXML:function (indent) { 163 indent = indent ? indent : ""; 164 var props = this.getProperties(); 165 var xml = indent + "<" + this.constructor.getClassName(); 166 for (var p in props) { 167 // If the value should be serialized, call it's getter 168 if (props[p][2]) { 169 xml += " " + p.toLowerCase() + "=\"" + props[p][0]().toString() + "\""; 170 } 171 } 172 173 xml += "/>\n"; 174 return xml; 175 }, 176 177 /** 178 * Returns <tt>true</tt> if the object has been destroyed. For objects which are 179 * being updated by containers, this method is used to determine if the object should 180 * be updated. It is important to check this method if you are outside the normal 181 * bounds of updating an object. For example, if an object is drawing its bounding 182 * box using it's collision component, the component may have been destroyed along 183 * with the object, by another object. Checking to see if the object is destroyed 184 * before calling such method would prevent an exception being thrown when trying 185 * to access the component which was destroyed as well. 186 * @return {Boolean} 187 */ 188 isDestroyed:function () { 189 return this._destroyed; 190 }, 191 192 /** 193 * Get the model data associated with an object. If <tt>key</tt> is provided, only the 194 * data for <tt>key</tt> will be returned. If the data has not yet been assigned, 195 * an empty object will be created to contain the data. 196 * 197 * @param [key] {String} Optional key which contains the data, or <tt>null</tt> for the 198 * entire data model. 199 */ 200 getObjectDataModel:function (key) { 201 var mData = this[R.engine.PooledObject.DATA_MODEL]; 202 if (mData == null) { 203 this[R.engine.PooledObject.DATA_MODEL] = {}; 204 mData = this[R.engine.PooledObject.DATA_MODEL]; 205 } 206 return key ? mData[key] : mData; 207 }, 208 209 /** 210 * Set a key, within the object's data model, to a specific value. 211 * 212 * @param key {String} The key to set the data for 213 * @param value {Object} The value to assign to the key 214 */ 215 setObjectDataModel:function (key, value) { 216 var mData = this.getObjectDataModel(); 217 mData[key] = value; 218 return mData[key]; 219 }, 220 221 /** 222 * Clear all of the spatial container model data. 223 */ 224 clearObjectDataModel:function () { 225 this[R.engine.PooledObject.DATA_MODEL] = null; 226 } 227 228 }, /** @scope R.engine.PooledObject.prototype **/{ 229 230 /** 231 * <tt>true</tt> for all objects within the engine. 232 * @type {Boolean} 233 */ 234 isRenderEngineObject:true, 235 236 /** 237 * <tt>true</tt> for all objects that are pooled. 238 * @type {Boolean} 239 */ 240 isPooledObject:true, 241 242 /** 243 * The maximum number of objects, per class, that can be pooled. This value can 244 * be tweaked, per class, which extends from <tt>R.engine.PooledObject</tt>. 245 * <i>(default: 50)</i> 246 * @type {Number} 247 */ 248 MAX_POOL_COUNT:50, 249 250 /** 251 * Number of new objects put into the pool 252 * @type {Number} 253 */ 254 poolNew:0, 255 256 /** 257 * Total number of objects in the pool 258 * @type {Number} 259 */ 260 poolSize:0, 261 262 /* pragma:DEBUG_START */ 263 classPool:{}, 264 /* pragma:DEBUG_END */ 265 266 /** 267 * Similar to a constructor, all pooled objects implement this method to create an instance of the object. 268 * The <tt>create()</tt> method will either create a new instance, if no object of the object's 269 * class exists within the pool, or will reuse an existing pooled instance of 270 * the object. Either way, the constructor for the object instance is called so that 271 * instance creation can be maintained in the constructor. 272 * <p/> 273 * Usage: <tt>var obj = [ObjectClass].create(arg1, arg2, arg3...);</tt> 274 * @static 275 */ 276 create:function () { 277 // Check the pool for the object type 278 if (R.engine.PooledObject.objectPool[this.getClassName()] && 279 R.engine.PooledObject.objectPool[this.getClassName()].length != 0) { 280 281 R.engine.PooledObject.poolSize--; 282 R.debug.Metrics.add("poolLoad", Math.floor((R.engine.PooledObject.poolSize / R.engine.PooledObject.poolNew) * 100), false, "#%"); 283 var obj = R.engine.PooledObject.objectPool[this.getClassName()].shift(); 284 obj.constructor.apply(obj, arguments); 285 286 /* pragma:DEBUG_START */ 287 R.engine.PooledObject.classPool[this.getClassName()][1]++; 288 R.engine.PooledObject.classPool[this.getClassName()][2]--; 289 /* pragma:DEBUG_END */ 290 291 return obj; 292 } else { 293 R.engine.PooledObject.poolNew++; 294 R.debug.Metrics.add("poolNew", R.engine.PooledObject.poolNew, false, "#"); 295 296 /* pragma:DEBUG_START */ 297 if (R.engine.PooledObject.classPool[this.getClassName()]) { 298 R.engine.PooledObject.classPool[this.getClassName()][0]++; 299 } else { 300 // 0: new, 1: in use, 2: pooled 301 R.engine.PooledObject.classPool[this.getClassName()] = [1, 0, 0]; 302 } 303 /* pragma:DEBUG_END */ 304 305 // TODO: Any more than 15 arguments and construction will fail! 306 return new this(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], 307 arguments[5], arguments[6], arguments[7], arguments[8], arguments[9], 308 arguments[10], arguments[11], arguments[12], arguments[13], arguments[14]); 309 } 310 }, 311 312 /** 313 * During object destruction, it needs to return itself to the 314 * pool so it can be used again. Instead of creating new instances 315 * of each object, we utilize a pooled object so we don't need the 316 * garbage collector to be invoked for removed objects. 317 * @private 318 */ 319 returnToPool:function (obj) { 320 // If there is no pool for objects of this type, create one 321 if (!R.engine.PooledObject.objectPool[obj.constructor.getClassName()]) { 322 R.engine.PooledObject.objectPool[obj.constructor.getClassName()] = []; 323 } 324 325 // We'll only add elements to the pool if the pool for objects is 326 // smaller than the defined limit per class (MAX_POOL_COUNT) 327 var maxPool = obj.constructor.MAX_POOL_COUNT || R.engine.PooledObject.MAX_POOL_COUNT; 328 if (R.engine.PooledObject.objectPool[obj.constructor.getClassName()].length < maxPool) { 329 // Push this object into the pool 330 R.engine.PooledObject.poolSize++; 331 R.engine.PooledObject.objectPool[obj.constructor.getClassName()].push(obj); 332 333 /* pragma:DEBUG_START */ 334 if (R.engine.PooledObject.classPool[obj.constructor.getClassName()][1] != 0) { 335 R.engine.PooledObject.classPool[obj.constructor.getClassName()][1]--; 336 } 337 R.engine.PooledObject.classPool[obj.constructor.getClassName()][2]++; 338 /* pragma:DEBUG_END */ 339 340 R.debug.Metrics.add("pooledObjects", R.engine.PooledObject.poolSize, false, "#"); 341 } 342 }, 343 344 /** 345 * The pool of all objects, stored by class name. 346 * @type {Object} 347 */ 348 objectPool:{}, 349 350 /** 351 * Get the class name of this object 352 * 353 * @return {String} "R.engine.PooledObject" 354 */ 355 getClassName:function () { 356 if (!this.hasOwnProperty("getClassName")) { 357 R.debug.Console.warn("Object is missing getClassName()"); 358 } 359 return "R.engine.PooledObject"; 360 }, 361 362 /** 363 * Serialize the pooled object into an object. 364 * @param obj {R.engine.PooledObject} The object to serialize 365 * @param [defaults] {Object} An optional set of defaults to ignore if the values 366 * are no different than the default. 367 */ 368 serialize:function (obj, defaults) { 369 var bean = obj.getProperties(), propObj = {}, val; 370 defaults = defaults || []; 371 for (var p in bean) { 372 if (bean[p][1]) { 373 val = bean[p][0](); 374 if (val != defaults[p]) { 375 propObj[p] = bean[p][0](); 376 } 377 } 378 } 379 propObj.CLASSNAME = obj.constructor.getClassName(); 380 return propObj; 381 }, 382 383 /** 384 * Deserialize the object back into a Pooled Object. 385 * @param obj {Object} The object to deserialize 386 * @param [clazz] {Class} The object class to populate 387 * @return {R.engine.PooledObject} The object which was deserialized 388 */ 389 deserialize:function (obj, clazz) { 390 // We'll remove the CLASSNAME field because we're not currently using it 391 delete obj.CLASSNAME; 392 393 clazz = clazz || R.engine.PooledObject.create(obj.name); 394 var bean = clazz.getProperties(); 395 for (var p in obj) { 396 // Regardless if the editable flag is true, if there is a setter, we'll 397 // call it to copy the value over. 398 if (bean[p][1]) { 399 if (bean[p][1].multi || bean[p][1].toggle || bean[p][1].editor) { 400 // Multi-select, toggle, or custom editor 401 bean[p][1].fn(obj[p]); 402 } else { 403 // Single value 404 bean[p][1](obj[p]); 405 } 406 } 407 } 408 409 return clazz; 410 }, 411 412 /** 413 * @private 414 */ 415 DATA_MODEL:"$$OBJECT_DATA_MODEL" 416 417 }); 418