Graph database Analysis of the Steam Network
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

984 lines
25 KiB

  1. /**
  2. * conrad.js is a tiny JavaScript jobs scheduler,
  3. *
  4. * Version: 0.1.0
  5. * Sources: http://github.com/jacomyal/conrad.js
  6. * Doc: http://github.com/jacomyal/conrad.js#readme
  7. *
  8. * License:
  9. * --------
  10. * Copyright © 2013 Alexis Jacomy, Sciences-Po médialab
  11. *
  12. * Permission is hereby granted, free of charge, to any person obtaining a copy
  13. * of this software and associated documentation files (the "Software"), to
  14. * deal in the Software without restriction, including without limitation the
  15. * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  16. * sell copies of the Software, and to permit persons to whom the Software is
  17. * furnished to do so, subject to the following conditions:
  18. *
  19. * The above copyright notice and this permission notice shall be included in
  20. * all copies or substantial portions of the Software.
  21. *
  22. * The Software is provided "as is", without warranty of any kind, express or
  23. * implied, including but not limited to the warranties of merchantability,
  24. * fitness for a particular purpose and noninfringement. In no event shall the
  25. * authors or copyright holders be liable for any claim, damages or other
  26. * liability, whether in an action of contract, tort or otherwise, arising
  27. * from, out of or in connection with the software or the use or other dealings
  28. * in the Software.
  29. */
  30. (function(global) {
  31. 'use strict';
  32. // Check that conrad.js has not been loaded yet:
  33. if (global.conrad)
  34. throw new Error('conrad already exists');
  35. /**
  36. * PRIVATE VARIABLES:
  37. * ******************
  38. */
  39. /**
  40. * A flag indicating whether conrad is running or not.
  41. *
  42. * @type {Number}
  43. */
  44. var _lastFrameTime;
  45. /**
  46. * A flag indicating whether conrad is running or not.
  47. *
  48. * @type {Boolean}
  49. */
  50. var _isRunning = false;
  51. /**
  52. * The hash of registered jobs. Each job must at least have a unique ID
  53. * under the key "id" and a function under the key "job". This hash
  54. * contains each running job and each waiting job.
  55. *
  56. * @type {Object}
  57. */
  58. var _jobs = {};
  59. /**
  60. * The hash of currently running jobs.
  61. *
  62. * @type {Object}
  63. */
  64. var _runningJobs = {};
  65. /**
  66. * The array of currently running jobs, sorted by priority.
  67. *
  68. * @type {Array}
  69. */
  70. var _sortedByPriorityJobs = [];
  71. /**
  72. * The array of currently waiting jobs.
  73. *
  74. * @type {Object}
  75. */
  76. var _waitingJobs = {};
  77. /**
  78. * The array of finished jobs. They are stored in an array, since two jobs
  79. * with the same "id" can happen at two different times.
  80. *
  81. * @type {Array}
  82. */
  83. var _doneJobs = [];
  84. /**
  85. * A dirty flag to keep conrad from starting: Indeed, when addJob() is called
  86. * with several jobs, conrad must be started only at the end. This flag keeps
  87. * me from duplicating the code that effectively adds a job.
  88. *
  89. * @type {Boolean}
  90. */
  91. var _noStart = false;
  92. /**
  93. * An hash containing some global settings about how conrad.js should
  94. * behave.
  95. *
  96. * @type {Object}
  97. */
  98. var _parameters = {
  99. frameDuration: 20,
  100. history: true
  101. };
  102. /**
  103. * This object contains every handlers bound to conrad events. It does not
  104. * requirea any DOM implementation, since the events are all JavaScript.
  105. *
  106. * @type {Object}
  107. */
  108. var _handlers = Object.create(null);
  109. /**
  110. * PRIVATE FUNCTIONS:
  111. * ******************
  112. */
  113. /**
  114. * Will execute the handler everytime that the indicated event (or the
  115. * indicated events) will be triggered.
  116. *
  117. * @param {string|array|object} events The name of the event (or the events
  118. * separated by spaces).
  119. * @param {function(Object)} handler The handler to bind.
  120. * @return {Object} Returns conrad.
  121. */
  122. function _bind(events, handler) {
  123. var i,
  124. i_end,
  125. event,
  126. eArray;
  127. if (!arguments.length)
  128. return;
  129. else if (
  130. arguments.length === 1 &&
  131. Object(arguments[0]) === arguments[0]
  132. )
  133. for (events in arguments[0])
  134. _bind(events, arguments[0][events]);
  135. else if (arguments.length > 1) {
  136. eArray =
  137. Array.isArray(events) ?
  138. events :
  139. events.split(/ /);
  140. for (i = 0, i_end = eArray.length; i !== i_end; i += 1) {
  141. event = eArray[i];
  142. if (!_handlers[event])
  143. _handlers[event] = [];
  144. // Using an object instead of directly the handler will make possible
  145. // later to add flags
  146. _handlers[event].push({
  147. handler: handler
  148. });
  149. }
  150. }
  151. }
  152. /**
  153. * Removes the handler from a specified event (or specified events).
  154. *
  155. * @param {?string} events The name of the event (or the events
  156. * separated by spaces). If undefined,
  157. * then all handlers are removed.
  158. * @param {?function(Object)} handler The handler to unbind. If undefined,
  159. * each handler bound to the event or the
  160. * events will be removed.
  161. * @return {Object} Returns conrad.
  162. */
  163. function _unbind(events, handler) {
  164. var i,
  165. i_end,
  166. j,
  167. j_end,
  168. a,
  169. event,
  170. eArray = Array.isArray(events) ?
  171. events :
  172. events.split(/ /);
  173. if (!arguments.length)
  174. _handlers = Object.create(null);
  175. else if (handler) {
  176. for (i = 0, i_end = eArray.length; i !== i_end; i += 1) {
  177. event = eArray[i];
  178. if (_handlers[event]) {
  179. a = [];
  180. for (j = 0, j_end = _handlers[event].length; j !== j_end; j += 1)
  181. if (_handlers[event][j].handler !== handler)
  182. a.push(_handlers[event][j]);
  183. _handlers[event] = a;
  184. }
  185. if (_handlers[event] && _handlers[event].length === 0)
  186. delete _handlers[event];
  187. }
  188. } else
  189. for (i = 0, i_end = eArray.length; i !== i_end; i += 1)
  190. delete _handlers[eArray[i]];
  191. }
  192. /**
  193. * Executes each handler bound to the event.
  194. *
  195. * @param {string} events The name of the event (or the events separated
  196. * by spaces).
  197. * @param {?Object} data The content of the event (optional).
  198. * @return {Object} Returns conrad.
  199. */
  200. function _dispatch(events, data) {
  201. var i,
  202. j,
  203. i_end,
  204. j_end,
  205. event,
  206. eventName,
  207. eArray = Array.isArray(events) ?
  208. events :
  209. events.split(/ /);
  210. data = data === undefined ? {} : data;
  211. for (i = 0, i_end = eArray.length; i !== i_end; i += 1) {
  212. eventName = eArray[i];
  213. if (_handlers[eventName]) {
  214. event = {
  215. type: eventName,
  216. data: data || {}
  217. };
  218. for (j = 0, j_end = _handlers[eventName].length; j !== j_end; j += 1)
  219. try {
  220. _handlers[eventName][j].handler(event);
  221. } catch (e) {}
  222. }
  223. }
  224. }
  225. /**
  226. * Executes the most prioritary job once, and deals with filling the stats
  227. * (done, time, averageTime, currentTime, etc...).
  228. *
  229. * @return {?Object} Returns the job object if it has to be killed, null else.
  230. */
  231. function _executeFirstJob() {
  232. var i,
  233. l,
  234. test,
  235. kill,
  236. pushed = false,
  237. time = __dateNow(),
  238. job = _sortedByPriorityJobs.shift();
  239. // Execute the job and look at the result:
  240. test = job.job();
  241. // Deal with stats:
  242. time = __dateNow() - time;
  243. job.done++;
  244. job.time += time;
  245. job.currentTime += time;
  246. job.weightTime = job.currentTime / (job.weight || 1);
  247. job.averageTime = job.time / job.done;
  248. // Check if the job has to be killed:
  249. kill = job.count ? (job.count <= job.done) : !test;
  250. // Reset priorities:
  251. if (!kill) {
  252. for (i = 0, l = _sortedByPriorityJobs.length; i < l; i++)
  253. if (_sortedByPriorityJobs[i].weightTime > job.weightTime) {
  254. _sortedByPriorityJobs.splice(i, 0, job);
  255. pushed = true;
  256. break;
  257. }
  258. if (!pushed)
  259. _sortedByPriorityJobs.push(job);
  260. }
  261. return kill ? job : null;
  262. }
  263. /**
  264. * Activates a job, by adding it to the _runningJobs object and the
  265. * _sortedByPriorityJobs array. It also initializes its currentTime value.
  266. *
  267. * @param {Object} job The job to activate.
  268. */
  269. function _activateJob(job) {
  270. var l = _sortedByPriorityJobs.length;
  271. // Add the job to the running jobs:
  272. _runningJobs[job.id] = job;
  273. job.status = 'running';
  274. // Add the job to the priorities:
  275. if (l) {
  276. job.weightTime = _sortedByPriorityJobs[l - 1].weightTime;
  277. job.currentTime = job.weightTime * (job.weight || 1);
  278. }
  279. // Initialize the job and dispatch:
  280. job.startTime = __dateNow();
  281. _dispatch('jobStarted', __clone(job));
  282. _sortedByPriorityJobs.push(job);
  283. }
  284. /**
  285. * The main loop of conrad.js:
  286. * . It executes job such that they all occupate the same processing time.
  287. * . It stops jobs that do not need to be executed anymore.
  288. * . It triggers callbacks when it is relevant.
  289. * . It starts waiting jobs when they need to be started.
  290. * . It injects frames to keep a constant frapes per second ratio.
  291. * . It stops itself when there are no more jobs to execute.
  292. */
  293. function _loop() {
  294. var k,
  295. o,
  296. l,
  297. job,
  298. time,
  299. deadJob;
  300. // Deal with the newly added jobs (the _jobs object):
  301. for (k in _jobs) {
  302. job = _jobs[k];
  303. if (job.after)
  304. _waitingJobs[k] = job;
  305. else
  306. _activateJob(job);
  307. delete _jobs[k];
  308. }
  309. // Set the _isRunning flag to false if there are no running job:
  310. _isRunning = !!_sortedByPriorityJobs.length;
  311. // Deal with the running jobs (the _runningJobs object):
  312. while (
  313. _sortedByPriorityJobs.length &&
  314. __dateNow() - _lastFrameTime < _parameters.frameDuration
  315. ) {
  316. deadJob = _executeFirstJob();
  317. // Deal with the case where the job has ended:
  318. if (deadJob) {
  319. _killJob(deadJob.id);
  320. // Check for waiting jobs:
  321. for (k in _waitingJobs)
  322. if (_waitingJobs[k].after === deadJob.id) {
  323. _activateJob(_waitingJobs[k]);
  324. delete _waitingJobs[k];
  325. }
  326. }
  327. }
  328. // Check if conrad still has jobs to deal with, and kill it if not:
  329. if (_isRunning) {
  330. // Update the _lastFrameTime:
  331. _lastFrameTime = __dateNow();
  332. _dispatch('enterFrame');
  333. setTimeout(_loop, 0);
  334. } else
  335. _dispatch('stop');
  336. }
  337. /**
  338. * Adds one or more jobs, and starts the loop if no job was running before. A
  339. * job is at least a unique string "id" and a function, and there are some
  340. * parameters that you can specify for each job to modify the way conrad will
  341. * execute it. If a job is added with the "id" of another job that is waiting
  342. * or still running, an error will be thrown.
  343. *
  344. * When a job is added, it is referenced in the _jobs object, by its id.
  345. * Then, if it has to be executed right now, it will be also referenced in
  346. * the _runningJobs object. If it has to wait, then it will be added into the
  347. * _waitingJobs object, until it can start.
  348. *
  349. * Keep reading this documentation to see how to call this method.
  350. *
  351. * @return {Object} Returns conrad.
  352. *
  353. * Adding one job:
  354. * ***************
  355. * Basically, a job is defined by its string id and a function (the job). It
  356. * is also possible to add some parameters:
  357. *
  358. * > conrad.addJob('myJobId', myJobFunction);
  359. * > conrad.addJob('myJobId', {
  360. * > job: myJobFunction,
  361. * > someParameter: someValue
  362. * > });
  363. * > conrad.addJob({
  364. * > id: 'myJobId',
  365. * > job: myJobFunction,
  366. * > someParameter: someValue
  367. * > });
  368. *
  369. * Adding several jobs:
  370. * ********************
  371. * When adding several jobs at the same time, it is possible to specify
  372. * parameters for each one individually or for all:
  373. *
  374. * > conrad.addJob([
  375. * > {
  376. * > id: 'myJobId1',
  377. * > job: myJobFunction1,
  378. * > someParameter1: someValue1
  379. * > },
  380. * > {
  381. * > id: 'myJobId2',
  382. * > job: myJobFunction2,
  383. * > someParameter2: someValue2
  384. * > }
  385. * > ], {
  386. * > someCommonParameter: someCommonValue
  387. * > });
  388. * > conrad.addJob({
  389. * > myJobId1: {,
  390. * > job: myJobFunction1,
  391. * > someParameter1: someValue1
  392. * > },
  393. * > myJobId2: {,
  394. * > job: myJobFunction2,
  395. * > someParameter2: someValue2
  396. * > }
  397. * > }, {
  398. * > someCommonParameter: someCommonValue
  399. * > });
  400. * > conrad.addJob({
  401. * > myJobId1: myJobFunction1,
  402. * > myJobId2: myJobFunction2
  403. * > }, {
  404. * > someCommonParameter: someCommonValue
  405. * > });
  406. *
  407. * Recognized parameters:
  408. * **********************
  409. * Here is the exhaustive list of every accepted parameters:
  410. *
  411. * {?Function} end A callback to execute when the job is ended. It is
  412. * not executed if the job is killed instead of ended
  413. * "naturally".
  414. * {?Integer} count The number of time the job has to be executed.
  415. * {?Number} weight If specified, the job will be executed as it was
  416. * added "weight" times.
  417. * {?String} after The id of another job (eventually not added yet).
  418. * If specified, this job will start only when the
  419. * specified "after" job is ended.
  420. */
  421. function _addJob(v1, v2) {
  422. var i,
  423. l,
  424. o;
  425. // Array of jobs:
  426. if (Array.isArray(v1)) {
  427. // Keep conrad to start until the last job is added:
  428. _noStart = true;
  429. for (i = 0, l = v1.length; i < l; i++)
  430. _addJob(v1[i].id, __extend(v1[i], v2));
  431. _noStart = false;
  432. if (!_isRunning) {
  433. // Update the _lastFrameTime:
  434. _lastFrameTime = __dateNow();
  435. _dispatch('start');
  436. _loop();
  437. }
  438. } else if (typeof v1 === 'object') {
  439. // One job (object):
  440. if (typeof v1.id === 'string')
  441. _addJob(v1.id, v1);
  442. // Hash of jobs:
  443. else {
  444. // Keep conrad to start until the last job is added:
  445. _noStart = true;
  446. for (i in v1)
  447. if (typeof v1[i] === 'function')
  448. _addJob(i, __extend({
  449. job: v1[i]
  450. }, v2));
  451. else
  452. _addJob(i, __extend(v1[i], v2));
  453. _noStart = false;
  454. if (!_isRunning) {
  455. // Update the _lastFrameTime:
  456. _lastFrameTime = __dateNow();
  457. _dispatch('start');
  458. _loop();
  459. }
  460. }
  461. // One job (string, *):
  462. } else if (typeof v1 === 'string') {
  463. if (_hasJob(v1))
  464. throw new Error(
  465. '[conrad.addJob] Job with id "' + v1 + '" already exists.'
  466. );
  467. // One job (string, function):
  468. if (typeof v2 === 'function') {
  469. o = {
  470. id: v1,
  471. done: 0,
  472. time: 0,
  473. status: 'waiting',
  474. currentTime: 0,
  475. averageTime: 0,
  476. weightTime: 0,
  477. job: v2
  478. };
  479. // One job (string, object):
  480. } else if (typeof v2 === 'object') {
  481. o = __extend(
  482. {
  483. id: v1,
  484. done: 0,
  485. time: 0,
  486. status: 'waiting',
  487. currentTime: 0,
  488. averageTime: 0,
  489. weightTime: 0
  490. },
  491. v2
  492. );
  493. // If none of those cases, throw an error:
  494. } else
  495. throw new Error('[conrad.addJob] Wrong arguments.');
  496. // Effectively add the job:
  497. _jobs[v1] = o;
  498. _dispatch('jobAdded', __clone(o));
  499. // Check if the loop has to be started:
  500. if (!_isRunning && !_noStart) {
  501. // Update the _lastFrameTime:
  502. _lastFrameTime = __dateNow();
  503. _dispatch('start');
  504. _loop();
  505. }
  506. // If none of those cases, throw an error:
  507. } else
  508. throw new Error('[conrad.addJob] Wrong arguments.');
  509. return this;
  510. }
  511. /**
  512. * Kills one or more jobs, indicated by their ids. It is only possible to
  513. * kill running jobs or waiting jobs. If you try to kill a job that does not
  514. * exist or that is already killed, a warning will be thrown.
  515. *
  516. * @param {Array|String} v1 A string job id or an array of job ids.
  517. * @return {Object} Returns conrad.
  518. */
  519. function _killJob(v1) {
  520. var i,
  521. l,
  522. k,
  523. a,
  524. job,
  525. found = false;
  526. // Array of job ids:
  527. if (Array.isArray(v1))
  528. for (i = 0, l = v1.length; i < l; i++)
  529. _killJob(v1[i]);
  530. // One job's id:
  531. else if (typeof v1 === 'string') {
  532. a = [_runningJobs, _waitingJobs, _jobs];
  533. // Remove the job from the hashes:
  534. for (i = 0, l = a.length; i < l; i++)
  535. if (v1 in a[i]) {
  536. job = a[i][v1];
  537. if (_parameters.history) {
  538. job.status = 'done';
  539. _doneJobs.push(job);
  540. }
  541. _dispatch('jobEnded', __clone(job));
  542. delete a[i][v1];
  543. if (typeof job.end === 'function')
  544. job.end();
  545. found = true;
  546. }
  547. // Remove the priorities array:
  548. a = _sortedByPriorityJobs;
  549. for (i = 0, l = a.length; i < l; i++)
  550. if (a[i].id === v1) {
  551. a.splice(i, 1);
  552. break;
  553. }
  554. if (!found)
  555. throw new Error('[conrad.killJob] Job "' + v1 + '" not found.');
  556. // If none of those cases, throw an error:
  557. } else
  558. throw new Error('[conrad.killJob] Wrong arguments.');
  559. return this;
  560. }
  561. /**
  562. * Kills every running, waiting, and just added jobs.
  563. *
  564. * @return {Object} Returns conrad.
  565. */
  566. function _killAll() {
  567. var k,
  568. jobs = __extend(_jobs, _runningJobs, _waitingJobs);
  569. // Take every jobs and push them into the _doneJobs object:
  570. if (_parameters.history)
  571. for (k in jobs) {
  572. jobs[k].status = 'done';
  573. _doneJobs.push(jobs[k]);
  574. if (typeof jobs[k].end === 'function')
  575. jobs[k].end();
  576. }
  577. // Reinitialize the different jobs lists:
  578. _jobs = {};
  579. _waitingJobs = {};
  580. _runningJobs = {};
  581. _sortedByPriorityJobs = [];
  582. // In case some jobs are added right after the kill:
  583. _isRunning = false;
  584. return this;
  585. }
  586. /**
  587. * Returns true if a job with the specified id is currently running or
  588. * waiting, and false else.
  589. *
  590. * @param {String} id The id of the job.
  591. * @return {?Object} Returns the job object if it exists.
  592. */
  593. function _hasJob(id) {
  594. var job = _jobs[id] || _runningJobs[id] || _waitingJobs[id];
  595. return job ? __extend(job) : null;
  596. }
  597. /**
  598. * This method will set the setting specified by "v1" to the value specified
  599. * by "v2" if both are given, and else return the current value of the
  600. * settings "v1".
  601. *
  602. * @param {String} v1 The name of the property.
  603. * @param {?*} v2 Eventually, a value to set to the specified
  604. * property.
  605. * @return {Object|*} Returns the specified settings value if "v2" is not
  606. * given, and conrad else.
  607. */
  608. function _settings(v1, v2) {
  609. var o;
  610. if (typeof a1 === 'string' && arguments.length === 1)
  611. return _parameters[a1];
  612. else {
  613. o = (typeof a1 === 'object' && arguments.length === 1) ?
  614. a1 || {} :
  615. {};
  616. if (typeof a1 === 'string')
  617. o[a1] = a2;
  618. for (var k in o)
  619. if (o[k] !== undefined)
  620. _parameters[k] = o[k];
  621. else
  622. delete _parameters[k];
  623. return this;
  624. }
  625. }
  626. /**
  627. * Returns true if conrad is currently running, and false else.
  628. *
  629. * @return {Boolean} Returns _isRunning.
  630. */
  631. function _getIsRunning() {
  632. return _isRunning;
  633. }
  634. /**
  635. * Unreference every job that is stored in the _doneJobs object. It will
  636. * not be possible anymore to get stats about these jobs, but it will release
  637. * the memory.
  638. *
  639. * @return {Object} Returns conrad.
  640. */
  641. function _clearHistory() {
  642. _doneJobs = [];
  643. return this;
  644. }
  645. /**
  646. * Returns a snapshot of every data about jobs that wait to be started, are
  647. * currently running or are done.
  648. *
  649. * It is possible to get only running, waiting or done jobs by giving
  650. * "running", "waiting" or "done" as fist argument.
  651. *
  652. * It is also possible to get every job with a specified id by giving it as
  653. * first argument. Also, using a RegExp instead of an id will return every
  654. * jobs whose ids match the RegExp. And these two last use cases work as well
  655. * by giving before "running", "waiting" or "done".
  656. *
  657. * @return {Array} The array of the matching jobs.
  658. *
  659. * Some call examples:
  660. * *******************
  661. * > conrad.getStats('running')
  662. * > conrad.getStats('waiting')
  663. * > conrad.getStats('done')
  664. * > conrad.getStats('myJob')
  665. * > conrad.getStats(/test/)
  666. * > conrad.getStats('running', 'myRunningJob')
  667. * > conrad.getStats('running', /test/)
  668. */
  669. function _getStats(v1, v2) {
  670. var a,
  671. k,
  672. i,
  673. l,
  674. stats,
  675. pattern,
  676. isPatternString;
  677. if (!arguments.length) {
  678. stats = [];
  679. for (k in _jobs)
  680. stats.push(_jobs[k]);
  681. for (k in _waitingJobs)
  682. stats.push(_waitingJobs[k]);
  683. for (k in _runningJobs)
  684. stats.push(_runningJobs[k]);
  685. stats = stats.concat(_doneJobs);
  686. }
  687. if (typeof v1 === 'string')
  688. switch (v1) {
  689. case 'waiting':
  690. stats = __objectValues(_waitingJobs);
  691. break;
  692. case 'running':
  693. stats = __objectValues(_runningJobs);
  694. break;
  695. case 'done':
  696. stats = _doneJobs;
  697. break;
  698. default:
  699. pattern = v1;
  700. }
  701. if (v1 instanceof RegExp)
  702. pattern = v1;
  703. if (!pattern && (typeof v2 === 'string' || v2 instanceof RegExp))
  704. pattern = v2;
  705. // Filter jobs if a pattern is given:
  706. if (pattern) {
  707. isPatternString = typeof pattern === 'string';
  708. if (stats instanceof Array) {
  709. a = stats;
  710. } else if (typeof stats === 'object') {
  711. a = [];
  712. for (k in stats)
  713. a = a.concat(stats[k]);
  714. } else {
  715. a = [];
  716. for (k in _jobs)
  717. a.push(_jobs[k]);
  718. for (k in _waitingJobs)
  719. a.push(_waitingJobs[k]);
  720. for (k in _runningJobs)
  721. a.push(_runningJobs[k]);
  722. a = a.concat(_doneJobs);
  723. }
  724. stats = [];
  725. for (i = 0, l = a.length; i < l; i++)
  726. if (isPatternString ? a[i].id === pattern : a[i].id.match(pattern))
  727. stats.push(a[i]);
  728. }
  729. return __clone(stats);
  730. }
  731. /**
  732. * TOOLS FUNCTIONS:
  733. * ****************
  734. */
  735. /**
  736. * This function takes any number of objects as arguments, copies from each
  737. * of these objects each pair key/value into a new object, and finally
  738. * returns this object.
  739. *
  740. * The arguments are parsed from the last one to the first one, such that
  741. * when two objects have keys in common, the "earliest" object wins.
  742. *
  743. * Example:
  744. * ********
  745. * > var o1 = {
  746. * > a: 1,
  747. * > b: 2,
  748. * > c: '3'
  749. * > },
  750. * > o2 = {
  751. * > c: '4',
  752. * > d: [ 5 ]
  753. * > };
  754. * > __extend(o1, o2);
  755. * > // Returns: {
  756. * > // a: 1,
  757. * > // b: 2,
  758. * > // c: '3',
  759. * > // d: [ 5 ]
  760. * > // };
  761. *
  762. * @param {Object+} Any number of objects.
  763. * @return {Object} The merged object.
  764. */
  765. function __extend() {
  766. var i,
  767. k,
  768. res = {},
  769. l = arguments.length;
  770. for (i = l - 1; i >= 0; i--)
  771. for (k in arguments[i])
  772. res[k] = arguments[i][k];
  773. return res;
  774. }
  775. /**
  776. * This function simply clones an object. This object must contain only
  777. * objects, arrays and immutable values. Since it is not public, it does not
  778. * deal with cyclic references, DOM elements and instantiated objects - so
  779. * use it carefully.
  780. *
  781. * @param {Object} The object to clone.
  782. * @return {Object} The clone.
  783. */
  784. function __clone(item) {
  785. var result, i, k, l;
  786. if (!item)
  787. return item;
  788. if (Array.isArray(item)) {
  789. result = [];
  790. for (i = 0, l = item.length; i < l; i++)
  791. result.push(__clone(item[i]));
  792. } else if (typeof item === 'object') {
  793. result = {};
  794. for (i in item)
  795. result[i] = __clone(item[i]);
  796. } else
  797. result = item;
  798. return result;
  799. }
  800. /**
  801. * Returns an array containing the values of an object.
  802. *
  803. * @param {Object} The object.
  804. * @return {Array} The array of values.
  805. */
  806. function __objectValues(o) {
  807. var k,
  808. a = [];
  809. for (k in o)
  810. a.push(o[k]);
  811. return a;
  812. }
  813. /**
  814. * A short "Date.now()" polyfill.
  815. *
  816. * @return {Number} The current time (in ms).
  817. */
  818. function __dateNow() {
  819. return Date.now ? Date.now() : new Date().getTime();
  820. }
  821. /**
  822. * Polyfill for the Array.isArray function:
  823. */
  824. if (!Array.isArray)
  825. Array.isArray = function(v) {
  826. return Object.prototype.toString.call(v) === '[object Array]';
  827. };
  828. /**
  829. * EXPORT PUBLIC API:
  830. * ******************
  831. */
  832. var conrad = {
  833. hasJob: _hasJob,
  834. addJob: _addJob,
  835. killJob: _killJob,
  836. killAll: _killAll,
  837. settings: _settings,
  838. getStats: _getStats,
  839. isRunning: _getIsRunning,
  840. clearHistory: _clearHistory,
  841. // Events management:
  842. bind: _bind,
  843. unbind: _unbind,
  844. // Version:
  845. version: '0.1.0'
  846. };
  847. if (typeof exports !== 'undefined') {
  848. if (typeof module !== 'undefined' && module.exports)
  849. exports = module.exports = conrad;
  850. exports.conrad = conrad;
  851. }
  852. global.conrad = conrad;
  853. })(this);