From 1d084c48b1a0ed53745b2da24af376792e5ddc2f Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 13 Jan 2022 13:32:58 +0300 Subject: [PATCH] Fixed bug: canvas is busy (at least one reproducing way) (#4151) * Fixed bug: canvas is busy (at least one reproducing way) * Updated version & changelog * Fixed license headers --- CHANGELOG.md | 1 + cvat-canvas/package-lock.json | 4 +- cvat-canvas/package.json | 2 +- cvat-canvas/src/typescript/canvasView.ts | 57 ++++++++++++------- cvat-ui/package-lock.json | 4 +- cvat-ui/package.json | 2 +- cvat-ui/src/actions/annotation-actions.ts | 51 +++++++++++------ .../annotation-page/canvas/canvas-wrapper.tsx | 16 +++--- cvat-ui/src/utils/is-able-to-change-frame.ts | 6 +- 9 files changed, 88 insertions(+), 55 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 750298858..ede6b4659 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Order in an annotation file() - Fixed task data upload progressbar () - Email in org invitations is case sensitive () +- Bug: canvas is busy when start playing, start resizing a shape and do not release the mouse cursor () ### Security - Updated ELK to 6.8.22 which uses log4j 2.17.0 () diff --git a/cvat-canvas/package-lock.json b/cvat-canvas/package-lock.json index 6806cf71d..228a91101 100644 --- a/cvat-canvas/package-lock.json +++ b/cvat-canvas/package-lock.json @@ -1,12 +1,12 @@ { "name": "cvat-canvas", - "version": "2.12.0", + "version": "2.12.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cvat-canvas", - "version": "2.12.0", + "version": "2.12.1", "license": "MIT", "dependencies": { "@types/polylabel": "^1.0.5", diff --git a/cvat-canvas/package.json b/cvat-canvas/package.json index 0cfc1bc4e..7e4ce64f4 100644 --- a/cvat-canvas/package.json +++ b/cvat-canvas/package.json @@ -1,6 +1,6 @@ { "name": "cvat-canvas", - "version": "2.12.0", + "version": "2.12.1", "description": "Part of Computer Vision Annotation Tool which presents its canvas library", "main": "src/canvas.ts", "scripts": { diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 7a78ba326..1d9cf9e8b 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2021 Intel Corporation +// Copyright (C) 2019-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -684,18 +684,7 @@ export class CanvasViewImpl implements CanvasView, Listener { this.deactivate(); } - for (const state of deleted) { - if (state.clientID in this.svgTexts) { - this.svgTexts[state.clientID].remove(); - delete this.svgTexts[state.clientID]; - } - - this.svgShapes[state.clientID].off('click.canvas'); - this.svgShapes[state.clientID].remove(); - delete this.drawnStates[state.clientID]; - delete this.svgShapes[state.clientID]; - } - + this.deleteObjects(deleted); this.addObjects(created); this.updateObjects(updated); this.sortObjects(); @@ -1727,6 +1716,22 @@ export class CanvasViewImpl implements CanvasView, Listener { } } + private deleteObjects(states: any[]): void { + for (const state of states) { + if (state.clientID in this.svgTexts) { + this.svgTexts[state.clientID].remove(); + delete this.svgTexts[state.clientID]; + } + + this.svgShapes[state.clientID].fire('remove'); + this.svgShapes[state.clientID].off('click'); + this.svgShapes[state.clientID].off('remove'); + this.svgShapes[state.clientID].remove(); + delete this.drawnStates[state.clientID]; + delete this.svgShapes[state.clientID]; + } + } + private addObjects(states: any[]): void { const { displayAllText } = this.configuration; for (const state of states) { @@ -1940,10 +1945,14 @@ export class CanvasViewImpl implements CanvasView, Listener { .on('dragstart', (): void => { this.mode = Mode.DRAG; hideText(); + (shape as any).on('remove.drag', (): void => { + this.mode = Mode.IDLE; + }); }) .on('dragend', (e: CustomEvent): void => { - showText(); + (shape as any).off('remove.drag'); this.mode = Mode.IDLE; + showText(); const p1 = e.detail.handler.startPoints.point; const p2 = e.detail.p; const delta = 1; @@ -1997,6 +2006,15 @@ export class CanvasViewImpl implements CanvasView, Listener { let shapeSizeElement: ShapeSizeElement | null = null; let resized = false; + + const resizeFinally = (): void => { + if (shapeSizeElement) { + shapeSizeElement.rm(); + shapeSizeElement = null; + } + this.mode = Mode.IDLE; + }; + (shape as any) .resize({ snapToGrid: 0.1, @@ -2010,6 +2028,7 @@ export class CanvasViewImpl implements CanvasView, Listener { if (state.shapeType === 'rectangle') { shapeSizeElement = displayShapeSize(this.adoptedContent, this.adoptedText); } + (shape as any).on('remove.resize', resizeFinally); }) .on('resizing', (): void => { resized = true; @@ -2018,16 +2037,10 @@ export class CanvasViewImpl implements CanvasView, Listener { } }) .on('resizedone', (): void => { - if (shapeSizeElement) { - shapeSizeElement.rm(); - shapeSizeElement = null; - } - + (shape as any).off('remove.resize'); + resizeFinally(); showDirection(); showText(); - - this.mode = Mode.IDLE; - if (resized) { let rotation = shape.transform().rotation || 0; diff --git a/cvat-ui/package-lock.json b/cvat-ui/package-lock.json index 18c2629cf..141bc22c2 100644 --- a/cvat-ui/package-lock.json +++ b/cvat-ui/package-lock.json @@ -1,12 +1,12 @@ { "name": "cvat-ui", - "version": "1.33.0", + "version": "1.33.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cvat-ui", - "version": "1.33.0", + "version": "1.33.1", "license": "MIT", "dependencies": { "@ant-design/icons": "^4.6.3", diff --git a/cvat-ui/package.json b/cvat-ui/package.json index c91697277..0cf3930af 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.33.0", + "version": "1.33.1", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 375bc59d5..27b4e9df2 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -6,10 +6,12 @@ import { ActionCreator, AnyAction, Dispatch, Store, } from 'redux'; import { ThunkAction } from 'utils/redux'; +import isAbleToChangeFrame from 'utils/is-able-to-change-frame'; import { RectDrawingMethod, CuboidDrawingMethod, Canvas } from 'cvat-canvas-wrapper'; import getCore from 'cvat-core-wrapper'; import logger, { LogType } from 'cvat-logger'; import { getCVATStore } from 'cvat-store'; + import { ActiveControl, CombinedState, @@ -691,8 +693,8 @@ export function changeFrameAsync( frameStep?: number, forceUpdate?: boolean, ): ThunkAction { - return async (dispatch: ActionCreator): Promise => { - const state: CombinedState = getStore().getState(); + return async (dispatch: ActionCreator, getState: () => CombinedState): Promise => { + const state: CombinedState = getState(); const { instance: job } = state.annotation.job; const { filters, frame, showAllInterpolationTracks } = receiveAnnotationsParameters(); @@ -701,37 +703,50 @@ export function changeFrameAsync( throw Error(`Required frame ${toFrame} is out of the current job`); } - if (toFrame === frame && !forceUpdate) { - dispatch({ + const abortAction = (): AnyAction => { + const currentState = getState(); + return ({ type: AnnotationActionTypes.CHANGE_FRAME_SUCCESS, payload: { - number: state.annotation.player.frame.number, - data: state.annotation.player.frame.data, - filename: state.annotation.player.frame.filename, - hasRelatedContext: state.annotation.player.frame.hasRelatedContext, - delay: state.annotation.player.frame.delay, - changeTime: state.annotation.player.frame.changeTime, - states: state.annotation.annotations.states, - minZ: state.annotation.annotations.zLayer.min, - maxZ: state.annotation.annotations.zLayer.max, - curZ: state.annotation.annotations.zLayer.cur, + number: currentState.annotation.player.frame.number, + data: currentState.annotation.player.frame.data, + filename: currentState.annotation.player.frame.filename, + hasRelatedContext: currentState.annotation.player.frame.hasRelatedContext, + delay: currentState.annotation.player.frame.delay, + changeTime: currentState.annotation.player.frame.changeTime, + states: currentState.annotation.annotations.states, + minZ: currentState.annotation.annotations.zLayer.min, + maxZ: currentState.annotation.annotations.zLayer.max, + curZ: currentState.annotation.annotations.zLayer.cur, }, }); + }; - return; - } - // Start async requests dispatch({ type: AnnotationActionTypes.CHANGE_FRAME, payload: {}, }); + if (toFrame === frame && !forceUpdate) { + dispatch(abortAction()); + return; + } + + // Start async requests await job.logger.log(LogType.changeFrame, { from: frame, to: toFrame, }); const data = await job.frames.get(toFrame, fillBuffer, frameStep); const states = await job.annotations.get(toFrame, showAllInterpolationTracks, filters); + + if (!isAbleToChangeFrame()) { + // while doing async actions above, canvas can become used by user in another way + // so, we need additional check and if it is used, we do not update state + dispatch(abortAction()); + return; + } + const [minZ, maxZ] = computeZRange(states); const currentTime = new Date().getTime(); let frameSpeed; diff --git a/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper.tsx b/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper.tsx index 2be9ae0f7..6c1ca79b7 100644 --- a/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -308,12 +308,14 @@ export default class CanvasWrapperComponent extends React.PureComponent { } } - const loadingAnimation = window.document.getElementById('cvat_canvas_loading_animation'); - if (loadingAnimation && frameFetching !== prevProps.frameFetching) { - if (frameFetching) { - loadingAnimation.classList.remove('cvat_canvas_hidden'); - } else { - loadingAnimation.classList.add('cvat_canvas_hidden'); + if (frameFetching !== prevProps.frameFetching) { + const loadingAnimation = window.document.getElementById('cvat_canvas_loading_animation'); + if (loadingAnimation) { + if (frameFetching) { + loadingAnimation.classList.remove('cvat_canvas_hidden'); + } else { + loadingAnimation.classList.add('cvat_canvas_hidden'); + } } } diff --git a/cvat-ui/src/utils/is-able-to-change-frame.ts b/cvat-ui/src/utils/is-able-to-change-frame.ts index 0425811b6..08c8511d6 100644 --- a/cvat-ui/src/utils/is-able-to-change-frame.ts +++ b/cvat-ui/src/utils/is-able-to-change-frame.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Intel Corporation +// Copyright (C) 2021-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -9,5 +9,7 @@ export default function isAbleToChangeFrame(): boolean { const store = getCVATStore(); const state: CombinedState = store.getState(); - return state.annotation.canvas.instance.isAbleToChangeFrame() && !state.annotation.player.navigationBlocked; + const { instance } = state.annotation.canvas; + return !!instance && instance.isAbleToChangeFrame() && + !state.annotation.player.navigationBlocked; } -- GitLab