1 /**
  2  * The Render Engine
  3  * Engine Linker Class
  4  *
  5  * @fileoverview A class for checking class dependencies and class intialization
  6  *
  7  * @author: Brett Fattori (brettf@renderengine.com)
  8  * @author: $Author: bfattori $
  9  * @version: $Revision: 1555 $
 10  *
 11  * Copyright (c) 2011 Brett Fattori (brettf@renderengine.com)
 12  *
 13  * Permission is hereby granted, free of charge, to any person obtaining a copy
 14  * of this software and associated documentation files (the "Software"), to deal
 15  * in the Software without restriction, including without limitation the rights
 16  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 17  * copies of the Software, and to permit persons to whom the Software is
 18  * furnished to do so, subject to the following conditions:
 19  *
 20  * The above copyright notice and this permission notice shall be included in
 21  * all copies or substantial portions of the Software.
 22  *
 23  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 24  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 25  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 26  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 27  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 28  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 29  * THE SOFTWARE.
 30  *
 31  */
 32 
 33 /**
 34  * @class A static class for processing class files, looking for dependencies, and
 35  *        ensuring that all dependencies exist before initializing a class.  The linker
 36  *        expects that files will follow a fairly strict format, so that patterns can be
 37  *        identified and all dependencies resolved.
 38  *        <p/>
 39  *        These methods handle object dependencies so that each object will be
 40  *        initialized as soon as its dependencies become available.  Using this method
 41  *        scripts can be loaded immediately, using the browsers threading model, and
 42  *        executed when dependencies are fulfilled.
 43  *
 44  * @static
 45  * @private
 46  */
 47 R.engine.Linker = Base.extend(/** @scope R.engine.Linker.prototype */{
 48 
 49     constructor:null,
 50 
 51     //====================================================================================================
 52     //====================================================================================================
 53     //                                   DEPENDENCY PROCESSOR
 54     //
 55     //====================================================================================================
 56     //====================================================================================================
 57 
 58     classDefinitions:{}, // These are class definitions which have been parsed
 59     processed:{}, // These are the classes/files which have been processed
 60     resolvedClasses:{}, // These are resolved (loaded & ready) classes
 61     resolvedFiles:{}, // These are resolved (loaded) files
 62 
 63     loadClasses:[], // Classes which need to be loaded
 64     queuedClasses:{}, // Classes which are queued to be initialized
 65 
 66     classLoaderTimer:null,
 67     classTimer:null,
 68     failTimer:null,
 69 
 70     waiting:{},
 71 
 72     /**
 73      * See R.Engine.define()
 74      * @private
 75      */
 76     define:function (classDef) {
 77         if (typeof classDef["class"] === "undefined") {
 78             throw new SyntaxError("Missing 'class' key in class definition!");
 79         }
 80         var className = classDef["class"];
 81 
 82         if (R.engine.Linker.resolvedClasses[className] != null) {
 83             throw new ReferenceError("Class '" + className + "' is already defined!");
 84         }
 85 
 86         R.debug.Console.info("R.engine.Linker => Process definition for ", className);
 87 
 88         R.engine.Linker.classDefinitions[className] = classDef;
 89         var deps = [];
 90         if (classDef.requires && classDef.requires.length > 0) deps = deps.concat(classDef.requires);
 91         var incs = [];
 92         if (classDef.includes && classDef.includes.length > 0) incs = incs.concat(classDef.includes);
 93 
 94         if (deps.length == 0 && incs.length == 0) {
 95             // This class is ready to go already
 96             R.engine.Linker._initClass(className);
 97             return;
 98         }
 99 
100         if (!R.engine.Linker.processed[className]) {
101             R.engine.Linker.processed[className] = true;
102             if (!R.engine.Linker.resolvedClasses[className]) {
103                 // Queue the class to be resolved
104                 R.engine.Linker.queuedClasses[className] = true;
105             }
106         }
107 
108         // Remove any dependencies which are already resolved
109         var unresDeps = [];
110         while (deps.length > 0) {
111             var dep = deps.shift();
112             if (!R.engine.Linker.resolvedClasses[dep]) {
113                 unresDeps.push(dep);
114             }
115         }
116 
117         // Remove any includes which are already loaded
118         var unresIncs = [];
119         while (incs.length > 0) {
120             var inc = incs.shift();
121             if (!R.engine.Linker.resolvedFiles[inc]) {
122                 unresIncs.push(inc);
123             }
124         }
125 
126         // Load the includes ASAP
127         while (unresIncs.length > 0) {
128             var inc = unresIncs.shift();
129 
130             // If the include hasn't been processed yet, do it now
131             if (!R.engine.Linker.processed[inc]) {
132                 var cb = function (path, result) {
133                     if (result === R.engine.Script.SCRIPT_LOADED) {
134                         R.engine.Linker.resolvedFiles[path] = true;
135                     }
136                 };
137                 R.engine.Script.loadNow(inc, cb);
138                 R.engine.Linker.processed[inc] = true;
139             }
140         }
141 
142         // Queue up the classes for processing
143         while (unresDeps.length > 0) {
144             var dep = unresDeps.shift();
145             if (!R.engine.Linker.processed[dep]) {
146                 R.engine.Linker.processed[dep] = true;
147                 R.engine.Linker.loadClasses.push(dep);
148             }
149         }
150 
151         if (R.engine.Linker.loadClasses.length > 0) {
152             // Run the class loader
153             setTimeout(function () {
154                 R.engine.Linker.classLoader();
155             }, 100);
156         }
157 
158         if (R.engine.Linker.classTimer == null) {
159             // After 10 seconds, if classes haven't been processed, fail
160             R.engine.Linker.failTimer = setTimeout(function () {
161                 R.engine.Linker._failure();
162             }, 10000);
163 
164             R.engine.Linker.classTimer = setTimeout(function () {
165                 R.engine.Linker._processClasses();
166             }, 100);
167         }
168     },
169 
170     /**
171      * Loads the class by converting the namespaced class to a filename and
172      * calling the script loader.  When the file finishes loading, it is
173      * put into the class queue to be processed.
174      *
175      * @private
176      */
177     classLoader:function () {
178         // Load the classes
179         while (R.engine.Linker.loadClasses.length > 0) {
180             R.engine.Linker._doLoad(R.engine.Linker.loadClasses.shift());
181         }
182     },
183 
184     /**
185      * Linker uses this to load classes and track them
186      * @private
187      */
188     _doLoad:function (className) {
189         // Split the class into packages
190         var cn = className.split(".");
191 
192         // Shift off the namespace
193         cn.shift();
194 
195         // Is this in the engine package?
196         if (cn[0] == "engine") {
197             // Shift off the package
198             cn.shift();
199         }
200 
201         // Convert the class to a path
202         var path = "/" + cn.join("/").toLowerCase() + ".js";
203 
204         // Classes waiting for data
205         R.engine.Linker.waiting[path] = className;
206 
207         // Load the class
208         R.debug.Console.log("Loading " + path);
209         R.engine.Script.loadNow(path, R.engine.Linker._loaded);
210     },
211 
212     /**
213      * The callback for when a class file is loaded
214      * @private
215      */
216     _loaded:function (path, result) {
217 
218         // Get the class for the path name
219         var className = R.engine.Linker.waiting[path];
220         delete R.engine.Linker.waiting[path];
221 
222         if (result === R.engine.Script.SCRIPT_LOADED) {
223             // Push the class into the processing queue
224             R.debug.Console.info("R.engine.Linker => Initializing " + className);
225             R.engine.Linker.queuedClasses[className] = true;
226         } else {
227             R.debug.Console.error("R.engine.Linker => " + className + " failed to load!");
228         }
229 
230     },
231 
232     /**
233      * Performs dependency and include checking for a class before
234      * initializing it into the namespace.
235      *
236      * @private
237      */
238     _processClasses:function () {
239         var inProcess = 0, processed = 0, completed = [];
240         for (var cn in R.engine.Linker.queuedClasses) {
241             inProcess++;
242 
243             // Get the class definition
244             var def = R.engine.Linker.classDefinitions[cn];
245 
246             if (!def) {
247                 throw new Error("R.engine.Linker => Class '" + cn + "' doesn't have a definition!");
248             }
249 
250             // Check to see if the dependencies exist
251             var missDeps = false, reqs = [], unres = [];
252             if (def.requires && def.requires.length > 0) reqs = reqs.concat(def.requires);
253             while (reqs.length > 0) {
254                 var req = reqs.shift();
255 
256                 if (!R.engine.Linker.resolvedClasses[req]) {
257                     // Check for A => B  => A
258                     // If such a circular reference exists, we can ignore the dependency
259                     var depDef = R.engine.Linker.classDefinitions[req];
260                     if (depDef && depDef.requires) {
261                         if (R.engine.Support.indexOf(depDef.requires, cn) == -1) {
262                             // Not a circular reference
263                             unres.push(req);
264                         }
265                     } else {
266                         // Class not resolved
267                         unres.push(req);
268                     }
269                 }
270             }
271 
272             // Anything left unresolved means we cannot initialize
273             missDeps = (unres.length > 0);
274 
275             // Check for local dependencies
276             var localDeps = false, lDeps = [], lUnres = [];
277             if (def.depends && def.depends.length > 0) lDeps = lDeps.concat(def.depends);
278             while (lDeps.length > 0) {
279                 var lDep = lDeps.shift();
280 
281                 if (!R.engine.Linker.resolvedClasses[lDep]) {
282                     // Check for A => B  => A
283                     // If such a circular reference exists, we can ignore the dependency
284                     var lDepDef = R.engine.Linker.classDefinitions[lDep];
285                     if (lDepDef && lDepDef.requires) {
286                         if (R.engine.Support.indexOf(lDepDef.requires, cn) == -1) {
287                             // Not a circular reference
288                             lUnres.push(lDep);
289                         }
290                     } else if (lDepDef && lDepDef.depends) {
291                         if (R.engine.Support.indexOf(lDepDef.depends, cn) == -1) {
292                             // Not a circular reference
293                             lUnres.push(lDep);
294                         }
295                     } else {
296                         // Class not resolved
297                         lUnres.push(lDep);
298                     }
299                 }
300             }
301 
302             // Anything left unresolved means we cannot initialize
303             localDeps = (lUnres.length > 0);
304 
305             // If all requirements are loaded, check the includes
306             if (!(missDeps || localDeps)) {
307                 var missIncs = false, incs = def.includes || [];
308                 for (var i = 0; i < incs.length; i++) {
309                     if (!R.engine.Linker.resolvedFiles[incs[i]]) {
310                         missIncs = true;
311                         break;
312                     }
313                 }
314 
315                 if (!missIncs) {
316                     R.engine.Linker._initClass(cn);
317 
318                     // No need to process it again
319                     completed.push(cn);
320                     processed++;
321                 }
322             }
323         }
324 
325         // Clean up processed classes
326         while (completed.length > 0) {
327             delete R.engine.Linker.queuedClasses[completed.shift()];
328         }
329 
330         if (processed != 0) {
331             // Something was processed, reset the fail timer
332             clearTimeout(R.engine.Linker.failTimer);
333             R.engine.Linker.failTimer = setTimeout(function () {
334                 R.engine.Linker._failure();
335             }, 10000);
336         }
337 
338         var newClzz = 0;
339         for (var j in R.engine.Linker.queuedClasses) {
340             newClzz++;
341         }
342 
343         if (newClzz > 0 || inProcess > processed) {
344             // There are classes waiting for their dependencies, do this again
345             R.engine.Linker.classTimer = setTimeout(function () {
346                 R.engine.Linker._processClasses();
347             }, 100);
348         } else if (inProcess == processed) {
349             // Clear the fail timer
350             clearTimeout(R.engine.Linker.failTimer);
351 
352             // All classes waiting to be processed have been processed
353             R.engine.Linker.classTimer = null;
354         }
355     },
356 
357     /**
358      * Initializes classes which have their dependencies resolved
359      * @private
360      */
361     _initClass:function (className) {
362         if (R.engine.Linker.resolvedClasses[className]) {
363             // This is all set, no need to run through this again
364             return;
365         }
366 
367         // Get the class object
368         var pkg = R.global, clazz = className.split(".");
369         while (clazz.length > 1) {
370             pkg = pkg[clazz.shift()];
371         }
372         var shortName = clazz.shift(), classObjDef = pkg[shortName];
373 
374         // We can initialize the class
375         if ($.isFunction(classObjDef)) {
376             pkg[shortName] = classObjDef();
377         } else {
378             pkg[shortName] = classObjDef;
379         }
380 
381         // If the class defines a "resolved()" class method, call that
382         if ((typeof pkg[shortName] !== "undefined") && pkg[shortName].resolved) {
383             pkg[shortName].resolved();
384         }
385 
386         R.debug.Console.info("R.engine.Linker => " + className + " initialized");
387         R.engine.Linker.resolvedClasses[className] = true;
388     },
389 
390     /**
391      * Called if the linker has failed to load any classes and seems to be
392      * stuck waiting for resolution.
393      * @private
394      */
395     _failure:function () {
396         clearTimeout(R.engine.Linker.failTimer);
397         clearTimeout(R.engine.Linker.classTimer);
398         clearTimeout(R.engine.Linker.classLoader);
399 
400         R.debug.Console.error("R.engine.Linker => FAILURE TO LOAD CLASSES!", "Resolved: ", R.engine.Linker.resolvedClasses, " Unprocessed: ", R.engine.Linker.queuedClasses, " ClassDefs: ", R.engine.Linker.classDefinitions);
401     }
402 
403 });
404