@shapediver/viewer.session-engine.session-engine 2.11.0 → 2.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,7 +4,7 @@ import {
4
4
  latestVersion,
5
5
  validate,
6
6
  versions
7
- } from '@shapediver/viewer.settings';
7
+ } from '@shapediver/viewer.settings';
8
8
  import {
9
9
  create,
10
10
  isGBResponseError,
@@ -21,7 +21,7 @@ import {
21
21
  ShapeDiverResponseOutput,
22
22
  ShapeDiverSdk,
23
23
  ShapeDiverSdkConfigType
24
- } from '@shapediver/sdk.geometry-api-sdk-v2';
24
+ } from '@shapediver/sdk.geometry-api-sdk-v2';
25
25
  import {
26
26
  EventEngine,
27
27
  EVENTTYPE,
@@ -36,7 +36,7 @@ import {
36
36
  StateEngine,
37
37
  SystemInfo,
38
38
  UuidGenerator
39
- } from '@shapediver/viewer.shared.services';
39
+ } from '@shapediver/viewer.shared.services';
40
40
  import { Export } from './dto/Export';
41
41
  import { FileParameter } from './dto/FileParameter';
42
42
  import { IExport } from '../interfaces/dto/IExport';
@@ -45,13 +45,13 @@ import { IOutput } from '../interfaces/dto/IOutput';
45
45
  import { IParameter } from '../interfaces/dto/IParameter';
46
46
  import { ISessionEngine, ISettingsSections, PARAMETER_TYPE } from '../interfaces/ISessionEngine';
47
47
  import { ISessionTreeNode } from '../interfaces/ISessionTreeNode';
48
- import { ITaskEvent, TASK_TYPE } from '@shapediver/viewer.shared.types';
48
+ import { IOutputEvent, ITaskEvent, TASK_TYPE } from '@shapediver/viewer.shared.types';
49
49
  import {
50
50
  ITree,
51
51
  ITreeNode,
52
52
  Tree,
53
53
  TreeNode
54
- } from '@shapediver/viewer.shared.node-tree';
54
+ } from '@shapediver/viewer.shared.node-tree';
55
55
  import { Output } from './dto/Output';
56
56
  import { OutputDelayException } from './OutputDelayException';
57
57
  import { OutputLoader, OutputLoaderTaskEventInfo } from './OutputLoader';
@@ -62,7 +62,7 @@ import { vec3 } from 'gl-matrix';
62
62
  /* eslint-disable @typescript-eslint/no-empty-function */
63
63
 
