Home Reference Source

cables_dev/cables/src/core/core_patch.js

  1. import { Logger } from "cables-shared-client";
  2. import { EventTarget } from "./eventtarget.js";
  3. import { ajax, ajaxSync, prefixedHash, cleanJson, shortId } from "./utils.js";
  4. import { LoadingStatus } from "./loadingstatus.js";
  5. import { Timer } from "./timer.js";
  6. import { Link } from "./core_link.js";
  7. import { Profiler } from "./core_profiler.js";
  8. import { Context } from "./cgl/cgl_state.js";
  9. import { CONSTANTS } from "./constants.js";
  10. import PatchVariable from "./core_variable.js";
  11.  
  12.  
  13. /**
  14. * Patch class, contains all operators,values,links etc. manages loading and running of the whole patch
  15. *
  16. * see {@link PatchConfig}
  17. *
  18. * @namespace external:CABLES#Patch
  19. * @hideconstructor
  20. * @param {PatchConfig} cfg The configuration object.
  21. * @class
  22. * @example
  23. * CABLES.patch=new CABLES.Patch(
  24. * {
  25. * patch:pStr,
  26. * glCanvasId:'glcanvas',
  27. * glCanvasResizeToWindow:true,
  28. * canvas:{powerPreference:"high-performance"},
  29. * prefixAssetPath:'/assets/',
  30. * prefixJsPath:'/js/',
  31. * onError:function(e){console.log(e);}
  32. * glslPrecision:'highp'
  33. * });
  34. */
  35.  
  36. class Patch extends EventTarget
  37. {
  38. // const Patch(cfg)
  39. constructor(cfg)
  40. {
  41. super();
  42. // EventTarget.apply(this);
  43.  
  44. this._log = new Logger("core_patch", { "onError": cfg.onError });
  45. this.ops = [];
  46. this.settings = {};
  47. this.config = cfg ||
  48. {
  49. "glCanvasResizeToWindow": false,
  50. "prefixAssetPath": "",
  51. "prefixJsPath": "",
  52. "silent": true,
  53. "onError": null,
  54. "onFinishedLoading": null,
  55. "onFirstFrameRendered": null,
  56. "onPatchLoaded": null,
  57. "fpsLimit": 0
  58. };
  59. this.timer = new Timer();
  60. this.freeTimer = new Timer();
  61. this.animFrameOps = [];
  62. this.animFrameCallbacks = [];
  63. this.gui = false;
  64. CABLES.logSilent = this.silent = true;
  65. this.profiler = null;
  66. this.aborted = false;
  67. this._crashedOps = [];
  68. this._renderOneFrame = false;
  69. this._animReq = null;
  70. this._opIdCache = {};
  71. this._triggerStack = [];
  72. this.storeObjNames = false; // remove after may release
  73.  
  74. this.loading = new LoadingStatus(this);
  75.  
  76. this._volumeListeners = [];
  77. this._paused = false;
  78. this._frameNum = 0;
  79. this.onOneFrameRendered = null;
  80. this.namedTriggers = {};
  81.  
  82. this._origData = null;
  83. this._frameNext = 0;
  84. this._frameInterval = 0;
  85. this._lastFrameTime = 0;
  86. this._frameWasdelayed = true;
  87. this.tempData = this.frameStore = {};
  88. this.deSerialized = false;
  89. this.reqAnimTimeStamp = 0;
  90.  
  91. this.cgCanvas = null;
  92.  
  93. if (!(function () { return !this; }())) console.log("not in strict mode: core patch");
  94.  
  95. this._isLocal = document.location.href.indexOf("file:") === 0;
  96.  
  97. if (this.config.hasOwnProperty("silent")) this.silent = CABLES.logSilent = this.config.silent;
  98. if (!this.config.hasOwnProperty("doRequestAnimation")) this.config.doRequestAnimation = true;
  99.  
  100. if (!this.config.prefixAssetPath) this.config.prefixAssetPath = "";
  101. if (!this.config.prefixJsPath) this.config.prefixJsPath = "";
  102. if (!this.config.masterVolume) this.config.masterVolume = 1.0;
  103.  
  104. this._variables = {};
  105. this._variableListeners = [];
  106. this.vars = {};
  107. if (cfg && cfg.vars) this.vars = cfg.vars; // vars is old!
  108.  
  109. this.cgl = new Context(this);
  110. this.cgp = null;
  111.  
  112. this._subpatchOpCache = {};
  113.  
  114. this.cgl.setCanvas(this.config.glCanvasId || this.config.glCanvas || "glcanvas");
  115. if (this.config.glCanvasResizeToWindow === true) this.cgl.setAutoResize("window");
  116. if (this.config.glCanvasResizeToParent === true) this.cgl.setAutoResize("parent");
  117. this.loading.setOnFinishedLoading(this.config.onFinishedLoading);
  118.  
  119. if (this.cgl.aborted) this.aborted = true;
  120. if (this.cgl.silent) this.silent = true;
  121.  
  122. this.freeTimer.play();
  123. this.exec();
  124.  
  125. if (!this.aborted)
  126. {
  127. if (this.config.patch)
  128. {
  129. this.deSerialize(this.config.patch);
  130. }
  131. else if (this.config.patchFile)
  132. {
  133. ajax(
  134. this.config.patchFile,
  135. (err, _data) =>
  136. {
  137. try
  138. {
  139. const data = JSON.parse(_data);
  140. if (err)
  141. {
  142. const txt = "";
  143. this._log.error("err", err);
  144. this._log.error("data", data);
  145. this._log.error("data", data.msg);
  146. return;
  147. }
  148. this.deSerialize(data);
  149. }
  150. catch (e)
  151. {
  152. this._log.error("could not load/parse patch ", e);
  153. }
  154. }
  155. );
  156. }
  157. this.timer.play();
  158. }
  159.  
  160. console.log("made with https://cables.gl"); // eslint-disable-line
  161. }
  162.  
  163. isPlaying()
  164. {
  165. return !this._paused;
  166. }
  167.  
  168. isRenderingOneFrame()
  169. {
  170. return this._renderOneFrame;
  171. }
  172.  
  173.  
  174. renderOneFrame()
  175. {
  176. this._paused = true;
  177. this._renderOneFrame = true;
  178. this.exec();
  179. this._renderOneFrame = false;
  180. }
  181.  
  182. /**
  183. * current number of frames per second
  184. * @function getFPS
  185. * @memberof Patch
  186. * @instance
  187. * @return {Number} fps
  188. */
  189. getFPS()
  190. {
  191. this._log.error("deprecated getfps");
  192. return 0;
  193. }
  194.  
  195. /**
  196. * returns true if patch is opened in editor/gui mode
  197. * @function isEditorMode
  198. * @memberof Patch
  199. * @instance
  200. * @return {Boolean} editor mode
  201. */
  202. isEditorMode()
  203. {
  204. return this.config.editorMode === true;
  205. }
  206.  
  207. /**
  208. * pauses patch execution
  209. * @function pause
  210. * @memberof Patch
  211. * @instance
  212. */
  213. pause()
  214. {
  215. cancelAnimationFrame(this._animReq);
  216. this.emitEvent("pause");
  217. this._animReq = null;
  218. this._paused = true;
  219. this.freeTimer.pause();
  220. }
  221.  
  222. /**
  223. * resumes patch execution
  224. * @function resume
  225. * @memberof Patch
  226. * @instance
  227. */
  228. resume()
  229. {
  230. if (this._paused)
  231. {
  232. cancelAnimationFrame(this._animReq);
  233. this._paused = false;
  234. this.freeTimer.play();
  235. this.emitEvent("resume");
  236. this.exec();
  237. }
  238. }
  239.  
  240. /**
  241. * set volume [0-1]
  242. * @function setVolume
  243. * @param {Number} v volume
  244. * @memberof Patch
  245. * @instance
  246. */
  247. setVolume(v)
  248. {
  249. this.config.masterVolume = v;
  250. for (let i = 0; i < this._volumeListeners.length; i++) this._volumeListeners[i].onMasterVolumeChanged(v);
  251. }
  252.  
  253.  
  254. /**
  255. * get asset path
  256. * @function getAssetPath
  257. * @memberof Patch
  258. * @param patchId
  259. * @instance
  260. */
  261. getAssetPath(patchId = null)
  262. {
  263. if (this.config.hasOwnProperty("assetPath"))
  264. {
  265. return this.config.assetPath;
  266. }
  267. else if (this.isEditorMode())
  268. {
  269. let id = patchId || gui.project()._id;
  270. return "/assets/" + id + "/";
  271. }
  272. else if (document.location.href.indexOf("cables.gl") > 0 || document.location.href.indexOf("cables.local") > 0)
  273. {
  274. const parts = document.location.pathname.split("/");
  275. let id = patchId || parts[parts.length - 1];
  276. return "/assets/" + id + "/";
  277. }
  278. else
  279. {
  280. return "assets/";
  281. }
  282. }
  283.  
  284. /**
  285. * get js path
  286. * @function getJsPath
  287. * @memberof Patch
  288. * @instance
  289. */
  290. getJsPath()
  291. {
  292. if (this.config.hasOwnProperty("jsPath"))
  293. {
  294. return this.config.jsPath;
  295. }
  296. else
  297. {
  298. return "js/";
  299. }
  300. }
  301.  
  302. /**
  303. * get url/filepath for a filename
  304. * this uses prefixAssetpath in exported patches
  305. * @function getFilePath
  306. * @memberof Patch
  307. * @instance
  308. * @param {String} filename
  309. * @return {String} url
  310. */
  311. getFilePath(filename)
  312. {
  313. if (!filename) return filename;
  314. filename = String(filename);
  315. if (filename.indexOf("https:") === 0 || filename.indexOf("http:") === 0) return filename;
  316. if (filename.indexOf("data:") === 0) return filename;
  317. if (filename.indexOf("file:") === 0) return filename;
  318. filename = filename.replace("//", "/");
  319. if (filename.startsWith(this.config.prefixAssetPath)) filename = filename.replace(this.config.prefixAssetPath, "");
  320. return this.config.prefixAssetPath + filename + (this.config.suffixAssetPath || "");
  321. }
  322.  
  323. clear()
  324. {
  325. this.emitEvent("patchClearStart");
  326. this.cgl.TextureEffectMesh = null;
  327. this.animFrameOps.length = 0;
  328. this.timer = new Timer();
  329. while (this.ops.length > 0) this.deleteOp(this.ops[0].id);
  330.  
  331. this._opIdCache = {};
  332. this.emitEvent("patchClearEnd");
  333. }
  334.  
  335.  
  336.  
  337.  
  338. createOp(identifier, id, opName = null)
  339. {
  340. let op = null;
  341. let objName = "";
  342.  
  343. try
  344. {
  345. if (!identifier)
  346. {
  347. console.error("createop identifier false", identifier);
  348. console.log((new Error()).stack);
  349. return;
  350. }
  351. if (identifier.indexOf("Ops.") === -1)
  352. {
  353. // this should be a uuid, not a namespace
  354. // creating ops by id should be the default way from now on!
  355. const opId = identifier;
  356.  
  357.  
  358.  
  359. if (CABLES.OPS[opId])
  360. {
  361. objName = CABLES.OPS[opId].objName;
  362. op = new CABLES.OPS[opId].f(this, objName, id, opId);
  363. op.opId = opId;
  364. }
  365. else
  366. {
  367. if (opName)
  368. {
  369. identifier = opName;
  370. this._log.warn("could not find op by id: " + opId);
  371. }
  372. else
  373. {
  374. throw new Error("could not find op by id: " + opId, { "cause": "opId:" + opId });
  375. }
  376. }
  377. }
  378.  
  379. if (!op)
  380. {
  381. // fallback: create by objname!
  382. objName = identifier;
  383. const parts = identifier.split(".");
  384. const opObj = Patch.getOpClass(objName);
  385.  
  386. if (!opObj)
  387. {
  388. this.emitEvent("criticalError", { "title": "unknown op" + objName, "text": "unknown op: " + objName });
  389.  
  390. this._log.error("unknown op: " + objName);
  391. throw new Error("unknown op: " + objName);
  392. }
  393. else
  394. {
  395. if (parts.length == 2) op = new window[parts[0]][parts[1]](this, objName, id);
  396. else if (parts.length == 3) op = new window[parts[0]][parts[1]][parts[2]](this, objName, id);
  397. else if (parts.length == 4) op = new window[parts[0]][parts[1]][parts[2]][parts[3]](this, objName, id);
  398. else if (parts.length == 5) op = new window[parts[0]][parts[1]][parts[2]][parts[3]][parts[4]](this, objName, id);
  399. else if (parts.length == 6) op = new window[parts[0]][parts[1]][parts[2]][parts[3]][parts[4]][parts[5]](this, objName, id);
  400. else if (parts.length == 7) op = new window[parts[0]][parts[1]][parts[2]][parts[3]][parts[4]][parts[5]][parts[6]](this, objName, id);
  401. else if (parts.length == 8) op = new window[parts[0]][parts[1]][parts[2]][parts[3]][parts[4]][parts[5]][parts[6]][parts[7]](this, objName, id);
  402. else if (parts.length == 9) op = new window[parts[0]][parts[1]][parts[2]][parts[3]][parts[4]][parts[5]][parts[6]][parts[7]][parts[8]](this, objName, id);
  403. else if (parts.length == 10) op = new window[parts[0]][parts[1]][parts[2]][parts[3]][parts[4]][parts[5]][parts[6]][parts[7]][parts[8]][parts[9]](this, objName, id);
  404. else console.log("parts.length", parts.length);
  405. }
  406.  
  407. if (op)
  408. {
  409. op.opId = null;
  410. for (const i in CABLES.OPS)
  411. {
  412. if (CABLES.OPS[i].objName == objName) op.opId = i;
  413. }
  414. }
  415. }
  416. }
  417. catch (e)
  418. {
  419. this._crashedOps.push(objName);
  420.  
  421. this._log.error("[instancing error] " + objName, e);
  422.  
  423. if (!this.isEditorMode())
  424. {
  425. this._log.error("INSTANCE_ERR", "Instancing Error: " + objName, e);
  426. // throw new Error("instancing error 1" + objName);
  427. }
  428. }
  429.  
  430. if (op)
  431. {
  432. op._objName = objName;
  433. op.patch = this;
  434. }
  435. else
  436. {
  437. this._log.log("no op was created!?", identifier, id);
  438. }
  439. return op;
  440. }
  441.  
  442. /**
  443. * create a new op in patch
  444. * @function addOp
  445. * @memberof Patch
  446. * @instance
  447. * @param {string} opIdentifier uuid or name, e.g. Ops.Math.Sum
  448. * @param {Object} uiAttribs Attributes
  449. * @param {string} id
  450. * @param {boolean} fromDeserialize
  451. * @param {string} opName e.g. Ops.Math.Sum
  452. * @example
  453. * // add invisible op
  454. * patch.addOp('Ops.Math.Sum', { showUiAttribs: false });
  455. */
  456. addOp(opIdentifier, uiAttribs, id, fromDeserialize, opName)
  457. {
  458. const op = this.createOp(opIdentifier, id, opName);
  459.  
  460. if (op)
  461. {
  462. uiAttribs = uiAttribs || {};
  463. if (uiAttribs.hasOwnProperty("errors")) delete uiAttribs.errors;
  464. if (uiAttribs.hasOwnProperty("error")) delete uiAttribs.error;
  465. uiAttribs.subPatch = uiAttribs.subPatch || 0;
  466.  
  467. op.setUiAttribs(uiAttribs);
  468. if (op.onCreate) op.onCreate();
  469.  
  470. if (op.hasOwnProperty("onAnimFrame")) this.addOnAnimFrame(op);
  471. if (op.hasOwnProperty("onMasterVolumeChanged")) this._volumeListeners.push(op);
  472.  
  473. if (this._opIdCache[op.id])
  474. {
  475. this._log.warn("opid with id " + op.id + " already exists in patch!");
  476. this.deleteOp(op.id); // strange with subpatch ops: why is this needed, somehow ops get added twice ???.....
  477. // return;
  478. }
  479.  
  480. this.ops.push(op);
  481. this._opIdCache[op.id] = op;
  482.  
  483. if (this._subPatchCacheAdd) this._subPatchCacheAdd(uiAttribs.subPatch, op);
  484. this.emitEvent("onOpAdd", op, fromDeserialize);
  485.  
  486. if (op.init) op.init();
  487.  
  488. op.emitEvent("init", fromDeserialize);
  489. }
  490. else
  491. {
  492. this._log.error("addop: op could not be created: ", opIdentifier);
  493. }
  494.  
  495. return op;
  496. }
  497.  
  498. addOnAnimFrame(op)
  499. {
  500. for (let i = 0; i < this.animFrameOps.length; i++) if (this.animFrameOps[i] == op) { return; }
  501.  
  502. this.animFrameOps.push(op);
  503. }
  504.  
  505. removeOnAnimFrame(op)
  506. {
  507. for (let i = 0; i < this.animFrameOps.length; i++)
  508. {
  509. if (this.animFrameOps[i] == op)
  510. {
  511. this.animFrameOps.splice(i, 1);
  512. return;
  513. }
  514. }
  515. }
  516.  
  517. addOnAnimFrameCallback(cb)
  518. {
  519. this.animFrameCallbacks.push(cb);
  520. }
  521.  
  522. removeOnAnimCallback(cb)
  523. {
  524. for (let i = 0; i < this.animFrameCallbacks.length; i++)
  525. {
  526. if (this.animFrameCallbacks[i] == cb)
  527. {
  528. this.animFrameCallbacks.splice(i, 1);
  529. return;
  530. }
  531. }
  532. }
  533.  
  534. deleteOp(opid, tryRelink, reloadingOp)
  535. {
  536. let found = false;
  537. for (const i in this.ops)
  538. {
  539. if (this.ops[i].id == opid)
  540. {
  541. const op = this.ops[i];
  542. let reLinkP1 = null;
  543. let reLinkP2 = null;
  544.  
  545. if (op)
  546. {
  547. found = true;
  548. if (tryRelink)
  549. {
  550. if (op.portsIn.length > 0 && op.portsIn[0].isLinked() && (op.portsOut.length > 0 && op.portsOut[0].isLinked()))
  551. {
  552. if (op.portsIn[0].getType() == op.portsOut[0].getType() && op.portsIn[0].links[0])
  553. {
  554. reLinkP1 = op.portsIn[0].links[0].getOtherPort(op.portsIn[0]);
  555. reLinkP2 = op.portsOut[0].links[0].getOtherPort(op.portsOut[0]);
  556. }
  557. }
  558. }
  559.  
  560. const opToDelete = this.ops[i];
  561. opToDelete.removeLinks();
  562.  
  563. if (this.onDelete)
  564. {
  565. // todo: remove
  566. this._log.warn("deprecated this.onDelete", this.onDelete);
  567. this.onDelete(opToDelete);
  568. }
  569.  
  570. this.ops.splice(i, 1);
  571. opToDelete.emitEvent("delete", opToDelete);
  572. this.emitEvent("onOpDelete", opToDelete, reloadingOp);
  573.  
  574. if (this.clearSubPatchCache) this.clearSubPatchCache(opToDelete.uiAttribs.subPatch);
  575.  
  576. if (opToDelete.onDelete) opToDelete.onDelete(reloadingOp);
  577. opToDelete.cleanUp();
  578.  
  579. if (reLinkP1 !== null && reLinkP2 !== null)
  580. {
  581. this.link(reLinkP1.op, reLinkP1.getName(), reLinkP2.op, reLinkP2.getName());
  582. }
  583.  
  584. delete this._opIdCache[opid];
  585. break;
  586. }
  587. }
  588. }
  589.  
  590. if (!found) this._log.warn("core patch deleteop: not found...", opid);
  591. }
  592.  
  593. getFrameNum()
  594. {
  595. return this._frameNum;
  596. }
  597.  
  598. emitOnAnimFrameEvent(time, delta)
  599. {
  600. time = time || this.timer.getTime();
  601.  
  602. for (let i = 0; i < this.animFrameCallbacks.length; ++i)
  603. if (this.animFrameCallbacks[i])
  604. this.animFrameCallbacks[i](time, this._frameNum, delta);
  605.  
  606. for (let i = 0; i < this.animFrameOps.length; ++i)
  607. if (this.animFrameOps[i].onAnimFrame)
  608. this.animFrameOps[i].onAnimFrame(time, this._frameNum, delta);
  609. }
  610.  
  611. renderFrame(timestamp)
  612. {
  613. this.timer.update(this.reqAnimTimeStamp);
  614. this.freeTimer.update(this.reqAnimTimeStamp);
  615. const time = this.timer.getTime();
  616. const startTime = performance.now();
  617. this.cgl.frameStartTime = this.timer.getTime();
  618.  
  619. const delta = timestamp - this.reqAnimTimeStamp || timestamp;
  620.  
  621. this.emitOnAnimFrameEvent(null, delta);
  622.  
  623. this.cgl.profileData.profileFrameDelta = delta;
  624. this.reqAnimTimeStamp = timestamp;
  625. this.cgl.profileData.profileOnAnimFrameOps = performance.now() - startTime;
  626.  
  627. this.emitEvent("onRenderFrame", time);
  628.  
  629. this._frameNum++;
  630. if (this._frameNum == 1)
  631. {
  632. if (this.config.onFirstFrameRendered) this.config.onFirstFrameRendered();
  633. }
  634. }
  635.  
  636. exec(timestamp)
  637. {
  638. if (!this._renderOneFrame && (this._paused || this.aborted)) return;
  639. this.emitEvent("reqAnimFrame");
  640. cancelAnimationFrame(this._animReq);
  641.  
  642. this.config.fpsLimit = this.config.fpsLimit || 0;
  643. if (this.config.fpsLimit)
  644. {
  645. this._frameInterval = 1000 / this.config.fpsLimit;
  646. }
  647.  
  648. const now = CABLES.now();
  649. const frameDelta = now - this._frameNext;
  650.  
  651. if (this.isEditorMode())
  652. {
  653. if (!this._renderOneFrame)
  654. {
  655. if (now - this._lastFrameTime >= 500 && this._lastFrameTime !== 0 && !this._frameWasdelayed)
  656. {
  657. this._lastFrameTime = 0;
  658. setTimeout(this.exec.bind(this), 500);
  659. this.emitEvent("renderDelayStart");
  660. this._frameWasdelayed = true;
  661. return;
  662. }
  663. }
  664. }
  665.  
  666. if (this._renderOneFrame || this.config.fpsLimit === 0 || frameDelta > this._frameInterval || this._frameWasdelayed)
  667. {
  668. this.renderFrame(timestamp);
  669.  
  670. if (this._frameInterval) this._frameNext = now - (frameDelta % this._frameInterval);
  671. }
  672.  
  673. if (this._frameWasdelayed)
  674. {
  675. this.emitEvent("renderDelayEnd");
  676. this._frameWasdelayed = false;
  677. }
  678.  
  679. if (this._renderOneFrame)
  680. {
  681. if (this.onOneFrameRendered) this.onOneFrameRendered(); // todo remove everywhere and use propper event...
  682. this.emitEvent("renderedOneFrame");
  683. this._renderOneFrame = false;
  684. }
  685.  
  686.  
  687. if (this.config.doRequestAnimation) this._animReq = this.cgl.canvas.ownerDocument.defaultView.requestAnimationFrame(this.exec.bind(this));
  688. }
  689.  
  690. /**
  691. * link two ops/ports
  692. * @function link
  693. * @memberof Patch
  694. * @instance
  695. * @param {Op} op1
  696. * @param {String} port1Name
  697. * @param {Op} op2
  698. * @param {String} port2Name
  699. * @param {boolean} lowerCase
  700. * @param {boolean} fromDeserialize
  701. */
  702. link(op1, port1Name, op2, port2Name, lowerCase, fromDeserialize)
  703. {
  704. if (!op1) return this._log.warn("link: op1 is null ");
  705. if (!op2) return this._log.warn("link: op2 is null");
  706.  
  707. const port1 = op1.getPort(port1Name, lowerCase);
  708. const port2 = op2.getPort(port2Name, lowerCase);
  709.  
  710. if (!port1) return op1._log.warn("port1 not found! " + port1Name + " (" + op1.objName + ")");
  711. if (!port2) return op1._log.warn("port2 not found! " + port2Name + " of " + op2.name + "(" + op2.objName + ")", op2);
  712.  
  713. if (!port1.shouldLink(port1, port2) || !port2.shouldLink(port1, port2)) return false;
  714.  
  715. if (Link.canLink(port1, port2))
  716. {
  717. const link = new Link(this);
  718. link.link(port1, port2);
  719.  
  720. this.emitEvent("onLink", port1, port2, link, fromDeserialize);
  721. return link;
  722. }
  723. }
  724.  
  725. serialize(options)
  726. {
  727. const obj = {};
  728.  
  729. options = options || {};
  730. obj.ops = [];
  731. obj.settings = this.settings;
  732. for (const i in this.ops)
  733. {
  734. const op = this.ops[i];
  735. if (op && op.getSerialized)obj.ops.push(op.getSerialized());
  736. }
  737.  
  738. cleanJson(obj);
  739.  
  740. if (options.asObject) return obj;
  741. return JSON.stringify(obj);
  742. }
  743.  
  744. getOpsByRefId(refId)
  745. {
  746. const perf = CABLES.UI.uiProfiler.start("[corepatchetend] getOpsByRefId");
  747. const refOps = [];
  748. const ops = gui.corePatch().ops;
  749. for (let i = 0; i < ops.length; i++)
  750. if (ops[i].storage && ops[i].storage.ref == refId) refOps.push(ops[i]);
  751. perf.finish();
  752. return refOps;
  753. }
  754.  
  755. getOpById(opid)
  756. {
  757. return this._opIdCache[opid];
  758. }
  759.  
  760. getOpsByName(name)
  761. {
  762. // TODO: is this still needed ? unclear behaviour....
  763. const arr = [];
  764. for (const i in this.ops)
  765. if (this.ops[i].name == name) arr.push(this.ops[i]);
  766. return arr;
  767. }
  768.  
  769. getOpsByObjName(name)
  770. {
  771. const arr = [];
  772. for (const i in this.ops)
  773. if (this.ops[i].objName == name) arr.push(this.ops[i]);
  774. return arr;
  775. }
  776.  
  777. getOpsByOpId(opid)
  778. {
  779. const arr = [];
  780. for (const i in this.ops)
  781. if (this.ops[i].opId == opid) arr.push(this.ops[i]);
  782. return arr;
  783. }
  784.  
  785. loadLib(which)
  786. {
  787. ajaxSync(
  788. "/ui/libs/" + which + ".js",
  789. (err, res) =>
  790. {
  791. const se = document.createElement("script");
  792. se.type = "text/javascript";
  793. se.text = res;
  794. document.getElementsByTagName("head")[0].appendChild(se);
  795. },
  796. "GET",
  797. );
  798. }
  799.  
  800. getSubPatchOpsByName(patchId, objName)
  801. {
  802. const arr = [];
  803. for (const i in this.ops)
  804. if (this.ops[i].uiAttribs && this.ops[i].uiAttribs.subPatch == patchId && this.ops[i].objName == objName)
  805. arr.push(this.ops[i]);
  806.  
  807. return arr;
  808. }
  809.  
  810. getSubPatchOp(patchId, objName)
  811. {
  812. return this.getFirstSubPatchOpByName(patchId, objName);
  813. }
  814.  
  815. getFirstSubPatchOpByName(patchId, objName)
  816. {
  817. for (const i in this.ops)
  818. if (this.ops[i].uiAttribs && this.ops[i].uiAttribs.subPatch == patchId && this.ops[i].objName == objName)
  819. return this.ops[i];
  820.  
  821. return false;
  822. }
  823.  
  824. _addLink(opinid, opoutid, inName, outName)
  825. {
  826. return this.link(this.getOpById(opinid), inName, this.getOpById(opoutid), outName, false, true);
  827. }
  828.  
  829. deSerialize(obj, options)
  830. {
  831. options = options || { "genIds": false, "createRef": false };
  832. if (this.aborted) return;
  833. const newOps = [];
  834. const loadingId = this.loading.start("core", "deserialize");
  835.  
  836. this.namespace = obj.namespace || "";
  837. this.name = obj.name || "";
  838.  
  839. if (typeof obj === "string") obj = JSON.parse(obj);
  840.  
  841. this.settings = obj.settings;
  842.  
  843. this.emitEvent("patchLoadStart");
  844.  
  845. obj.ops = obj.ops || [];
  846.  
  847. if (window.logStartup)logStartup("add " + obj.ops.length + " ops... ");
  848.  
  849. const addedOps = [];
  850.  
  851. // add ops...
  852. for (let iop = 0; iop < obj.ops.length; iop++)
  853. {
  854. const start = CABLES.now();
  855. const opData = obj.ops[iop];
  856. let op = null;
  857.  
  858. try
  859. {
  860. if (opData.opId) op = this.addOp(opData.opId, opData.uiAttribs, opData.id, true, opData.objName);
  861. else op = this.addOp(opData.objName, opData.uiAttribs, opData.id, true);
  862. }
  863. catch (e)
  864. {
  865. this._log.error("[instancing error] op data:", opData, e);
  866. // throw new Error("could not create op by id: <b>" + (opData.objName || opData.opId) + "</b> (" + opData.id + ")");
  867. }
  868.  
  869. if (op)
  870. {
  871. addedOps.push(op);
  872. if (options.genIds) op.id = shortId();
  873. op.portsInData = opData.portsIn;
  874. op._origData = JSON.parse(JSON.stringify(opData));
  875. op.storage = opData.storage;
  876. // if (opData.hasOwnProperty("disabled"))op.setEnabled(!opData.disabled);
  877.  
  878. for (const ipi in opData.portsIn)
  879. {
  880. const objPort = opData.portsIn[ipi];
  881. if (objPort && objPort.hasOwnProperty("name"))
  882. {
  883. const port = op.getPort(objPort.name);
  884.  
  885. if (port && (port.uiAttribs.display == "bool" || port.uiAttribs.type == "bool") && !isNaN(objPort.value)) objPort.value = objPort.value == true ? 1 : 0;
  886. if (port && objPort.value !== undefined && port.type != CONSTANTS.OP.OP_PORT_TYPE_TEXTURE) port.set(objPort.value);
  887.  
  888. if (port)
  889. {
  890. port.deSerializeSettings(objPort);
  891. }
  892. else
  893. {
  894. // if (port.uiAttribs.hasOwnProperty("title"))
  895. // {
  896. // op.preservedPortTitles = op.preservedPortTitles || {};
  897. // op.preservedPortTitles[port.name] = port.uiAttribs.title;
  898. // }
  899. op.preservedPortValues = op.preservedPortValues || {};
  900. op.preservedPortValues[objPort.name] = objPort.value;
  901. }
  902. }
  903. }
  904.  
  905. for (const ipo in opData.portsOut)
  906. {
  907. const objPort = opData.portsOut[ipo];
  908. if (objPort && objPort.hasOwnProperty("name"))
  909. {
  910. const port2 = op.getPort(objPort.name);
  911.  
  912. if (port2)
  913. {
  914. port2.deSerializeSettings(objPort);
  915.  
  916. if (port2.uiAttribs.hasOwnProperty("title"))
  917. {
  918. op.preservedPortTitles = op.preservedPortTitles || {};
  919. op.preservedPortTitles[port2.name] = port2.uiAttribs.title;
  920. }
  921.  
  922.  
  923. if (port2.type != CONSTANTS.OP.OP_PORT_TYPE_TEXTURE && objPort.hasOwnProperty("value"))
  924. port2.set(obj.ops[iop].portsOut[ipo].value);
  925.  
  926. if (objPort.expose) port2.setUiAttribs({ "expose": true });
  927. }
  928. }
  929. }
  930. newOps.push(op);
  931. }
  932.  
  933. const timeused = Math.round(100 * (CABLES.now() - start)) / 100;
  934. if (!this.silent && timeused > 5) console.log("long op init ", obj.ops[iop].objName, timeused);
  935. }
  936. if (window.logStartup)logStartup("add ops done");
  937.  
  938. for (const i in this.ops)
  939. {
  940. if (this.ops[i].onLoadedValueSet)
  941. {
  942. this.ops[i].onLoadedValueSet(this.ops[i]._origData);
  943. this.ops[i].onLoadedValueSet = null;
  944. this.ops[i]._origData = null;
  945. }
  946. this.ops[i].emitEvent("loadedValueSet");
  947. }
  948.  
  949. if (window.logStartup)logStartup("creating links");
  950.  
  951. if (options.opsCreated)options.opsCreated(addedOps);
  952. // create links...
  953. if (obj.ops)
  954. {
  955. for (let iop = 0; iop < obj.ops.length; iop++)
  956. {
  957. if (obj.ops[iop].portsIn)
  958. {
  959. for (let ipi2 = 0; ipi2 < obj.ops[iop].portsIn.length; ipi2++)
  960. {
  961. if (obj.ops[iop].portsIn[ipi2] && obj.ops[iop].portsIn[ipi2].links)
  962. {
  963. for (let ili = 0; ili < obj.ops[iop].portsIn[ipi2].links.length; ili++)
  964. {
  965. const l = this._addLink(
  966. obj.ops[iop].portsIn[ipi2].links[ili].objIn,
  967. obj.ops[iop].portsIn[ipi2].links[ili].objOut,
  968. obj.ops[iop].portsIn[ipi2].links[ili].portIn,
  969. obj.ops[iop].portsIn[ipi2].links[ili].portOut);
  970.  
  971. // const took = performance.now - startTime;
  972. // if (took > 100)console.log(obj().ops[iop].portsIn[ipi2].links[ili].objIn, obj.ops[iop].portsIn[ipi2].links[ili].objOut, took);
  973. }
  974. }
  975. }
  976. }
  977. if (obj.ops[iop].portsOut)
  978. for (let ipi2 = 0; ipi2 < obj.ops[iop].portsOut.length; ipi2++)
  979. if (obj.ops[iop].portsOut[ipi2] && obj.ops[iop].portsOut[ipi2].links)
  980. {
  981. for (let ili = 0; ili < obj.ops[iop].portsOut[ipi2].links.length; ili++)
  982. {
  983. if (obj.ops[iop].portsOut[ipi2].links[ili])
  984. {
  985. if (obj.ops[iop].portsOut[ipi2].links[ili].subOpRef)
  986. {
  987. // lost link
  988. const outOp = this.getOpById(obj.ops[iop].portsOut[ipi2].links[ili].objOut);
  989. let dstOp = null;
  990. let theSubPatch = 0;
  991.  
  992. for (let i = 0; i < this.ops.length; i++)
  993. {
  994. if (
  995. this.ops[i].storage &&
  996. this.ops[i].storage.ref == obj.ops[iop].portsOut[ipi2].links[ili].subOpRef &&
  997. outOp.uiAttribs.subPatch == this.ops[i].uiAttribs.subPatch
  998. )
  999. {
  1000. theSubPatch = this.ops[i].patchId.get();
  1001. break;
  1002. }
  1003. }
  1004.  
  1005. for (let i = 0; i < this.ops.length; i++)
  1006. {
  1007. if (
  1008. this.ops[i].storage &&
  1009. this.ops[i].storage.ref == obj.ops[iop].portsOut[ipi2].links[ili].refOp &&
  1010. this.ops[i].uiAttribs.subPatch == theSubPatch)
  1011. {
  1012. dstOp = this.ops[i];
  1013. break;
  1014. }
  1015. }
  1016.  
  1017. if (!dstOp) this._log.warn("could not find op for lost link");
  1018. else
  1019. {
  1020. const l = this._addLink(
  1021. dstOp.id,
  1022. obj.ops[iop].portsOut[ipi2].links[ili].objOut,
  1023.  
  1024. obj.ops[iop].portsOut[ipi2].links[ili].portIn,
  1025. obj.ops[iop].portsOut[ipi2].links[ili].portOut);
  1026. }
  1027. }
  1028. else
  1029. {
  1030. const l = this._addLink(obj.ops[iop].portsOut[ipi2].links[ili].objIn, obj.ops[iop].portsOut[ipi2].links[ili].objOut, obj.ops[iop].portsOut[ipi2].links[ili].portIn, obj.ops[iop].portsOut[ipi2].links[ili].portOut);
  1031.  
  1032. if (!l)
  1033. {
  1034. const op1 = this.getOpById(obj.ops[iop].portsOut[ipi2].links[ili].objIn);
  1035. const op2 = this.getOpById(obj.ops[iop].portsOut[ipi2].links[ili].objOut);
  1036.  
  1037. if (!op1)console.log("could not find link op1");
  1038. if (!op2)console.log("could not find link op2");
  1039.  
  1040. const p1Name = obj.ops[iop].portsOut[ipi2].links[ili].portIn;
  1041.  
  1042. if (op1 && !op1.getPort(p1Name))
  1043. {
  1044. // console.log("PRESERVE port 1 not found", p1Name);
  1045.  
  1046. op1.preservedPortLinks[p1Name] = op1.preservedPortLinks[p1Name] || [];
  1047. op1.preservedPortLinks[p1Name].push(obj.ops[iop].portsOut[ipi2].links[ili]);
  1048. }
  1049.  
  1050. const p2Name = obj.ops[iop].portsOut[ipi2].links[ili].portOut;
  1051. if (op2 && !op2.getPort(p2Name))
  1052. {
  1053. // console.log("PRESERVE port 2 not found", obj.ops[iop].portsOut[ipi2].links[ili].portOut);
  1054. op2.preservedPortLinks[p1Name] = op2.preservedPortLinks[p1Name] || [];
  1055. op2.preservedPortLinks[p1Name].push(obj.ops[iop].portsOut[ipi2].links[ili]);
  1056. }
  1057. }
  1058. }
  1059. }
  1060. }
  1061. }
  1062. }
  1063. }
  1064.  
  1065. if (window.logStartup)logStartup("calling ops onloaded");
  1066.  
  1067. for (const i in this.ops)
  1068. {
  1069. if (this.ops[i].onLoaded)
  1070. {
  1071. // TODO: deprecate!!!
  1072. this.ops[i].onLoaded();
  1073. this.ops[i].onLoaded = null;
  1074. }
  1075. }
  1076.  
  1077. if (window.logStartup)logStartup("initializing ops...");
  1078. for (const i in this.ops)
  1079. {
  1080. if (this.ops[i].init)
  1081. {
  1082. try
  1083. {
  1084. this.ops[i].init();
  1085. this.ops[i].init = null;
  1086. }
  1087. catch (e)
  1088. {
  1089. console.error("op.init crash", e);
  1090. }
  1091. }
  1092. }
  1093.  
  1094. if (window.logStartup)logStartup("initializing vars...");
  1095.  
  1096. if (this.config.variables)
  1097. for (const varName in this.config.variables)
  1098. this.setVarValue(varName, this.config.variables[varName]);
  1099.  
  1100. if (window.logStartup)logStartup("initializing var ports");
  1101.  
  1102. for (const i in this.ops)
  1103. {
  1104. this.ops[i].initVarPorts();
  1105. delete this.ops[i].uiAttribs.pasted;
  1106. }
  1107.  
  1108. setTimeout(() => { this.loading.finished(loadingId); }, 100);
  1109.  
  1110. if (this.config.onPatchLoaded) this.config.onPatchLoaded(this);
  1111.  
  1112. this.deSerialized = true;
  1113. this.emitEvent("patchLoadEnd", newOps, obj, options.genIds);
  1114. }
  1115.  
  1116. profile(enable)
  1117. {
  1118. this.profiler = new Profiler(this);
  1119. for (const i in this.ops)
  1120. {
  1121. this.ops[i].profile(enable);
  1122. }
  1123. }
  1124.  
  1125. // ----------------------
  1126.  
  1127. /**
  1128. * set variable value
  1129. * @function setVariable
  1130. * @memberof Patch
  1131. * @instance
  1132. * @param {String} name of variable
  1133. * @param {Number|String|Boolean} val value
  1134. */
  1135. setVariable(name, val)
  1136. {
  1137. // if (this._variables.hasOwnProperty(name))
  1138. if (this._variables[name] !== undefined)
  1139. {
  1140. this._variables[name].setValue(val);
  1141. }
  1142. else
  1143. {
  1144. this._log.warn("variable " + name + " not found!");
  1145. }
  1146. }
  1147.  
  1148. _sortVars()
  1149. {
  1150. if (!this.isEditorMode()) return;
  1151. const ordered = {};
  1152. Object.keys(this._variables).sort(
  1153. (a, b) =>
  1154. { return a.localeCompare(b, "en", { "sensitivity": "base" }); }
  1155. ).forEach((key) =>
  1156. {
  1157. ordered[key] = this._variables[key];
  1158. });
  1159. this._variables = ordered;
  1160. }
  1161.  
  1162. /**
  1163. * has variable
  1164. * @function hasVariable
  1165. * @memberof Patch
  1166. * @instance
  1167. * @param {String} name of variable
  1168. */
  1169. hasVar(name)
  1170. {
  1171. return this._variables[name] !== undefined;
  1172.  
  1173. // return this._variables.hasOwnProperty(name);
  1174. }
  1175.  
  1176. // used internally
  1177. setVarValue(name, val, type)
  1178. {
  1179. if (this.hasVar(name))
  1180. {
  1181. this._variables[name].setValue(val);
  1182. }
  1183. else
  1184. {
  1185. this._variables[name] = new PatchVariable(name, val, type);
  1186. this._sortVars();
  1187. this.emitEvent("variablesChanged");
  1188. }
  1189. return this._variables[name];
  1190. }
  1191.  
  1192. // old?
  1193. getVarValue(name, val)
  1194. {
  1195. if (this._variables.hasOwnProperty(name)) return this._variables[name].getValue();
  1196. }
  1197.  
  1198. /**
  1199. * @function getVar
  1200. * @memberof Patch
  1201. * @instance
  1202. * @param {String} name
  1203. * @return {Variable} variable
  1204. */
  1205. getVar(name)
  1206. {
  1207. if (this._variables.hasOwnProperty(name)) return this._variables[name];
  1208. }
  1209.  
  1210.  
  1211. deleteVar(name)
  1212. {
  1213. for (let i = 0; i < this.ops.length; i++)
  1214. for (let j = 0; j < this.ops[i].portsIn.length; j++)
  1215. if (this.ops[i].portsIn[j].getVariableName() == name)
  1216. this.ops[i].portsIn[j].setVariable(null);
  1217.  
  1218. delete this._variables[name];
  1219. this.emitEvent("variableDeleted", name);
  1220. this.emitEvent("variablesChanged");
  1221. }
  1222.  
  1223. /**
  1224. * @function getVars
  1225. * @memberof Patch
  1226. * @instance
  1227. * @param t
  1228. * @return {Array<Variable>} variables
  1229. * @function
  1230. */
  1231. getVars(t)
  1232. {
  1233. if (t === undefined) return this._variables;
  1234.  
  1235. const vars = [];
  1236. if (t == CABLES.OP_PORT_TYPE_STRING) t = "string";
  1237. if (t == CABLES.OP_PORT_TYPE_VALUE) t = "number";
  1238. if (t == CABLES.OP_PORT_TYPE_ARRAY) t = "array";
  1239. if (t == CABLES.OP_PORT_TYPE_OBJECT) t = "object";
  1240.  
  1241. for (const i in this._variables)
  1242. {
  1243. if (!this._variables[i].type || this._variables[i].type == t) vars.push(this._variables[i]);
  1244. }
  1245. return vars;
  1246. }
  1247.  
  1248.  
  1249. /**
  1250. * @function preRenderOps
  1251. * @memberof Patch
  1252. * @instance
  1253. * @description invoke pre rendering of ops
  1254. * @function
  1255. */
  1256. preRenderOps()
  1257. {
  1258. this._log.log("prerendering...");
  1259.  
  1260. for (let i = 0; i < this.ops.length; i++)
  1261. {
  1262. if (this.ops[i].preRender)
  1263. {
  1264. this.ops[i].preRender();
  1265. this._log.log("prerender " + this.ops[i].objName);
  1266. }
  1267. }
  1268. }
  1269.  
  1270. /**
  1271. * @function dispose
  1272. * @memberof Patch
  1273. * @instance
  1274. * @description stop, dispose and cleanup patch
  1275. */
  1276. dispose()
  1277. {
  1278. this.pause();
  1279. this.clear();
  1280. this.cgl.dispose();
  1281. }
  1282.  
  1283. pushTriggerStack(p)
  1284. {
  1285. this._triggerStack.push(p);
  1286. }
  1287.  
  1288. popTriggerStack()
  1289. {
  1290. this._triggerStack.pop();
  1291. }
  1292.  
  1293. printTriggerStack()
  1294. {
  1295. if (this._triggerStack.length == 0)
  1296. {
  1297. // console.log("stack length", this._triggerStack.length); // eslint-disable-line
  1298. return;
  1299. }
  1300. console.groupCollapsed( // eslint-disable-line
  1301. "trigger port stack " + this._triggerStack[this._triggerStack.length - 1].op.objName + "." + this._triggerStack[this._triggerStack.length - 1].name,
  1302. );
  1303.  
  1304. const rows = [];
  1305. for (let i = 0; i < this._triggerStack.length; i++)
  1306. {
  1307. rows.push(i + ". " + this._triggerStack[i].op.objName + " " + this._triggerStack[i].name);
  1308. }
  1309.  
  1310. console.table(rows); // eslint-disable-line
  1311. console.groupEnd(); // eslint-disable-line
  1312. }
  1313.  
  1314. /**
  1315. * returns document object of the patch could be != global document object when opening canvas ina popout window
  1316. * @function getDocument
  1317. * @memberof Patch
  1318. * @instance
  1319. * @return {Object} document
  1320. */
  1321. getDocument()
  1322. {
  1323. return this.cgl.canvas.ownerDocument;
  1324. }
  1325. }
  1326.  
  1327. Patch.getOpClass = function (objName)
  1328. {
  1329. const parts = objName.split(".");
  1330. let opObj = null;
  1331.  
  1332. try
  1333. {
  1334. if (parts.length == 2) opObj = window[parts[0]][parts[1]];
  1335. else if (parts.length == 3) opObj = window[parts[0]][parts[1]][parts[2]];
  1336. else if (parts.length == 4) opObj = window[parts[0]][parts[1]][parts[2]][parts[3]];
  1337. else if (parts.length == 5) opObj = window[parts[0]][parts[1]][parts[2]][parts[3]][parts[4]];
  1338. else if (parts.length == 6) opObj = window[parts[0]][parts[1]][parts[2]][parts[3]][parts[4]][parts[5]];
  1339. else if (parts.length == 7) opObj = window[parts[0]][parts[1]][parts[2]][parts[3]][parts[4]][parts[5]][parts[6]];
  1340. else if (parts.length == 8) opObj = window[parts[0]][parts[1]][parts[2]][parts[3]][parts[4]][parts[5]][parts[6]][parts[7]];
  1341. else if (parts.length == 9) opObj = window[parts[0]][parts[1]][parts[2]][parts[3]][parts[4]][parts[5]][parts[6]][parts[7]][parts[8]];
  1342. else if (parts.length == 10) opObj = window[parts[0]][parts[1]][parts[2]][parts[3]][parts[4]][parts[5]][parts[6]][parts[7]][parts[8]][parts[9]];
  1343. return opObj;
  1344. }
  1345. catch (e)
  1346. {
  1347. return null;
  1348. }
  1349. };
  1350.  
  1351.  
  1352.  
  1353. Patch.replaceOpIds = function (json, options)
  1354. {
  1355. const opids = {};
  1356. for (const i in json.ops)
  1357. {
  1358. opids[json.ops[i].id] = json.ops[i];
  1359. }
  1360.  
  1361. for (const j in json.ops)
  1362. {
  1363. for (const k in json.ops[j].portsOut)
  1364. {
  1365. const links = json.ops[j].portsOut[k].links;
  1366. if (links)
  1367. {
  1368. let l = links.length;
  1369.  
  1370. while (l--)
  1371. {
  1372. if (links[l] && (!opids[links[l].objIn] || !opids[links[l].objOut]))
  1373. {
  1374. if (!options.doNotUnlinkLostLinks)
  1375. {
  1376. links.splice(l, 1);
  1377. }
  1378. else
  1379. {
  1380. if (options.fixLostLinks)
  1381. {
  1382. // console.log("lost link...?", links[l]);
  1383. const op = gui.corePatch().getOpById(links[l].objIn);
  1384. if (!op) console.log("op not found!");
  1385. else
  1386. {
  1387. const outerOp = gui.patchView.getSubPatchOuterOp(op.uiAttribs.subPatch);
  1388. if (outerOp)
  1389. {
  1390. op.storage = op.storage || {};
  1391. op.storage.ref = op.storage.ref || CABLES.shortId();
  1392. links[l].refOp = op.storage.ref;
  1393. links[l].subOpRef = outerOp.storage.ref;
  1394. }
  1395. }
  1396. }
  1397. }
  1398. }
  1399. }
  1400. }
  1401. }
  1402. }
  1403.  
  1404.  
  1405.  
  1406. for (const i in json.ops)
  1407. {
  1408. const op = json.ops[i];
  1409. const oldId = op.id;
  1410. let newId = CABLES.shortId();
  1411.  
  1412. if (options.prefixHash) newId = prefixedHash(options.prefixHash + oldId);
  1413.  
  1414. else if (options.prefixId) newId = options.prefixId + oldId;
  1415. else if (options.refAsId) // when saving json
  1416. {
  1417. if (op.storage && op.storage.ref)
  1418. {
  1419. newId = op.storage.ref;
  1420. delete op.storage.ref;
  1421. }
  1422. else
  1423. {
  1424. op.storage = op.storage || {};
  1425. op.storage.ref = newId = CABLES.shortId();
  1426. }
  1427. }
  1428.  
  1429. const newID = op.id = newId;
  1430.  
  1431. if (options.oldIdAsRef) // when loading json
  1432. {
  1433. op.storage = op.storage || {};
  1434. op.storage.ref = oldId;
  1435. }
  1436.  
  1437. for (const j in json.ops)
  1438. {
  1439. if (json.ops[j].portsIn)
  1440. for (const k in json.ops[j].portsIn)
  1441. {
  1442. if (json.ops[j].portsIn[k].links)
  1443. {
  1444. let l = json.ops[j].portsIn[k].links.length;
  1445.  
  1446. while (l--) if (json.ops[j].portsIn[k].links[l] === null) json.ops[j].portsIn[k].links.splice(l, 1);
  1447.  
  1448. for (l in json.ops[j].portsIn[k].links)
  1449. {
  1450. if (json.ops[j].portsIn[k].links[l].objIn === oldId) json.ops[j].portsIn[k].links[l].objIn = newID;
  1451. if (json.ops[j].portsIn[k].links[l].objOut === oldId) json.ops[j].portsIn[k].links[l].objOut = newID;
  1452. }
  1453. }
  1454. }
  1455.  
  1456. if (json.ops[j].portsOut)
  1457. for (const k in json.ops[j].portsOut)
  1458. {
  1459. if (json.ops[j].portsOut[k].links)
  1460. {
  1461. let l = json.ops[j].portsOut[k].links.length;
  1462.  
  1463. while (l--) if (json.ops[j].portsOut[k].links[l] === null) json.ops[j].portsOut[k].links.splice(l, 1);
  1464.  
  1465. for (l in json.ops[j].portsOut[k].links)
  1466. {
  1467. if (json.ops[j].portsOut[k].links[l].objIn === oldId) json.ops[j].portsOut[k].links[l].objIn = newID;
  1468. if (json.ops[j].portsOut[k].links[l].objOut === oldId) json.ops[j].portsOut[k].links[l].objOut = newID;
  1469. }
  1470. }
  1471. }
  1472. }
  1473. }
  1474.  
  1475. // set correct subpatch
  1476. const subpatchIds = [];
  1477. const fixedSubPatches = [];
  1478.  
  1479. for (let i = 0; i < json.ops.length; i++)
  1480. {
  1481. // if (CABLES.Op.isSubPatchOpName(json.ops[i].objName))
  1482. if (json.ops[i].storage && json.ops[i].storage.subPatchVer)
  1483. {
  1484. for (const k in json.ops[i].portsIn)
  1485. {
  1486. if (json.ops[i].portsIn[k].name === "patchId")
  1487. {
  1488. let newId = shortId();
  1489.  
  1490. if (options.prefixHash) newId = prefixedHash(options.prefixHash + json.ops[i].portsIn[k].value);
  1491.  
  1492. const oldSubPatchId = json.ops[i].portsIn[k].value;
  1493. const newSubPatchId = json.ops[i].portsIn[k].value = newId;
  1494.  
  1495. subpatchIds.push(newSubPatchId);
  1496.  
  1497. for (let j = 0; j < json.ops.length; j++)
  1498. {
  1499. // op has no uiAttribs in export, we don't care about subpatches in export though
  1500. if (json.ops[j].uiAttribs)
  1501. {
  1502. if (json.ops[j].uiAttribs.subPatch === oldSubPatchId)
  1503. {
  1504. json.ops[j].uiAttribs.subPatch = newSubPatchId;
  1505. fixedSubPatches.push(json.ops[j].id);
  1506. }
  1507. }
  1508. }
  1509. }
  1510. }
  1511. }
  1512. }
  1513.  
  1514. for (const kk in json.ops)
  1515. {
  1516. let found = false;
  1517. for (let j = 0; j < fixedSubPatches.length; j++)
  1518. {
  1519. if (json.ops[kk].id === fixedSubPatches[j])
  1520. {
  1521. found = true;
  1522. break;
  1523. }
  1524. }
  1525. // op has no uiAttribs in export, we don't care about subpatches in export though
  1526. if (!found && json.ops[kk].uiAttribs && options.parentSubPatchId != null)
  1527. json.ops[kk].uiAttribs.subPatch = options.parentSubPatchId;
  1528. }
  1529.  
  1530. return json;
  1531. };
  1532. /**
  1533. * remove an eventlistener
  1534. * @instance
  1535. * @function addEventListener
  1536. * @param {String} name of event
  1537. * @param {function} callback
  1538. */
  1539.  
  1540. /**
  1541. * remove an eventlistener
  1542. * @instance
  1543. * @function removeEventListener
  1544. * @param {String} name of event
  1545. * @param {function} callback
  1546. */
  1547.  
  1548. /**
  1549. * op added to patch event
  1550. * @event onOpAdd
  1551. *
  1552. * @memberof Patch
  1553. * @type {Object}
  1554. * @property {Op} op new op
  1555. */
  1556.  
  1557. /**
  1558. * op deleted from patch
  1559. * @event onOpDelete
  1560. * @memberof Patch
  1561. * @type {Object}
  1562. * @property {Op} op that will be deleted
  1563. */
  1564.  
  1565. /**
  1566. * link event - two ports will be linked
  1567. * @event onLink
  1568. * @memberof Patch
  1569. * @type {Object}
  1570. * @property {Port} port1
  1571. * @property {Port} port2
  1572. */
  1573.  
  1574. /**
  1575. * unlink event - a link was deleted
  1576. * @event onUnLink
  1577. * @memberof Patch
  1578. * @type {Object}
  1579. */
  1580.  
  1581. /**
  1582. * variables has been changed / a variable has been added to the patch
  1583. * @event variablesChanged
  1584. * @memberof Patch
  1585. * @type {Object}
  1586. * @property {Port} port1
  1587. * @property {Port} port2
  1588. */
  1589.  
  1590. /**
  1591. * configuration object for loading a patch
  1592. * @typedef {Object} PatchConfig
  1593. * @hideconstructor
  1594. * @property {String} [prefixAssetPath=''] prefix for path to assets
  1595. * @property {String} [assetPath=''] path to assets
  1596. * @property {String} [jsPath=''] path to javascript files
  1597. * @property {String} [glCanvasId='glcanvas'] dom element id of canvas element
  1598. * @property {Function} [onError=null] called when an error occurs
  1599. * @property {Function} [onFinishedLoading=null] called when patch finished loading all assets
  1600. * @property {Function} [onFirstFrameRendered=null] called when patch rendered it's first frame
  1601. * @property {Boolean} [glCanvasResizeToWindow=false] resize canvas automatically to window size
  1602. * @property {Boolean} [doRequestAnimation=true] do requestAnimationFrame set to false if you want to trigger exec() from outside (only do if you know what you are doing)
  1603. * @property {Boolean} [clearCanvasColor=true] clear canvas in transparent color every frame
  1604. * @property {Boolean} [clearCanvasDepth=true] clear depth every frame
  1605. * @property {Boolean} [glValidateShader=true] enable/disable validation of shaders *
  1606. * @property {Boolean} [silent=false]
  1607. * @property {Number} [fpsLimit=0] 0 for maximum possible frames per second
  1608. * @property {String} [glslPrecision='mediump'] default precision for glsl shader
  1609. *
  1610. */
  1611.  
  1612. export default Patch;