All user data for FoundryVTT. Includes worlds, systems, modules, and any asset in the "foundryuserdata" directory. Does NOT include the FoundryVTT installation itself.
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.

152 lines
4.0 KiB

1 year ago
  1. import {
  2. logger
  3. } from './logger.js'
  4. const NAME = 'Events';
  5. let watches = {};
  6. let triggers = {};
  7. let id = 0;
  8. Array.prototype.removeIf = function (callback) {
  9. let i = this.length;
  10. while (i--) {
  11. if (callback(this[i], i)) {
  12. this.splice(i, 1);
  13. return true;
  14. }
  15. }
  16. return false;
  17. };
  18. export class Events {
  19. /**
  20. * Similar in operation to `Hooks.on`, with two exceptions. First, the provided function
  21. * can be asynchronous and will be awaited. Second, an optional `conditionFn` parameter
  22. * is added to help compartmentalize logic between detecting the desired event and responding to said event.
  23. *
  24. * @param {String} name Event name to watch for; It is recommended to use the enums found in {@link warpgate.EVENT}
  25. * @param {function(object):Promise|void} fn Function to execute when this event has passed the condition function. Will be awaited
  26. * @param {function(object):boolean} [condition = ()=>true] Optional. Function to determine if the event function should
  27. * be executed. While not strictly required, as the `fn` function could simply return as a NOOP, providing this
  28. * parameter may help compartmentalize "detection" vs "action" processing.
  29. *
  30. * @returns {number} Function id assigned to this event, for use with {@link warpgate.event.remove}
  31. */
  32. static watch(name, fn, condition = () => {
  33. return true;
  34. }) {
  35. if (!watches[name]) watches[name] = [];
  36. id++;
  37. watches[name].push({
  38. fn,
  39. condition,
  40. id
  41. });
  42. return id;
  43. }
  44. /**
  45. * Identical to {@link warpgate.event.watch}, except that this function will only be called once, after the condition is met.
  46. *
  47. * @see {@link warpgate.event.watch}
  48. */
  49. static trigger(name, fn, condition = () => {
  50. return true;
  51. }) {
  52. if (!triggers[name]) triggers[name] = [];
  53. id++;
  54. triggers[name].push({
  55. fn,
  56. condition,
  57. id
  58. });
  59. return id;
  60. }
  61. static async run(name, data) {
  62. for (const {
  63. fn,
  64. condition,
  65. id
  66. } of watches[name] ?? []) {
  67. try {
  68. if (condition(data)) {
  69. logger.debug(`${name} | ${id} passes watch condition`);
  70. await fn(data);
  71. } else {
  72. logger.debug(`${name} | ${id} fails watch condition`);
  73. }
  74. } catch (e) {
  75. logger.error(`${NAME} | error`, e, `\n \nIn watch function (${name})\n`, fn);
  76. }
  77. }
  78. let {
  79. run,
  80. keep
  81. } = (triggers[name] ?? []).reduce((acum, elem) => {
  82. try {
  83. const passed = elem.condition(data);
  84. if (passed) {
  85. logger.debug(`${name} | ${elem.id} passes trigger condition`);
  86. acum.run.push(elem);
  87. } else {
  88. logger.debug(`${name} | ${elem.id} fails trigger condition`);
  89. acum.keep.push(elem);
  90. }
  91. } catch (e) {
  92. logger.error(`${NAME} | error`, e, `\n \nIn trigger condition function (${name})\n`, elem.condition);
  93. return acum;
  94. } finally {
  95. return acum;
  96. }
  97. }, {
  98. run: [],
  99. keep: []
  100. });
  101. for (const {
  102. fn,
  103. id
  104. } of run) {
  105. logger.debug(`${name} | calling trigger ${id}`);
  106. try {
  107. await fn(data);
  108. } catch (e) {
  109. logger.error(`${NAME} | error`, e, `\n \nIn trigger function (${name})\n`, fn);
  110. }
  111. }
  112. triggers[name] = keep;
  113. }
  114. /**
  115. * Removes a `watch` or `trigger` by its provided id -- obtained by the return value of `watch` and `trigger`.
  116. *
  117. * @param {number} id Numerical ID of the event function to remove.
  118. *
  119. * @see warpgate.event.watch
  120. * @see warpgate.event.trigger
  121. */
  122. static remove(id) {
  123. const searchFn = (elem) => {
  124. return elem.id === id
  125. };
  126. const tryRemove = (page) => page.removeIf(searchFn);
  127. const hookRemove = Object.values(watches).map(tryRemove).reduce((sum, current) => {
  128. return sum || current
  129. }, false);
  130. const triggerRemove = Object.values(triggers).map(tryRemove).reduce((sum, current) => {
  131. return sum || current
  132. }, false);
  133. return hookRemove || triggerRemove;
  134. }
  135. }