64
64
  export class SessionEngine implements ISessionEngine {
65
- // #region Properties (43)
65
+ // #region Properties (44)
66
66
 
67
67
  private readonly _eventEngine = EventEngine.instance;
68
68
  private readonly _exports: { [key: string]: IExport; } = {};
@@ -84,7 +84,8 @@ export class SessionEngine implements ISessionEngine {
84
84
  private readonly _ticket?: string;
85
85
  private readonly _uuidGenerator = UuidGenerator.instance;
86
86
 
87
- #customizationProcess!: string;
87
+ #customizationBusyModes: string[] = [];
88
+ #customizationProcess?: string;
88
89
  #parameterHistory: {
89
90
  [key: string]: {
90
91
  value: unknown,
@@ -126,7 +127,7 @@ export class SessionEngine implements ISessionEngine {
126
127
  private _viewerSettingsVersion: string = latestVersion;
127
128
  private _viewerSettingsVersionBackend: string = latestVersion;
128
129
 
129
- // #endregion Properties (43)
130
+ // #endregion Properties (44)
130
131
 
131
132
  // #region Constructors (1)
132
133
 
@@ -267,7 +268,7 @@ export class SessionEngine implements ISessionEngine {
267
268
 
268
269
  // #endregion Public Accessors (25)
269
270
 
270
- // #region Public Methods (25)
271
+ // #region Public Methods (27)
271
272
 
272
273
  public applySettings(response: ShapeDiverResponseDto, sections?: ISettingsSections) {
273
274
  sections = sections || {};
@@ -317,7 +318,7 @@ export class SessionEngine implements ISessionEngine {
317
318
  if (sections.session.parameter.hidden) this.parameters[p].hidden = settings.session[p].hidden || false;
318
319
  }
319
320
 
320
- if (response.parameters && response.parameters[p]) {
321
+ if (response.parameters && response.parameters[p] && !((this.parameters[p] instanceof FileParameter) || this.parameters[p].type.startsWith('s'))) {
321
322
  if (sections.session.parameter.value) this.parameters[p].value = response.parameters[p].defval !== undefined ? response.parameters[p].defval : this.parameters[p].value;
322
323
  }
323
324
  }
@@ -409,6 +410,21 @@ export class SessionEngine implements ISessionEngine {
409
410
  return this.#parameterHistoryForward.length > 0;
410
411
  }
411
412
 
413
+ public cancelCustomization() {
414
+ if (this.#customizationProcess)
415
+ this.removeBusyMode(this.#customizationProcess);
416
+
417
+ for (const busyId of this.#customizationBusyModes) {
418
+ for (const r in this._stateEngine.renderingEngines) {
419
+ if (this._stateEngine.renderingEngines[r].busy.includes(busyId))
420
+ this._stateEngine.renderingEngines[r].busy.splice(this._stateEngine.renderingEngines[r].busy.indexOf(busyId), 1);
421
+ }
422
+ }
423
+
424
+ this.#customizationBusyModes = [];
425
+ this.#customizationProcess = undefined;
426
+ }
427
+
412
428
  public async close(retry = false): Promise<void> {
413
429
  this.checkAvailability('close');
414
430
 
@@ -452,9 +468,7 @@ export class SessionEngine implements ISessionEngine {
452
468
 
453
469
  this._logger.debugLow(`Session(${this.id}).customize: Customizing session.`);
454
470
 
455
- for (const r in this._stateEngine.renderingEngines)
456
- if (!this.excludeViewports.includes(r))
457
- this._stateEngine.renderingEngines[r].busy.push(customizationId);
471
+ this.addBusyMode(customizationId);
458
472
 
459
473
  const eventFileUpload: ITaskEvent = { type: TASK_TYPE.SESSION_CUSTOMIZATION, id: eventId, progress: 0.1, data: { sessionId: this.id }, status: 'Uploading file parameters' };
460
474
  this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_PROCESS, eventFileUpload);
@@ -466,51 +480,14 @@ export class SessionEngine implements ISessionEngine {
466
480
  fileParameterIds[parameterId] = await (<IFileParameter>this.parameters[parameterId]).upload();
467
481
 
468
482
  // OPTION TO SKIP - PART 1a
469
- if (this.#customizationProcess !== customizationId) {
470
- for (const r in this._stateEngine.renderingEngines)
471
- if (this._stateEngine.renderingEngines[r].busy.includes(customizationId))
472
- this._stateEngine.renderingEngines[r].busy.splice(this._stateEngine.renderingEngines[r].busy.indexOf(customizationId), 1);
473
-
474
- this._logger.debug(`Session(${this.id}).customize: Session customization was exceeded by other customization request.`);
475
-
476
- const eventCancel1a: ITaskEvent = { type: TASK_TYPE.SESSION_CUSTOMIZATION, id: eventId, progress: 1, data: { sessionId: this.id }, status: 'Session customization was exceeded by other customization request' };
477
- this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_CANCEL, eventCancel1a);
478
- return new SessionTreeNode();
479
- } else if (this._closed === true) {
480
- for (const r in this._stateEngine.renderingEngines)
481
- if (this._stateEngine.renderingEngines[r].busy.includes(customizationId))
482
- this._stateEngine.renderingEngines[r].busy.splice(this._stateEngine.renderingEngines[r].busy.indexOf(customizationId), 1);
483
-
484
- this._logger.debug(`Session(${this.id}).customize: Session was closed during customization request.`);
485
-
486
- const eventCancel1a: ITaskEvent = { type: TASK_TYPE.SESSION_CUSTOMIZATION, id: eventId, progress: 1, data: { sessionId: this.id }, status: 'Session was closed during customization request' };
487
- this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_CANCEL, eventCancel1a);
488
- return new SessionTreeNode();
489
- }
483
+ const cancelResult = this.cancelProcess(customizationId, eventId, TASK_TYPE.SESSION_CUSTOMIZATION, 1, { sessionId: this.id });
484
+ if (cancelResult) return cancelResult;
490
485
  }
491
486
  }
492
487
 
493
488
  // OPTION TO SKIP - PART 1b
494
- if (this.#customizationProcess !== customizationId) {
495
- for (const r in this._stateEngine.renderingEngines)
496
- if (this._stateEngine.renderingEngines[r].busy.includes(customizationId))
497
- this._stateEngine.renderingEngines[r].busy.splice(this._stateEngine.renderingEngines[r].busy.indexOf(customizationId), 1);
498
-
499
- const eventCancel1b: ITaskEvent = { type: TASK_TYPE.SESSION_CUSTOMIZATION, id: eventId, progress: 1, data: { sessionId: this.id }, status: 'Session customization was exceeded by other customization request' };
500
- this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_CANCEL, eventCancel1b);
501
- this._logger.debug(`Session(${this.id}).customize: Session customization was exceeded by other customization request.`);
502
- return new SessionTreeNode();
503
- } else if (this._closed === true) {
504
- for (const r in this._stateEngine.renderingEngines)
505
- if (this._stateEngine.renderingEngines[r].busy.includes(customizationId))
506
- this._stateEngine.renderingEngines[r].busy.splice(this._stateEngine.renderingEngines[r].busy.indexOf(customizationId), 1);
507
-
508
- this._logger.debug(`Session(${this.id}).customize: Session was closed during customization request.`);
509
-
510
- const eventCancel1b: ITaskEvent = { type: TASK_TYPE.SESSION_CUSTOMIZATION, id: eventId, progress: 1, data: { sessionId: this.id }, status: 'Session was closed during customization request' };
511
- this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_CANCEL, eventCancel1b);
512
- return new SessionTreeNode();
513
- }
489
+ const cancelResult = this.cancelProcess(customizationId, eventId, TASK_TYPE.SESSION_CUSTOMIZATION, 1, { sessionId: this.id });
490
+ if (cancelResult) return cancelResult;
514
491
 
515
492
  // assign the uploaded parameters
516
493
  for (const parameterId in fileParameterIds)
@@ -539,6 +516,8 @@ export class SessionEngine implements ISessionEngine {
539
516
  const eventRequest: ITaskEvent = { type: TASK_TYPE.SESSION_CUSTOMIZATION, id: eventId, progress: 0.1, data: { sessionId: this.id }, status: 'Sending customization request' };
540
517
  this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_PROCESS, eventRequest);
541
518
 
519
+ const oldOutputVersions = this._outputLoader.getCurrentOutputVersions();
520
+
542
521
  const newNode = await this.customizeInternal(() => this.#customizationProcess !== customizationId, {
543
522
  eventId,
544
523
  type: TASK_TYPE.SESSION_CUSTOMIZATION,
@@ -549,29 +528,32 @@ export class SessionEngine implements ISessionEngine {
549
528
  data: { sessionId: this.id }
550
529
  });
551
530
 
531
+ // OPTION TO SKIP - PART 2
532
+ const cancelResult2 = this.cancelProcess(customizationId, eventId, TASK_TYPE.SESSION_CUSTOMIZATION, 1, { sessionId: this.id });
533
+ if (cancelResult2) return cancelResult2;
534
+
535
+ const newOutputVersions = this._outputLoader.getCurrentOutputVersions();
536
+
552
537
  const eventSceneUpdate: ITaskEvent = { type: TASK_TYPE.SESSION_CUSTOMIZATION, id: eventId, progress: 0.9, data: { sessionId: this.id }, status: 'Updating scene' };
553
538
  this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_PROCESS, eventSceneUpdate);
554
539
 
555
- // OPTION TO SKIP - PART 2
556
- if (this.#customizationProcess !== customizationId) {
557
- for (const r in this._stateEngine.renderingEngines)
558
- if (this._stateEngine.renderingEngines[r].busy.includes(customizationId))
559
- this._stateEngine.renderingEngines[r].busy.splice(this._stateEngine.renderingEngines[r].busy.indexOf(customizationId), 1);
560
-
561
- const eventCancel2: ITaskEvent = { type: TASK_TYPE.SESSION_CUSTOMIZATION, id: eventId, progress: 1, data: { sessionId: this.id }, status: 'Session customization was exceeded by other customization request' };
562
- this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_CANCEL, eventCancel2);
563
- this._logger.debug(`Session(${this.id}).customize: Session customization was exceeded by other customization request.`);
564
- return newNode;
565
- } else if ((this._closed as boolean) === true) { // I get a TS warning here that the type of _closed is "false", I think TS doesn't get that there is a promise inbetween
566
- for (const r in this._stateEngine.renderingEngines)
567
- if (this._stateEngine.renderingEngines[r].busy.includes(customizationId))
568
- this._stateEngine.renderingEngines[r].busy.splice(this._stateEngine.renderingEngines[r].busy.indexOf(customizationId), 1);
540
+ // call the update callbacks
541
+ if (waitForViewportUpdate === false) {
542
+ for (const outputId in this.outputs) {
543
+ if (oldOutputVersions[outputId] !== newOutputVersions[outputId]) {
544
+ this._eventEngine.emitEvent(EVENTTYPE.OUTPUT.OUTPUT_UPDATED, <IOutputEvent>{
545
+ outputId: outputId,
546
+ outputVersion: newOutputVersions[outputId],
547
+ newNode: newNode.children.find(c => c.name === outputId)!,
548
+ oldNode: oldNode.children.find(c => c.name === outputId)!
549
+ });
550
+ }
551
+ }
569
552
 
570
- this._logger.debug(`Session(${this.id}).customize: Session was closed during customization request.`);
553
+ await this.waitForUpdateCallbacks(newOutputVersions, oldOutputVersions, newNode, oldNode);
571
554
 
572
- const eventCancel2: ITaskEvent = { type: TASK_TYPE.SESSION_CUSTOMIZATION, id: eventId, progress: 1, data: { sessionId: this.id }, status: 'Session was closed during customization request' };
573
- this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_CANCEL, eventCancel2);
574
- return new SessionTreeNode();
555
+ const cancelResult = this.cancelProcess(customizationId, eventId, TASK_TYPE.SESSION_CUSTOMIZATION, 1, { sessionId: this.id });
556
+ if (cancelResult) return cancelResult;
575
557
  }
576
558
 
577
559
  // if this is not a call by the goBack or goForward functions, add the parameter values to the history and delete the forward history
@@ -605,9 +587,7 @@ export class SessionEngine implements ISessionEngine {
605
587
 
606
588
  this.node.excludeViewports = JSON.parse(JSON.stringify(this._excludeViewports));
607
589
 
608
- for (const r in this._stateEngine.renderingEngines)
609
- if (this._stateEngine.renderingEngines[r].busy.includes(customizationId))
610
- this._stateEngine.renderingEngines[r].busy.splice(this._stateEngine.renderingEngines[r].busy.indexOf(customizationId), 1);
590
+ this.removeBusyMode(customizationId);
611
591
 
612
592
  this._logger.debug(`Session(${this.id}).customize: Session customized.`);
613
593
 
@@ -617,35 +597,49 @@ export class SessionEngine implements ISessionEngine {
617
597
  this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_END, eventEnd);
618
598
 
619
599
  // update the viewports
620
- if (waitForViewportUpdate)
600
+ if (waitForViewportUpdate) {
621
601
  for (const r in this._stateEngine.renderingEngines)
622
602
  if (!this.excludeViewports.includes(this._stateEngine.renderingEngines[r].id))
623
603
  this._stateEngine.renderingEngines[r].update(`SessionEngine(${this.id}).customize`);
624
604
 
625
- // call the update callback function on the session
626
- if (this._updateCallback) this._updateCallback(newNode, oldNode);
605
+ for (const outputId in this.outputs) {
606
+ if (oldOutputVersions[outputId] !== newOutputVersions[outputId]) {
607
+ this._eventEngine.emitEvent(EVENTTYPE.OUTPUT.OUTPUT_UPDATED, <IOutputEvent>{
608
+ outputId: outputId,
609
+ outputVersion: newOutputVersions[outputId],
610
+ newNode: newNode.children.find(c => c.name === outputId)!,
611
+ oldNode: oldNode.children.find(c => c.name === outputId)!
612
+ });
613
+ }
614
+ }
627
615
 
628
- // call the update callback functions on the outputs
629
- for (const outputId in this.outputs)
630
- this.outputs[outputId].triggerUpdateCallback(
631
- newNode.children.find(c => c.name === outputId)!,
632
- oldNode.children.find(c => c.name === outputId)!
633
- );
616
+ // call the update callbacks
617
+ await this.waitForUpdateCallbacks(newOutputVersions, oldOutputVersions, newNode, oldNode);
618
+
619
+ const cancelResult = this.cancelProcess(customizationId, eventId, TASK_TYPE.SESSION_CUSTOMIZATION, 1, { sessionId: this.id });
620
+ if (cancelResult) return cancelResult;
621
+ }
622
+
623
+ if (!waitForViewportUpdate) {
624
+ setTimeout(() => {
625
+ for (const r in this._stateEngine.renderingEngines)
626
+ if (!this.excludeViewports.includes(this._stateEngine.renderingEngines[r].id))
627
+ this._stateEngine.renderingEngines[r].update(`SessionEngine(${this.id}).customize`);
628
+ }, 0);
629
+ }
634
630
 
635
631
  return this.node;
636
632
  } catch (e) {
637
633
  const eventCancel: ITaskEvent = { type: TASK_TYPE.SESSION_CUSTOMIZATION, id: eventId, progress: 1, data: { sessionId: this.id }, status: 'Session customization failed' };
638
634
  this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_CANCEL, eventCancel);
639
635
 
640
- for (const r in this._stateEngine.renderingEngines)
641
- if (this._stateEngine.renderingEngines[r].busy.includes(customizationId))
642
- this._stateEngine.renderingEngines[r].busy.splice(this._stateEngine.renderingEngines[r].busy.indexOf(customizationId), 1);
636
+ this.removeBusyMode(customizationId);
643
637
 
644
638
  throw this._httpClient.convertError(e);
645
639
  }
646
640
  }
