core/Behaviour.js

  1. /**
  2. * Behaviour - Concept behaviours
  3. *
  4. * This code is licensed under the MIT License (MIT).
  5. *
  6. * Copyright 2020, 2021, 2022 Rolf Bagge, Janus B. Kristensen, CAVI,
  7. * Center for Advanced Visualization and Interaction, Aarhus University
  8. *
  9. * Permission is hereby granted, free of charge, to any person obtaining a copy
  10. * of this software and associated documentation files (the “Software”), to deal
  11. * in the Software without restriction, including without limitation the rights
  12. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. * copies of the Software, and to permit persons to whom the Software is
  14. * furnished to do so, subject to the following conditions:
  15. *
  16. * The above copyright notice and this permission notice shall be included in
  17. * all copies or substantial portions of the Software.
  18. *
  19. * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. * THE SOFTWARE.
  26. *
  27. */
  28. /**
  29. *
  30. */
  31. class Behaviour {
  32. constructor(name, triggers, actions, concept, overrideActionName=null) {
  33. const self = this;
  34. this.concept = concept;
  35. if(triggers == null) {
  36. triggers = [];
  37. }
  38. if(actions == null) {
  39. actions = [];
  40. }
  41. if(!Array.isArray(triggers)) {
  42. triggers = [triggers];
  43. }
  44. if(!Array.isArray(actions)) {
  45. actions = [actions];
  46. }
  47. this.cloneData = {
  48. name: name,
  49. triggers: triggers!=null?JSON.parse(JSON.stringify(triggers)):null,
  50. actions: actions!=null?JSON.parse(JSON.stringify(actions)):null,
  51. overrideActionName: overrideActionName
  52. }
  53. this.name = name;
  54. this.triggers = triggers.map((triggerJson)=>{
  55. if(typeof triggerJson === "string") {
  56. if(!Trigger.isTriggerType(triggerJson)) {
  57. console.log("Unknown trigger, guessing it is an action trigger:", triggerJson);
  58. triggerJson = {"action": triggerJson};
  59. }
  60. }
  61. let originalStringTrigger = null;
  62. if(typeof triggerJson === "string") {
  63. originalStringTrigger = triggerJson;
  64. triggerJson = {};
  65. triggerJson[originalStringTrigger] = {};
  66. }
  67. if(typeof triggerJson !== "string") {
  68. let triggerName = UUIDGenerator.generateUUID("trigger-");
  69. let trigger = ConceptLoader.parseTrigger(triggerName, triggerJson, concept);
  70. if(trigger != null) {
  71. self.concept.addTrigger(trigger);
  72. return triggerName;
  73. } else {
  74. if(originalStringTrigger == null) {
  75. console.warn("Unable to parse anonymous trigger:", triggerJson);
  76. } else {
  77. return originalStringTrigger;
  78. }
  79. }
  80. return null;
  81. }
  82. return triggerJson;
  83. }).filter((trigger)=>{
  84. return typeof trigger === "string";
  85. });
  86. this.deleteCallbacks = [];
  87. let actionName = name+".actions";
  88. if(overrideActionName != null) {
  89. actionName = overrideActionName;
  90. this.callableAction = true;
  91. }
  92. this.actionChain = new ActionChain(actionName, {}, this.concept);
  93. actions.forEach((actionJson)=>{
  94. if(typeof actionJson !== "string") {
  95. let actionName = UUIDGenerator.generateUUID("action-");
  96. if(!Array.isArray(actionJson)) {
  97. actionJson = [actionJson];
  98. }
  99. let action = ConceptLoader.parseAction(actionName, actionJson, self.concept);
  100. if(action != null) {
  101. self.actionChain.addAction(action);
  102. } else {
  103. console.warn("Unable to parse anonymous action:", actionJson);
  104. }
  105. } else {
  106. let action = null;
  107. //Check for primitive action first.
  108. if(Action.hasPrimitiveAction(actionJson)) {
  109. action = Action.getPrimitiveAction(actionJson, {}, concept);
  110. } else {
  111. action = new LookupActionAction("", {
  112. lookupActionName: actionJson
  113. }, concept);
  114. }
  115. self.actionChain.addAction(action);
  116. }
  117. });
  118. }
  119. cloneFresh(concept) {
  120. return new Behaviour(this.cloneData.name, this.cloneData.triggers, this.cloneData.actions, concept, this.cloneData.overrideActionName);
  121. }
  122. setupEvents() {
  123. const self = this;
  124. this.triggers.forEach((trigger)=>{
  125. self.deleteCallbacks.push(Trigger.registerTriggerEvent(trigger, async (context) => {
  126. try {
  127. await self.onTrigger(trigger, context);
  128. } catch(e) {
  129. console.error(e);
  130. }
  131. }));
  132. });
  133. }
  134. async onTrigger(triggerName, context) {
  135. try {
  136. await ActionTrigger.before(this.actionChain, context);
  137. let mark = VarvPerformance.start();
  138. let resultContext = await this.actionChain.apply(context);
  139. if(this.actionChain.isPrimitive) {
  140. VarvPerformance.stop("PrimitiveAction-"+this.actionChain.name, mark);
  141. } else {
  142. VarvPerformance.stop("CustomAction-"+this.actionChain.name, mark);
  143. }
  144. await ActionTrigger.after(this.actionChain, resultContext);
  145. } catch(e) {
  146. if(e instanceof StopError) {
  147. //console.log("We stopped the chain: "+e.message);
  148. } else {
  149. throw e;
  150. }
  151. }
  152. }
  153. destroy() {
  154. const self = this;
  155. this.deleteCallbacks.forEach((deleteCallback)=>{
  156. deleteCallback.delete();
  157. });
  158. this.triggers.forEach((triggerName)=>{
  159. let trigger = self.concept.getTrigger(triggerName);
  160. if(trigger != null) {
  161. self.concept.removeTrigger(trigger);
  162. }
  163. });
  164. this.deleteCallbacks = null;
  165. this.triggers = null;
  166. this.actionChain = null;
  167. }
  168. }
  169. window.Behaviour = Behaviour;