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