647
641
 
648
- public async customizeParallel(parameterValues: { [key: string]: string }): Promise<ITreeNode> {
642
+ public async customizeParallel(parameterValues: { [key: string]: string }, loadOutputs = true): Promise<ISessionTreeNode | ShapeDiverResponseDto> {
649
643
  const eventId = this._uuidGenerator.create();
650
644
 
651
645
  const eventStart: ITaskEvent = { type: TASK_TYPE.SESSION_CUSTOMIZATION, id: eventId, progress: 0, data: { sessionId: this.id }, status: 'Customizing session' };
@@ -659,7 +653,7 @@ export class SessionEngine implements ISessionEngine {
659
653
  for (const parameterId in this.parameters)
660
654
  parameterSet[parameterId] = parameterValues[parameterId] !== undefined ? (' ' + parameterValues[parameterId]).slice(1) : this.parameters[parameterId].stringify();
661
655
 
662
- const newNode = await this.customizeSession(parameterSet, () => false, {
656
+ const result = await this.customizeSession(parameterSet, () => false, {
663
657
  eventId,
664
658
  type: TASK_TYPE.SESSION_CUSTOMIZATION,
665
659
  progressRange: {
@@ -667,12 +661,14 @@ export class SessionEngine implements ISessionEngine {
667
661
  max: 1
668
662
  },
669
663
  data: { sessionId: this.id }
670
- }, true);
671
- newNode.excludeViewports = JSON.parse(JSON.stringify(this._excludeViewports));
664
+ }, true, loadOutputs);
665
+
666
+ if (result instanceof SessionTreeNode)
667
+ result.excludeViewports = JSON.parse(JSON.stringify(this._excludeViewports));
672
668
 
673
669
  const eventEnd: ITaskEvent = { type: TASK_TYPE.SESSION_CUSTOMIZATION, id: eventId, progress: 1, data: { sessionId: this.id }, status: 'Session customized' };
674
670
  this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_END, eventEnd);
675
- return newNode;
671
+ return result;
676
672
  }
677
673
 
678
674
  public async goBack(): Promise<ITreeNode> {
@@ -772,6 +768,63 @@ export class SessionEngine implements ISessionEngine {
772
768
  }
773
769
  }
774
770
 
771
+ public async loadCachedOutputsParallel(outputMapping: { [key: string]: string }, taskEventInfo?: OutputLoaderTaskEventInfo, retry = false): Promise<{ [key: string]: ITreeNode | undefined }> {
772
+ this.checkAvailability();
773
+ // if there is already task event info, use it
774
+ // this happens after a retry
775
+ const eventId = taskEventInfo ? taskEventInfo.eventId : this._uuidGenerator.create();
776
+ const eventType = taskEventInfo ? taskEventInfo.type : TASK_TYPE.SESSION_OUTPUTS_LOADING;
777
+ const eventData = taskEventInfo ? taskEventInfo.data : { sessionId: this.id };
778
+
779
+ taskEventInfo = taskEventInfo ? taskEventInfo : {
780
+ eventId,
781
+ type: eventType,
782
+ progressRange: {
783
+ min: 0,
784
+ max: 1
785
+ },
786
+ data: eventData
787
+ };
788
+
789
+ try {
790
+ // send start event if this function was called initially
791
+ if (!taskEventInfo) {
792
+ const eventStart: ITaskEvent = { type: eventType, id: eventId, progress: 0, data: eventData, status: 'Loading cached outputs' };
793
+ this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_START, eventStart);
794
+ }
795
+
796
+ // get the cached outputs
797
+ const responseDto = await this._sdk.output.getCache(this._sessionId!, outputMapping);
798
+
799
+ // create atomic output api objects for them
800
+ const outputs: {
801
+ [key: string]: IOutput;
802
+ } = {};
803
+ for (const outputId in responseDto.outputs) {
804
+ responseDto.outputs[outputId].id = outputId;
805
+ outputs[outputId] = new Output(<ShapeDiverResponseOutput>responseDto.outputs[outputId], this);
806
+ }
807
+
808
+ // process the output data
809
+ const node = await this._outputLoader.loadOutputs(this._responseDto!.model?.name || 'model', outputs, {}, taskEventInfo, false);
810
+
811
+ // send the end event once done
812
+ const eventEnd: ITaskEvent = { type: eventType, id: eventId, progress: 1, data: eventData, status: 'Loaded cached outputs' };
813
+ this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_END, eventEnd);
814
+
815
+ // create a mapping with a dictionary for the id of the outputs
816
+ const outputNodeMapping: { [key: string]: ITreeNode | undefined } = {};
817
+ for (const outputId in outputMapping)
818
+ outputNodeMapping[outputId] = node.children.find(n => n.name === outputId);
819
+
820
+ return outputNodeMapping;
821
+ }
822
+ catch (e) {
823
+ await this.handleError(e, retry);
824
+ return await this.loadCachedOutputsParallel(outputMapping, taskEventInfo, true);
825
+ }
826
+ }
827
+
775
828
  /**
776
829
  * Load the outputs and return the scene graph node of the result.
777
830
  * In case the outputs have a delay property, another customization request with the parameter set is sent.
@@ -788,15 +841,8 @@ export class SessionEngine implements ISessionEngine {
788
841
  try {
789
842
  const node = await this._outputLoader.loadOutputs(this._responseDto!.model?.name || 'model', o, of, taskEventInfo);
790
843
  node.data.push(new SessionData(this._responseDto!));
791
-
792
844
  if (cancelRequest()) return node;
793
-
794
- if (this._automaticSceneUpdate) this.removeFromSceneTree(this._node);
795
- this._node = node;
796
- if (this._automaticSceneUpdate && this._closed === false) this.addToSceneTree(this._node);
797
-
798
- this.node.excludeViewports = JSON.parse(JSON.stringify(this._excludeViewports));
799
-
845
+ node.excludeViewports = JSON.parse(JSON.stringify(this._excludeViewports));
800
846
  return node;
801
847
  }
802
848
  catch (e) {
@@ -1132,13 +1178,13 @@ export class SessionEngine implements ISessionEngine {
1132
1178
 
1133
1179
  this._logger.debugLow(`Session(${this.id}).updateOutputs: Updating Outputs.`);
1134
1180
 
1135
- for (const r in this._stateEngine.renderingEngines)
1136
- if (!this.excludeViewports.includes(r))
1137
- this._stateEngine.renderingEngines[r].busy.push(customizationId);
1181
+ this.addBusyMode(customizationId);
1138
1182
 
1139
1183
  const eventRequest: ITaskEvent = { type: eventType, id: eventId, progress: taskEventInfo ? (taskEventInfo.progressRange.max - taskEventInfo.progressRange.min) * 0.1 + taskEventInfo.progressRange.min : 0.1, data: eventData, status: 'Loading outputs' };
1140
1184
  this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_PROCESS, eventRequest);
1141
1185
 
1186
+ const oldOutputVersions = this._outputLoader.getCurrentOutputVersions();
1187
+
1142
1188
  const newNode = await this.loadOutputs(() => this.#customizationProcess !== customizationId, {
1143
1189
  eventId,
1144
1190
  type: eventType,
@@ -1149,29 +1195,33 @@ export class SessionEngine implements ISessionEngine {
1149
1195
  data: eventData
1150
1196
  });
1151
1197
 
1198
+ const newOutputVersions = this._outputLoader.getCurrentOutputVersions();
1199
+
1152
1200
  const eventSceneUpdate: ITaskEvent = { type: eventType, id: eventId, progress: taskEventInfo ? (taskEventInfo.progressRange.max - taskEventInfo.progressRange.min) * 0.9 + taskEventInfo.progressRange.min : 0.9, data: eventData, status: 'Updating scene' };
1153
1201
  this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_PROCESS, eventSceneUpdate);
1154
1202
 
1155
1203
  // OPTION TO SKIP - PART 1
1156
- if (this.#customizationProcess !== customizationId) {
1157
- for (const r in this._stateEngine.renderingEngines)
1158
- if (this._stateEngine.renderingEngines[r].busy.includes(customizationId))
1159
- this._stateEngine.renderingEngines[r].busy.splice(this._stateEngine.renderingEngines[r].busy.indexOf(customizationId), 1);
1160
-
1161
- const eventCancel1: ITaskEvent = { type: eventType, id: eventId, progress: taskEventInfo ? (taskEventInfo.progressRange.max - taskEventInfo.progressRange.min) * 1 + taskEventInfo.progressRange.min : 1, data: eventData, status: 'Output updating was exceeded by other customization request' };
1162
- this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_CANCEL, eventCancel1);
1163
- this._logger.debug(`Session(${this.id}).updateOutputs: Output updating was exceeded by other request.`);
1164
- return newNode;
1165
- } else if (this._closed === true) {
1166
- for (const r in this._stateEngine.renderingEngines)
1167
- if (this._stateEngine.renderingEngines[r].busy.includes(customizationId))
1168
- this._stateEngine.renderingEngines[r].busy.splice(this._stateEngine.renderingEngines[r].busy.indexOf(customizationId), 1);
1204
+ const cancelResult = this.cancelProcess(customizationId, eventId, eventType, taskEventInfo ? (taskEventInfo.progressRange.max - taskEventInfo.progressRange.min) * 1 + taskEventInfo.progressRange.min : 1, eventData, newNode);
1205
+ if (cancelResult) return cancelResult;
1206
+
1207
+ // call the update callbacks
1208
+ if (waitForViewportUpdate === false) {
1209
+ for (const outputId in this.outputs) {
1210
+ if (oldOutputVersions[outputId] !== newOutputVersions[outputId]) {
1211
+ this._eventEngine.emitEvent(EVENTTYPE.OUTPUT.OUTPUT_UPDATED, {
1212
+ outputId: outputId,
1213
+ outputVersion: newOutputVersions[outputId],
1214
+ newNode: newNode.children.find(c => c.name === outputId)!,
1215
+ oldNode: oldNode.children.find(c => c.name === outputId)!
1216
+ });
1217
+ }
1218
+ }
1169
1219
 
1170
- this._logger.debug(`Session(${this.id}).customize: Session was closed during customization request.`);
1220
+ await this.waitForUpdateCallbacks(newOutputVersions, oldOutputVersions, newNode, oldNode);
1171
1221
 
1172
- const eventCancel1a: ITaskEvent = { type: TASK_TYPE.SESSION_CUSTOMIZATION, id: eventId, progress: 1, data: { sessionId: this.id }, status: 'Session was closed during customization request' };
1173
- this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_CANCEL, eventCancel1a);
1174
- return new SessionTreeNode();
1222
+ // OPTION TO SKIP - PART 2
1223
+ const cancelResult = this.cancelProcess(customizationId, eventId, eventType, taskEventInfo ? (taskEventInfo.progressRange.max - taskEventInfo.progressRange.min) * 1 + taskEventInfo.progressRange.min : 1, eventData, newNode);
1224
+ if (cancelResult) return cancelResult;
1175
1225
  }
1176
1226
 
1177
1227
  if (this.automaticSceneUpdate) this.removeFromSceneTree(this.node);
@@ -1195,9 +1245,7 @@ export class SessionEngine implements ISessionEngine {
1195
1245
  this._warningCreator();
1196
1246
  this.node.excludeViewports = JSON.parse(JSON.stringify(this._excludeViewports));
1197
1247
 
1198
- for (const r in this._stateEngine.renderingEngines)
1199
- if (this._stateEngine.renderingEngines[r].busy.includes(customizationId))
1200
- this._stateEngine.renderingEngines[r].busy.splice(this._stateEngine.renderingEngines[r].busy.indexOf(customizationId), 1);
1248
+ this.removeBusyMode(customizationId);
1201
1249
 
1202
1250
  this._logger.debug(`Session(${this.id}).updateOutputs: Updated outputs.`);
1203
1251
 
@@ -1207,20 +1255,28 @@ export class SessionEngine implements ISessionEngine {
1207
1255
  }
1208
1256
 
1209
1257
  // update the viewports
1210
- if (waitForViewportUpdate)
1258
+ if (waitForViewportUpdate) {
1211
1259
  for (const r in this._stateEngine.renderingEngines)
1212
1260
  if (!this.excludeViewports.includes(this._stateEngine.renderingEngines[r].id))
1213
1261
  this._stateEngine.renderingEngines[r].update(`SessionEngine(${this.id}).updateOutputs`);
1214
1262
 
1215
- // call the update callback function on the session
1216
- if (this._updateCallback) this._updateCallback(newNode, oldNode);
1263
+ for (const outputId in this.outputs) {
1264
+ if (oldOutputVersions[outputId] !== newOutputVersions[outputId]) {
1265
+ this._eventEngine.emitEvent(EVENTTYPE.OUTPUT.OUTPUT_UPDATED, {
1266
+ outputId: outputId,
1267
+ outputVersion: newOutputVersions[outputId],
1268
+ newNode: newNode.children.find(c => c.name === outputId)!,
1269
+ oldNode: oldNode.children.find(c => c.name === outputId)!
1270
+ });
1271
+ }
1272
+ }
1217
1273
 
1218
- // call the update callback functions on the outputs
1219
- for (const outputId in this.outputs)
1220
- this.outputs[outputId].triggerUpdateCallback(
1221
- newNode.children.find(c => c.name === outputId)!,
1222
- oldNode.children.find(c => c.name === outputId)!
1223
- );
1274
+ await this.waitForUpdateCallbacks(newOutputVersions, oldOutputVersions, newNode, oldNode);
1275
+
1276
+ // OPTION TO SKIP - PART 3
1277
+ const cancelResult = this.cancelProcess(customizationId, eventId, eventType, taskEventInfo ? (taskEventInfo.progressRange.max - taskEventInfo.progressRange.min) * 1 + taskEventInfo.progressRange.min : 1, eventData, newNode);
1278
+ if (cancelResult) return cancelResult;
1279
+ }
1224
1280
 
1225
1281
  return this.node;
1226
1282
  }
@@ -1258,9 +1314,9 @@ export class SessionEngine implements ISessionEngine {
1258
1314
  }
1259
1315
  }
1260
1316
 
1261
- // #endregion Public Methods (25)
1317
+ // #endregion Public Methods (27)
1262
1318
 
1263
- // #region Private Methods (11)
1319
+ // #region Private Methods (15)
1264
1320
 
1265
1321
  private _saveSessionSettings() {
1266
1322
  const parameters = this.parameters;
@@ -1332,11 +1388,45 @@ export class SessionEngine implements ISessionEngine {
1332
1388
  }
1333
1389
  }
1334
1390
 
1391
+ private addBusyMode(busyId: string) {
1392
+ for (const r in this._stateEngine.renderingEngines) {
1393
+ if (!this.excludeViewports.includes(r)) {
1394
+ this._stateEngine.renderingEngines[r].busy.push(busyId);
1395
+ this.#customizationBusyModes.push(busyId);
1396
+ }
1397
+ }
1398
+ }
1399
+
1335
1400
  private addToSceneTree(node: ITreeNode) {
1336
1401
  this._sceneTree.addNode(node);
1337
1402
  this._sceneTree.root.updateVersion();
1338
1403
  }
1339
1404
 
1405
+ private cancelProcess(customizationId: string, eventId: string, eventType: TASK_TYPE, eventProgress: number, eventData: unknown, newNode: ITreeNode = new SessionTreeNode()): ITreeNode | undefined {
1406
+ if (this.#customizationProcess !== customizationId) {
1407
+ this.removeBusyMode(customizationId);
1408
+
1409
+ const eventCancel: ITaskEvent = {
1410
+ type: eventType,
1411
+ id: eventId,
1412
+ progress: eventProgress,
1413
+ data: eventData,
1414
+ status: 'The request was exceeded by another customization request'
1415
+ };
1416
+ this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_CANCEL, eventCancel);
1417
+ this._logger.debug(`Session(${this.id}).cancelProcess: The request was was exceeded by another request.`);
1418
+ return newNode;
1419
+ } else if ((this._closed as boolean) === true) {
1420
+ this.removeBusyMode(customizationId);
1421
+
1422
+ this._logger.debug(`Session(${this.id}).cancelProcess: The session was closed during the request.`);
1423
+
1424
+ const eventCancel: ITaskEvent = { type: TASK_TYPE.SESSION_CUSTOMIZATION, id: eventId, progress: 1, data: { sessionId: this.id }, status: 'The session was closed during the request.' };
1425
+ this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_CANCEL, eventCancel);
1426
+ return new SessionTreeNode();
1427
+ }
1428
+ }
1429
+
1340
1430
  private checkAvailability(action?: string, checkForModelId = false) {
1341
1431
  if (!this._responseDto)
1342
1432
  throw new ShapeDiverViewerSessionError('Session.checkAvailability: responseDto not available.');
@@ -1385,22 +1475,33 @@ export class SessionEngine implements ISessionEngine {
1385
1475
  }
1386
1476
 
1387
1477
  private async customizeInternal(cancelRequest: () => boolean, taskEventInfo: OutputLoaderTaskEventInfo): Promise<ISessionTreeNode> {
1388
- return this.customizeSession(this._parameterValues, cancelRequest, taskEventInfo);
1478
+ return this.customizeSession(this._parameterValues, cancelRequest, taskEventInfo) as Promise<ISessionTreeNode>;
1389
1479
  }
1390
1480
 
1391
- private async customizeSession(parameters: { [key: string]: string }, cancelRequest: () => boolean, taskEventInfo: OutputLoaderTaskEventInfo, parallel = false, retry = false): Promise<ISessionTreeNode> {
1481
+ private async customizeSession(parameters: { [key: string]: string }, cancelRequest: () => boolean, taskEventInfo: OutputLoaderTaskEventInfo, parallel = false, loadOutputs = true, retry = false): Promise<ISessionTreeNode | ShapeDiverResponseDto> {
1392
1482
  this.checkAvailability('customize');
1393
1483
  try {
1394
1484
  this._performanceEvaluator.startSection('sessionResponse');
1395
1485
  const responseDto = await this._sdk.utils.submitAndWaitForCustomization(this._sdk, this._sessionId!, parameters);
1396
1486
  this._performanceEvaluator.endSection('sessionResponse');
1397
- if (cancelRequest()) return new SessionTreeNode();
1398
- if (parallel === false) this.updateResponseDto(responseDto);
1399
- return parallel === false ? this.loadOutputs(cancelRequest, taskEventInfo) : this.loadOutputsParallel(responseDto, cancelRequest, taskEventInfo);
1487
+ if (loadOutputs === true) {
1488
+ if (cancelRequest()) return new SessionTreeNode();
1489
+ if (parallel === true) {
1490
+ // special case, we load the outputs put don't add them to the scene
1491
+ return this.loadOutputsParallel(responseDto, cancelRequest, taskEventInfo);
1492
+ } else {
1493
+ // default case, we load the outputs and return the nodes
1494
+ this.updateResponseDto(responseDto);
1495
+ return this.loadOutputs(cancelRequest, taskEventInfo);
1496
+ }
1497
+ } else {
1498
+ // special case, we don't load the outputs and only return the responseDto
1499
+ return responseDto;
1500
+ }
1400
1501
  } catch (e) {
1401
1502
  await this.handleError(e, retry);
1402
1503
  if (cancelRequest()) return new SessionTreeNode();
1403
- return await this.customizeSession(parameters, cancelRequest, taskEventInfo, parallel, true);
1504
+ return await this.customizeSession(parameters, cancelRequest, taskEventInfo, parallel, loadOutputs, true);
1404
1505
  }
1405
1506
  }
1406
1507
 
@@ -1455,6 +1556,16 @@ export class SessionEngine implements ISessionEngine {
1455
1556
  }
1456
1557
  }
1457
1558
 
1559
+ private removeBusyMode(busyId: string) {
1560
+ for (const r in this._stateEngine.renderingEngines) {
1561
+ if (this._stateEngine.renderingEngines[r].busy.includes(busyId))
1562
+ this._stateEngine.renderingEngines[r].busy.splice(this._stateEngine.renderingEngines[r].busy.indexOf(busyId), 1);
1563
+
1564
+ if (this.#customizationBusyModes.includes(busyId))
1565
+ this.#customizationBusyModes.splice(this.#customizationBusyModes.indexOf(busyId), 1);
1566
+ }
1567
+ }
1568
+
1458
1569
  private removeFromSceneTree(node: ITreeNode) {
1459
1570
  this._sceneTree.removeNode(node);
1460
1571
  this._sceneTree.root.updateVersion();
@@ -1585,5 +1696,24 @@ export class SessionEngine implements ISessionEngine {
1585
1696
  }
1586
1697
  }
1587
1698
 
1588
- // #endregion Private Methods (11)
1699
+ private async waitForUpdateCallbacks(newOutputVersions: { [key: string]: string }, oldOutputVersions: { [key: string]: string }, newNode: ITreeNode, oldNode: ITreeNode) {
1700
+ // call the update callback function on the session
1701
+ if (this._updateCallback) await Promise.resolve(this._updateCallback(newNode, oldNode));
1702
+
1703
+ const promises = [];
1704
+ // call the update callback functions on the outputs
1705
+ for (const outputId in this.outputs) {
1706
+ if (oldOutputVersions[outputId] !== newOutputVersions[outputId]) {
1707
+ promises.push(
1708
+ this.outputs[outputId].triggerUpdateCallback(
1709
+ newNode.children.find(c => c.name === outputId)!,
1710
+ oldNode.children.find(c => c.name === outputId)!
1711
+ )
1712
+ );
1713
+ }
1714
+ }
1715
+ await Promise.all(promises);
1716
+ }
1717
+
1718
+ // #endregion Private Methods (15)
1589
1719
  }