Skip to content

Commit

Permalink
Properly implement parallel timeline sorting & add some tests
Browse files Browse the repository at this point in the history
  • Loading branch information
nickschot committed Jan 27, 2023
1 parent ccf977e commit 4e3eb24
Show file tree
Hide file tree
Showing 2 changed files with 163 additions and 19 deletions.
113 changes: 113 additions & 0 deletions packages/boxel-motion-test-app/tests/unit/models/orchestration-test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { FPS } from '@cardstack/boxel-motion/behaviors/base';
import SpringBehavior from '@cardstack/boxel-motion/behaviors/spring';
import StaticBehavior from '@cardstack/boxel-motion/behaviors/static';
import TweenBehavior from '@cardstack/boxel-motion/behaviors/tween';
import WaitBehavior from '@cardstack/boxel-motion/behaviors/wait';
Expand Down Expand Up @@ -322,4 +323,116 @@ module('Unit | Orchestration', function () {
])
);
});

module('Unit | Orchestration | sortParallelTimeline', function () {
test('it sorts properly when all animations have durations', function (assert) {
let sprites = new Set([getMockSprite()]);

let animation1 = {
sprites,
properties: {},
timing: {
behavior: new TweenBehavior(),
duration: 2000,
},
};
let animation2 = {
sprites,
properties: {},
timing: {
behavior: new TweenBehavior(),
duration: 150,
},
};
let animation3 = {
sprites,
properties: {},
timing: {
behavior: new TweenBehavior(),
duration: 100,
anchor: 'center',
},
};

let sortedAnimations = OrchestrationMatrix.sortParallelTimeline([
animation2,
animation1,
animation3,
]);

assert.deepEqual(sortedAnimations, [animation1, animation2, animation3]);
});

test('it sorts properly when there is an undefined duration', function (assert) {
let sprites = new Set([getMockSprite()]);

let animation1 = {
sprites,
properties: {},
timing: {
behavior: new TweenBehavior(),
duration: 2000,
},
};
let animation2 = {
sprites,
properties: {},
timing: {
behavior: new TweenBehavior(),
duration: 150,
},
};
let animation3 = {
sprites,
properties: {},
timing: {
behavior: new TweenBehavior(),
},
};

let sortedAnimations = OrchestrationMatrix.sortParallelTimeline([
animation2,
animation3,
animation1,
]);

assert.deepEqual(sortedAnimations, [animation1, animation2, animation3]);
});

test('it sorts animations with SpringBehavior to the front', function (assert) {
let sprites = new Set([getMockSprite()]);

let animation1 = {
sprites,
properties: {},
timing: {
behavior: new SpringBehavior(),
},
};
let animation2 = {
sprites,
properties: {},
timing: {
behavior: new TweenBehavior(),
duration: 100,
},
};
let animation3 = {
sprites,
properties: {},
timing: {
behavior: new TweenBehavior(),
anchor: 'center',
},
};

let sortedAnimations = OrchestrationMatrix.sortParallelTimeline([
animation2,
animation1,
animation3,
]);

assert.deepEqual(sortedAnimations, [animation1, animation2, animation3]);
});
});
});
69 changes: 50 additions & 19 deletions packages/boxel-motion/addon/models/orchestration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import Behavior, { FPS } from '@cardstack/boxel-motion/behaviors/base';

import SpringBehavior from '@cardstack/boxel-motion/behaviors/spring';
import StaticBehavior from '@cardstack/boxel-motion/behaviors/static';
import TweenBehavior from '@cardstack/boxel-motion/behaviors/tween';
import WaitBehavior from '@cardstack/boxel-motion/behaviors/wait';
import Sprite, {
MotionOptions,
Expand Down Expand Up @@ -166,35 +165,61 @@ export class OrchestrationMatrix {
return timelineMatrix;
}

static fromParallelTimeline(
timeline: AnimationTimeline
): OrchestrationMatrix {
let timelineMatrix = OrchestrationMatrix.empty();
let submatrices = [];

// TODO: add support for nested timelines
timeline.animations.sort((a, b) => {
if (
'timing' in b &&
(b.timing.duration || !(b.timing.behavior instanceof SpringBehavior))
) {
// Probably need to do a 2-pass sort, resolve the springs first and then
// figure out what has the longest duration.
// Sort order generated by this is: Springs, Duration (long -> short), undefined timing
static sortParallelTimeline(
animations: (MotionDefinition | AnimationTimeline)[]
): (MotionDefinition | AnimationTimeline)[] {
return [...animations].sort((a, b) => {
if (!('timing' in b)) {
return -1;
} else if (
'timing' in a &&
(a.timing.duration || !(a.timing.behavior instanceof SpringBehavior))
) {
} else if (!('timing' in a)) {
return 1;
} else {
if (b.timing.behavior instanceof SpringBehavior) {
return 1;
}

if (a.timing.behavior instanceof SpringBehavior) {
return -1;
}

if (
a.timing.duration !== undefined &&
b.timing.duration !== undefined
) {
if (a.timing.duration > b.timing.duration) {
return -1;
} else if (a.timing.duration < b.timing.duration) {
return 1;
}
} else if (a.timing.duration !== undefined) {
return -1;
} else if (b.timing.duration !== undefined) {
return 1;
}
}

return 0;
});
}

static fromParallelTimeline(
timeline: AnimationTimeline
): OrchestrationMatrix {
let timelineMatrix = OrchestrationMatrix.empty();
let submatrices: OrchestrationMatrix[] = [];

timeline.animations = this.sortParallelTimeline(timeline.animations);

// maxLength is for anchoring to the end.
let maxLength = 0;
for (let item of timeline.animations) {
timeline.animations.forEach((item, index) => {
// If the parallel timeline has an anchor setting but the MotionDefinition
// doesn't have one, we take the parent anchor.
if (
index > 0 &&
timeline.anchor &&
'timing' in item &&
!item.timing.anchor &&
Expand All @@ -203,14 +228,19 @@ export class OrchestrationMatrix {
item.timing.anchor = timeline.anchor;
}

// after sorting the first item cannot have an anchor defined as it is the reference.
if (index === 0 && 'timing' in item && item.timing.anchor) {
delete item.timing.anchor;
}

// TODO: do we want a different option or more flexibility here? We could for example search for the longest
// non-inferred duration already compiled rather than picking the first one. Another option is to explicitly
// have to link to a MotionDefinition to infer from.
let submatrix = OrchestrationMatrix.from(item, maxLength);

maxLength = Math.max(maxLength, submatrix.totalColumns);
submatrices.push(submatrix);
}
});

for (let submatrix of submatrices) {
timelineMatrix.add(0, submatrix);
Expand All @@ -229,6 +259,7 @@ export class OrchestrationMatrix {
let properties = motionDefinition.properties;
let timing = motionDefinition.timing;
let rows = new Map<Sprite, RowFragment[]>();

for (let sprite of motionDefinition.sprites) {
let rowFragments: RowFragment[] = [];
let startColumn = 0;
Expand Down

0 comments on commit 4e3eb24

Please sign in to comment.