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