-
-
Notifications
You must be signed in to change notification settings - Fork 4.1k
fix: Align GRID_STATE_CHANGED timing with viewport updates #5624
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
0d8698d
15773a3
0df79b0
ad6e7fc
3854d67
a14430e
ce0955a
982499f
b9471f6
851eb77
b4a7367
4efdb7a
d2c2cdb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -397,11 +397,13 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi | |
| presentations?: Presentations | ||
| ): void { | ||
| const renderingEngine = this.getRenderingEngine(); | ||
| const { viewportGridService } = this.servicesManager.services; | ||
|
|
||
| // if not valid viewportData then return early | ||
| if (viewportData.viewportType === csEnums.ViewportType.STACK) { | ||
| // check if imageIds is valid | ||
| if (!viewportData.data[0].imageIds?.length) { | ||
| viewportGridService?.notifyViewportUpdateCompleted?.(viewportId); | ||
| return; | ||
| } | ||
| } | ||
|
|
@@ -490,6 +492,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi | |
| viewportData, | ||
| viewportId, | ||
| }); | ||
| viewportGridService?.notifyViewportUpdateCompleted?.(viewportId); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Promise rejection prevents grid state change notificationThe Additional Locations (1) |
||
| }); | ||
| } | ||
|
|
||
|
|
@@ -1107,6 +1110,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi | |
| const viewportInfo = this.getViewportInfo(viewportId); | ||
| const viewport = this.getCornerstoneViewport(viewportId); | ||
| const viewportCamera = viewport.getCamera(); | ||
| const { viewportGridService } = this.servicesManager.services; | ||
|
|
||
| let displaySetPromise; | ||
|
|
||
|
|
@@ -1128,6 +1132,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi | |
| viewportData, | ||
| viewportId, | ||
| }); | ||
| viewportGridService?.notifyViewportUpdateCompleted?.(viewportId); | ||
| }); | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,17 @@ type PresentationIdProvider = ( | |
| { viewport, viewports, isUpdatingSameViewport } | ||
| ) => unknown; | ||
|
|
||
| type PendingGridStateChangePayload = { | ||
| state: AppTypes.ViewportGrid.State; | ||
| viewports?: AppTypes.ViewportGrid.Viewport[]; | ||
| removedViewportIds: string[]; | ||
| }; | ||
|
|
||
| type PendingGridStateChange = { | ||
| payload: PendingGridStateChangePayload; | ||
| pendingViewportIds: Set<string>; | ||
| }; | ||
|
|
||
| class ViewportGridService extends PubSubService { | ||
| public static readonly EVENTS = { | ||
| ACTIVE_VIEWPORT_ID_CHANGED: 'event::activeviewportidchanged', | ||
|
|
@@ -26,12 +37,18 @@ class ViewportGridService extends PubSubService { | |
| serviceImplementation = {}; | ||
| servicesManager: AppTypes.ServicesManager; | ||
| presentationIdProviders: Map<string, PresentationIdProvider>; | ||
| pendingGridStateChanges: PendingGridStateChange[]; | ||
|
|
||
| constructor({ servicesManager }) { | ||
| super(ViewportGridService.EVENTS); | ||
| this.servicesManager = servicesManager; | ||
| this.serviceImplementation = {}; | ||
| this.presentationIdProviders = new Map(); | ||
| /** | ||
| * Pending grid state changes waiting for associated viewports | ||
| * to signal that their data updates completed. | ||
| */ | ||
| this.pendingGridStateChanges = []; | ||
| } | ||
|
|
||
| public addPresentationIdProvider(id: string, provider: PresentationIdProvider): void { | ||
|
|
@@ -148,14 +165,15 @@ class ViewportGridService extends PubSubService { | |
| if (id === this.getActiveViewportId()) { | ||
| return; | ||
| } | ||
| this.serviceImplementation._setActiveViewport(id); | ||
| const state = this.serviceImplementation._setActiveViewport(id); | ||
|
|
||
| // Use queueMicrotask to delay the event broadcast | ||
| setTimeout(() => { | ||
| this._broadcastEvent(this.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED, { | ||
| viewportId: id, | ||
| }); | ||
| }, 0); | ||
| this._broadcastEvent(this.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED, { | ||
| viewportId: id, | ||
| state, | ||
| }); | ||
|
|
||
| return state; | ||
| } | ||
|
|
||
| public getState(): AppTypes.ViewportGrid.State { | ||
|
|
@@ -182,14 +200,20 @@ class ViewportGridService extends PubSubService { | |
| }); | ||
| } | ||
|
|
||
| public setDisplaySetsForViewport(props) { | ||
| public setDisplaySetsForViewport(props, options: { preCallback?: () => void } = {}) { | ||
| // Just update a single viewport, but use the multi-viewport update for it. | ||
| this.setDisplaySetsForViewports([props]); | ||
| this.setDisplaySetsForViewports([props], { preCallback: options.preCallback }); | ||
| } | ||
|
|
||
| public async setDisplaySetsForViewports(viewportsToUpdate) { | ||
| await this.serviceImplementation._setDisplaySetsForViewports(viewportsToUpdate); | ||
| const state = this.getState(); | ||
| public async setDisplaySetsForViewports( | ||
| viewportsToUpdate, | ||
| { preCallback }: { preCallback?: () => void } = {} | ||
| ) { | ||
| if (preCallback) { | ||
| preCallback(); | ||
| } | ||
sedghi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| const state = await this.serviceImplementation._setDisplaySetsForViewports(viewportsToUpdate); | ||
| const updatedViewports = []; | ||
|
|
||
| const removedViewportIds = []; | ||
|
|
@@ -212,13 +236,14 @@ class ViewportGridService extends PubSubService { | |
| } | ||
| } | ||
|
|
||
| setTimeout(() => { | ||
| this._broadcastEvent(ViewportGridService.EVENTS.GRID_STATE_CHANGED, { | ||
| this._queueGridStateChanged( | ||
| { | ||
| state, | ||
| viewports: updatedViewports, | ||
| removedViewportIds, | ||
| }); | ||
| }); | ||
| }, | ||
| updatedViewports.map(viewport => viewport.viewportId) | ||
| ); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Empty viewport pending IDs block GRID_STATE_CHANGED eventsIn |
||
| } | ||
|
|
||
| /** | ||
|
|
@@ -253,7 +278,7 @@ class ViewportGridService extends PubSubService { | |
| const prevState = this.getState(); | ||
| const prevViewportIds = new Set(prevState.viewports.keys()); | ||
|
|
||
| await this.serviceImplementation._setLayout({ | ||
| const state = await this.serviceImplementation._setLayout({ | ||
| numCols, | ||
| numRows, | ||
| layoutOptions, | ||
|
|
@@ -263,29 +288,29 @@ class ViewportGridService extends PubSubService { | |
| isHangingProtocolLayout, | ||
| }); | ||
|
|
||
| // Use queueMicrotask to ensure the layout changed event is published after | ||
| setTimeout(() => { | ||
| // Get the new state after the layout change | ||
| const state = this.getState(); | ||
| const currentViewportIds = new Set(state.viewports.keys()); | ||
| const currentViewportIds = new Set(state.viewports.keys()); | ||
|
|
||
| // Determine which viewport IDs have been removed | ||
| const removedViewportIds = [...prevViewportIds].filter(id => !currentViewportIds.has(id)); | ||
| // Determine which viewport IDs have been removed | ||
| const removedViewportIds = [...prevViewportIds].filter(id => !currentViewportIds.has(id)); | ||
|
|
||
| this._broadcastEvent(this.EVENTS.LAYOUT_CHANGED, { | ||
| numCols, | ||
| numRows, | ||
| }); | ||
| // Use queueMicrotask to ensure the layout changed event is published after | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is published after the state has been updated/is available. |
||
| this._broadcastEvent(this.EVENTS.LAYOUT_CHANGED, { | ||
| numCols, | ||
| numRows, | ||
| }); | ||
|
|
||
| this._broadcastEvent(this.EVENTS.GRID_STATE_CHANGED, { | ||
| this._queueGridStateChanged( | ||
| { | ||
| state, | ||
| removedViewportIds, | ||
| }); | ||
| }, 0); | ||
| }, | ||
| this._getViewportIdsWithDisplaySets(state) | ||
| ); | ||
sedghi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| public reset() { | ||
| this.serviceImplementation._reset(); | ||
| this.pendingGridStateChanges = []; | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -296,31 +321,100 @@ class ViewportGridService extends PubSubService { | |
| */ | ||
| public onModeExit(): void { | ||
| this.serviceImplementation._onModeExit(); | ||
| this.pendingGridStateChanges = []; | ||
| } | ||
|
|
||
| public set(newState) { | ||
| const prevState = this.getState(); | ||
| const prevViewportIds = new Set(prevState.viewports.keys()); | ||
|
|
||
| this.serviceImplementation._set(newState); | ||
| const state = this.serviceImplementation._set(newState); | ||
|
|
||
| const state = this.getState(); | ||
| const currentViewportIds = new Set(state.viewports.keys()); | ||
|
|
||
| const removedViewportIds = [...prevViewportIds].filter(id => !currentViewportIds.has(id)); | ||
|
|
||
| setTimeout(() => { | ||
| this._broadcastEvent(this.EVENTS.GRID_STATE_CHANGED, { | ||
| this._queueGridStateChanged( | ||
| { | ||
| state, | ||
| removedViewportIds, | ||
| }); | ||
| }, 0); | ||
| }, | ||
| this._getViewportIdsWithDisplaySets(state) | ||
| ); | ||
| } | ||
|
|
||
| public getNumViewportPanes() { | ||
| return this.serviceImplementation._getNumViewportPanes(); | ||
| } | ||
|
|
||
| /** | ||
| * Signals that a viewport has finished applying its pending data update so that queued | ||
| * grid state changes can be published when all dependencies are resolved. | ||
| */ | ||
| public notifyViewportUpdateCompleted(viewportId: string) { | ||
| if (!viewportId || !this.pendingGridStateChanges.length) { | ||
| return; | ||
| } | ||
|
|
||
| let didUpdatePendingChange = false; | ||
| for (let i = 0; i < this.pendingGridStateChanges.length; i++) { | ||
| const pendingChange = this.pendingGridStateChanges[i]; | ||
| didUpdatePendingChange = | ||
| pendingChange.pendingViewportIds.delete(viewportId) || didUpdatePendingChange; | ||
| } | ||
sedghi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| if (didUpdatePendingChange) { | ||
| this._flushPendingGridStateChanges(); | ||
| } | ||
| } | ||
|
|
||
| private _getViewportIdsWithDisplaySets(state: AppTypes.ViewportGrid.State): string[] { | ||
| if (!state?.viewports?.size) { | ||
| return []; | ||
| } | ||
|
|
||
| const viewportIds: string[] = []; | ||
|
|
||
| state.viewports.forEach(viewport => { | ||
| if (viewport?.displaySetInstanceUIDs?.length && viewport.viewportId) { | ||
| viewportIds.push(viewport.viewportId); | ||
| } | ||
| }); | ||
|
|
||
| return viewportIds; | ||
| } | ||
|
|
||
| private _queueGridStateChanged( | ||
| payload: PendingGridStateChangePayload, | ||
| pendingViewportIds: string[] = [] | ||
| ) { | ||
| const uniquePendingIds = Array.from(new Set(pendingViewportIds?.filter(Boolean))); | ||
|
|
||
| if (!uniquePendingIds.length) { | ||
| this._broadcastEvent(this.EVENTS.GRID_STATE_CHANGED, payload); | ||
| return; | ||
| } | ||
|
|
||
| const pendingChange: PendingGridStateChange = { | ||
| payload, | ||
| pendingViewportIds: new Set(uniquePendingIds), | ||
| }; | ||
|
|
||
| this.pendingGridStateChanges.push(pendingChange); | ||
| } | ||
|
|
||
| private _flushPendingGridStateChanges() { | ||
| while (this.pendingGridStateChanges.length) { | ||
| const nextChange = this.pendingGridStateChanges[0]; | ||
| if (nextChange.pendingViewportIds.size) { | ||
| break; | ||
| } | ||
|
|
||
| this._broadcastEvent(this.EVENTS.GRID_STATE_CHANGED, nextChange.payload); | ||
| this.pendingGridStateChanges.shift(); | ||
| } | ||
| } | ||
|
|
||
| public getLayoutOptionsFromState( | ||
| state: any | ||
| ): { x: number; y: number; width: number; height: number }[] { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.