From 6bdd2ebe7d90f5a68b8c2c17045cce51f71a340e Mon Sep 17 00:00:00 2001 From: Josh Ellis Date: Mon, 29 Mar 2021 18:59:56 +0100 Subject: [PATCH 1/3] fix: add stopAsync call in async animation loop --- packages/core/src/runAsync.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/core/src/runAsync.ts b/packages/core/src/runAsync.ts index e7a95ff2e2..2e4b323422 100644 --- a/packages/core/src/runAsync.ts +++ b/packages/core/src/runAsync.ts @@ -1,4 +1,11 @@ -import { is, raf, flush, eachProp, Timeout } from '@react-spring/shared' +import { + is, + raf, + flush, + eachProp, + Timeout, + Globals as G, +} from '@react-spring/shared' import { Falsy } from '@react-spring/types' import { getDefaultProps } from './helpers' @@ -90,6 +97,15 @@ export function runAsync( const bailSignal = new BailSignal() return (async () => { + if (G.skipAnimation) { + /** + * We need to stop animations if `skipAnimation` + * is set in the Globals + * + */ + stopAsync(state) + } + bailIfEnded(bailSignal) const props: any = is.obj(arg1) ? { ...arg1 } : { ...arg2, to: arg1 } From 25f748766adb29b8303764f8590204d898216cd9 Mon Sep 17 00:00:00 2001 From: Josh Ellis Date: Tue, 30 Mar 2021 19:44:53 +0100 Subject: [PATCH 2/3] feat: add handle to not even start if skipAnimation is set --- packages/core/src/Controller.test.ts | 26 ++++++++++++++++++++++++++ packages/core/src/runAsync.ts | 20 +++++++++++--------- packages/core/test/setup.ts | 9 +++++++++ 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/packages/core/src/Controller.test.ts b/packages/core/src/Controller.test.ts index 4060b32ca9..afe375add7 100644 --- a/packages/core/src/Controller.test.ts +++ b/packages/core/src/Controller.test.ts @@ -255,6 +255,32 @@ describe('Controller', () => { // Since we call `update` twice, frames are generated! expect(global.getFrames(ctrl)).toMatchSnapshot() }) + + describe('when skipAnimations is true', () => { + it('should not run at all', async () => { + const ctrl = new Controller({ from: { x: 0 } }) + let n = 0 + + global.setSkipAnimation(true) + + ctrl.start({ + to: async next => { + while (true) { + console.log('animating') + n += 1 + await next({ x: 1, reset: true }) + } + }, + }) + + await flushMicroTasks() + expect(n).toBe(0) + }) + + it('should stop running and push the animation to the finished state when called mid animation', async () => {}) + + it('should call onStart and onRest', async () => {}) + }) }) describe('when the "onStart" prop is defined', () => { diff --git a/packages/core/src/runAsync.ts b/packages/core/src/runAsync.ts index 2e4b323422..157b7b9fc0 100644 --- a/packages/core/src/runAsync.ts +++ b/packages/core/src/runAsync.ts @@ -97,15 +97,6 @@ export function runAsync( const bailSignal = new BailSignal() return (async () => { - if (G.skipAnimation) { - /** - * We need to stop animations if `skipAnimation` - * is set in the Globals - * - */ - stopAsync(state) - } - bailIfEnded(bailSignal) const props: any = is.obj(arg1) ? { ...arg1 } : { ...arg2, to: arg1 } @@ -131,6 +122,17 @@ export function runAsync( } let result!: AnimationResult + + if (G.skipAnimation) { + /** + * We need to stop animations if `skipAnimation` + * is set in the Globals + * + */ + stopAsync(state) + return getFinishedResult(target, false) + } + try { let animating!: Promise diff --git a/packages/core/test/setup.ts b/packages/core/test/setup.ts index abcf16c76f..ab0cd21543 100644 --- a/packages/core/test/setup.ts +++ b/packages/core/test/setup.ts @@ -37,6 +37,8 @@ declare global { // @ts-ignore setTimeout: (handler: Function, ms: number) => number + + setSkipAnimation: (skip: boolean) => void } } } @@ -60,6 +62,7 @@ beforeEach(() => { now: global.mockRaf.now, requestAnimationFrame: global.mockRaf.raf, colors, + skipAnimation: false, // This lets our useTransition hook force its component // to update from within an "onRest" handler. batchedUpdates: act, @@ -184,3 +187,9 @@ global.advanceUntilValue = (spring, value) => { return stop }) } + +global.setSkipAnimation = skip => { + Globals.assign({ + skipAnimation: skip, + }) +} From b95df8367f84661cfefbb558391c2175646dfd9c Mon Sep 17 00:00:00 2001 From: Josh Ellis Date: Tue, 30 Mar 2021 23:18:32 +0100 Subject: [PATCH 3/3] feat: handle mid async loop skipAnimation setting --- packages/core/src/Controller.test.ts | 26 +++++++++++++++++++++++--- packages/core/src/runAsync.ts | 26 +++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/packages/core/src/Controller.test.ts b/packages/core/src/Controller.test.ts index afe375add7..a6cdf6fa12 100644 --- a/packages/core/src/Controller.test.ts +++ b/packages/core/src/Controller.test.ts @@ -266,7 +266,6 @@ describe('Controller', () => { ctrl.start({ to: async next => { while (true) { - console.log('animating') n += 1 await next({ x: 1, reset: true }) } @@ -277,9 +276,30 @@ describe('Controller', () => { expect(n).toBe(0) }) - it('should stop running and push the animation to the finished state when called mid animation', async () => {}) + it('should stop running and push the animation to the finished state when called mid animation', async () => { + const ctrl = new Controller({ from: { x: 0 } }) + let n = 0 - it('should call onStart and onRest', async () => {}) + ctrl.start({ + to: async next => { + while (n < 5) { + n++ + await next({ x: 10, reset: true }) + } + }, + }) + + await global.advance() + expect(n).toBe(1) + + global.setSkipAnimation(true) + + await global.advanceUntilIdle() + + const { x } = ctrl.springs + expect(n).toBe(2) + expect(x.get()).toEqual(10) + }) }) }) diff --git a/packages/core/src/runAsync.ts b/packages/core/src/runAsync.ts index 157b7b9fc0..dc51f6a3b8 100644 --- a/packages/core/src/runAsync.ts +++ b/packages/core/src/runAsync.ts @@ -95,8 +95,23 @@ export function runAsync( // Create the bail signal outside the returned promise, // so the generated stack trace is relevant. const bailSignal = new BailSignal() + const skipAnimationSignal = new SkipAniamtionSignal() return (async () => { + if (G.skipAnimation) { + /** + * We need to stop animations if `skipAnimation` + * is set in the Globals + * + */ + stopAsync(state) + + // create the rejection error that's handled gracefully + skipAnimationSignal.result = getFinishedResult(target, false) + bail(skipAnimationSignal) + throw skipAnimationSignal + } + bailIfEnded(bailSignal) const props: any = is.obj(arg1) ? { ...arg1 } : { ...arg2, to: arg1 } @@ -127,7 +142,6 @@ export function runAsync( /** * We need to stop animations if `skipAnimation` * is set in the Globals - * */ stopAsync(state) return getFinishedResult(target, false) @@ -157,6 +171,8 @@ export function runAsync( } catch (err) { if (err instanceof BailSignal) { result = err.result + } else if (err instanceof SkipAniamtionSignal) { + result = err.result } else { throw err } @@ -199,3 +215,11 @@ export class BailSignal extends Error { ) } } + +export class SkipAniamtionSignal extends Error { + result!: AnimationResult + + constructor() { + super('SkipAnimationSignal') + } +}