Skip to content

Commit d758dcf

Browse files
CZX123notnotmaxRichDom2185martin-henz
authored
CSE Machine: Add some control and stash animations (#2776)
* display array indices * animate moving numbers from agenda to stash * add code to read from previous agenda * Add animation components and some abstraction * Change test cases * Fix bug Properly disable animations when control and stash option is not enabled * display array indices * animate moving numbers from agenda to stash * add code to read from previous agenda * Add animation components and some abstraction * Change test cases * Fix bug Properly disable animations when control and stash option is not enabled * Fix issues with names after rebase * Fix animation bugs and refactoring of animation classes and logic * Fix mistake in test snapshot * Revert "Merge branch 'cse-uiux' of https://github.com/source-academy/frontend into cse-uiux" This reverts commit 7ef87d8, reversing changes made to efa8c57. * Restructure animation classes * Add binary operator animation * Add unary operator animation * Begin work on block separation animation * Improve binary operation animation, and improve the versatility of the base animation components * Improve the unary operation and block animations * Update test cases and remove block animation conditions * Add pop animation (linear movement) * Improve pop animation, and cleanup code for pull request * Revert envVisualizer test snapshot changes * Rename AnimationUtils.tsx to AnimationUtils.ts --------- Co-authored-by: notnotmax <[email protected]> Co-authored-by: Richard Dominick <[email protected]> Co-authored-by: Martin Henz <[email protected]>
1 parent a1fc27b commit d758dcf

15 files changed

+695
-5
lines changed

src/commons/sideContent/SideContentEnvVisualizer.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { HotKeys } from 'react-hotkeys';
1515
import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux';
1616
import { bindActionCreators, Dispatch } from 'redux';
1717
import EnvVisualizer from 'src/features/envVisualizer/EnvVisualizer';
18+
import { CSEAnimation } from 'src/features/envVisualizer/EnvVisualizerAnimation';
1819
import { Layout } from 'src/features/envVisualizer/EnvVisualizerLayout';
1920

2021
import { OverallState } from '../application/ApplicationTypes';
@@ -77,7 +78,7 @@ class SideContentEnvVisualizer extends React.Component<EnvVisualizerProps, State
7778
stepLimitExceeded: false
7879
};
7980
EnvVisualizer.init(
80-
visualization => this.setState({ visualization }),
81+
visualization => this.setState({ visualization }, () => CSEAnimation.playAnimation()),
8182
this.state.width,
8283
this.state.height,
8384
(segments: [number, number][]) => {
@@ -405,6 +406,7 @@ class SideContentEnvVisualizer extends React.Component<EnvVisualizerProps, State
405406
if (this.state.value !== lastStepValue) {
406407
this.sliderShift(this.state.value + 1);
407408
this.sliderRelease(this.state.value + 1);
409+
CSEAnimation.enableAnimations();
408410
}
409411
};
410412

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import { InstrType } from 'js-slang/dist/ec-evaluator/types';
2+
import { Easings } from 'konva/lib/Tween';
3+
4+
import { Animatable } from './animationComponents/AnimationComponents';
5+
import { BinaryOperationAnimation } from './animationComponents/BinaryOperationAnimation';
6+
import { BlockAnimation } from './animationComponents/BlockAnimation';
7+
import { LiteralAnimation } from './animationComponents/LiteralAnimation';
8+
import { PopAnimation } from './animationComponents/PopAnimation';
9+
import { UnaryOperationAnimation } from './animationComponents/UnaryOperationAnimation';
10+
import { isInstr } from './compactComponents/ControlStack';
11+
import EnvVisualizer from './EnvVisualizer';
12+
import { Layout } from './EnvVisualizerLayout';
13+
14+
export class CSEAnimation {
15+
private static animationEnabled = false;
16+
static readonly animationComponents: Animatable[] = [];
17+
static readonly defaultDuration = 0.3;
18+
static readonly defaultEasing = Easings.StrongEaseInOut;
19+
20+
static enableAnimations(): void {
21+
CSEAnimation.animationEnabled = true;
22+
}
23+
24+
static disableAnimations(): void {
25+
CSEAnimation.animationEnabled = false;
26+
}
27+
28+
private static clearAnimationComponents(): void {
29+
CSEAnimation.animationComponents.length = 0;
30+
}
31+
32+
static updateAnimation() {
33+
CSEAnimation.animationComponents.forEach(a => a.destroy());
34+
CSEAnimation.clearAnimationComponents();
35+
36+
if (!Layout.previousControlComponent) return;
37+
const lastControlItem = Layout.previousControlComponent.control.peek();
38+
const lastControlComponent = Layout.previousControlComponent.stackItemComponents.at(-1);
39+
if (
40+
!CSEAnimation.animationEnabled ||
41+
!lastControlItem ||
42+
!lastControlComponent ||
43+
!EnvVisualizer.getControlStash() // TODO: handle cases where there are environment animations
44+
) {
45+
return;
46+
}
47+
let animation: Animatable | undefined;
48+
if (!isInstr(lastControlItem)) {
49+
switch (lastControlItem.type) {
50+
case 'Literal':
51+
animation = new LiteralAnimation(
52+
lastControlComponent,
53+
Layout.stashComponent.stashItemComponents.at(-1)!
54+
);
55+
break;
56+
case 'Program':
57+
case 'ExpressionStatement':
58+
case 'VariableDeclaration':
59+
const currentControlSize = Layout.controlComponent.control.size();
60+
const previousControlSize = Layout.previousControlComponent.control.size();
61+
const numOfItems = currentControlSize - previousControlSize + 1;
62+
if (numOfItems <= 0) break;
63+
const targetItems = Array.from({ length: numOfItems }, (_, i) => {
64+
return Layout.controlComponent.stackItemComponents[previousControlSize + i - 1];
65+
});
66+
animation = new BlockAnimation(lastControlComponent, targetItems);
67+
break;
68+
}
69+
} else {
70+
switch (lastControlItem.instrType) {
71+
case InstrType.RESET:
72+
case InstrType.WHILE:
73+
case InstrType.FOR:
74+
case InstrType.ASSIGNMENT:
75+
break;
76+
case InstrType.UNARY_OP:
77+
animation = new UnaryOperationAnimation(
78+
lastControlComponent,
79+
Layout.previousStashComponent.stashItemComponents.at(-1)!,
80+
Layout.stashComponent.stashItemComponents.at(-1)!
81+
);
82+
break;
83+
case InstrType.BINARY_OP:
84+
animation = new BinaryOperationAnimation(
85+
lastControlComponent,
86+
Layout.previousStashComponent.stashItemComponents.at(-2)!,
87+
Layout.previousStashComponent.stashItemComponents.at(-1)!,
88+
Layout.stashComponent.stashItemComponents.at(-1)!
89+
);
90+
break;
91+
case InstrType.POP:
92+
animation = new PopAnimation(
93+
lastControlComponent,
94+
Layout.previousStashComponent.stashItemComponents.at(-1)!
95+
);
96+
break;
97+
case InstrType.APPLICATION:
98+
case InstrType.BRANCH:
99+
case InstrType.ENVIRONMENT:
100+
case InstrType.ARRAY_LITERAL:
101+
case InstrType.ARRAY_ACCESS:
102+
case InstrType.ARRAY_ASSIGNMENT:
103+
case InstrType.ARRAY_LENGTH:
104+
case InstrType.CONTINUE_MARKER:
105+
case InstrType.BREAK:
106+
case InstrType.BREAK_MARKER:
107+
case InstrType.MARKER:
108+
}
109+
}
110+
if (animation) CSEAnimation.animationComponents.push(animation);
111+
}
112+
113+
static playAnimation(): void {
114+
if (!CSEAnimation.animationEnabled) {
115+
CSEAnimation.disableAnimations();
116+
return;
117+
}
118+
CSEAnimation.disableAnimations();
119+
for (const animationComponent of this.animationComponents) {
120+
animationComponent.animate();
121+
}
122+
}
123+
}

src/features/envVisualizer/EnvVisualizerLayout.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { PrimitiveValue } from './components/values/PrimitiveValue';
2222
import { UnassignedValue } from './components/values/UnassignedValue';
2323
import { Value } from './components/values/Value';
2424
import EnvVisualizer from './EnvVisualizer';
25+
import { CSEAnimation } from './EnvVisualizerAnimation';
2526
import { Config, ShapeDefaultProps } from './EnvVisualizerConfig';
2627
import {
2728
CompactReferenceType,
@@ -82,6 +83,9 @@ export class Layout {
8283
static controlComponent: ControlStack;
8384
static stashComponent: StashStack;
8485

86+
static previousControlComponent: ControlStack;
87+
static previousStashComponent: StashStack;
88+
8589
/** memoized values */
8690
static values = new Map<Data, Value>();
8791
static compactValues = new Map<Data, CompactValue>();
@@ -156,6 +160,7 @@ export class Layout {
156160
});
157161
Layout.compactValues.clear();
158162
Layout.key = 0;
163+
159164
// deep copy so we don't mutate the context
160165
Layout.environmentTree = deepCopyTree(envTree);
161166
Layout.globalEnvNode = Layout.environmentTree.root;
@@ -212,9 +217,13 @@ export class Layout {
212217
this.grid.width() + Config.CanvasPaddingX * 2
213218
);
214219
}
220+
// initialise animations
221+
CSEAnimation.updateAnimation();
215222
}
216223

217224
static initializeControlStash() {
225+
Layout.previousControlComponent = Layout.controlComponent;
226+
Layout.previousStashComponent = Layout.stashComponent;
218227
this.controlComponent = new ControlStack(this.control);
219228
this.stashComponent = new StashStack(this.stash);
220229
}
@@ -581,6 +590,9 @@ export class Layout {
581590
{EnvVisualizer.getCompactLayout() &&
582591
EnvVisualizer.getControlStash() &&
583592
Layout.stashComponent.draw()}
593+
{EnvVisualizer.getCompactLayout() &&
594+
EnvVisualizer.getControlStash() &&
595+
CSEAnimation.animationComponents.map(c => c.draw())}
584596
</Layer>
585597
</Stage>
586598
</div>

src/features/envVisualizer/EnvVisualizerUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -655,7 +655,7 @@ export function getStashItemComponent(stashItem: StashValue, stackHeight: number
655655
return binding.data?.toString() === stashItem.toString();
656656
}
657657
return false;
658-
})?.value as FnValue | GlobalFnValue;
658+
})?.value as unknown as FnValue | GlobalFnValue;
659659
if (fn) return new StashItemComponent(stashItem, stackHeight, index, fn);
660660
} else {
661661
const ar: ArrayValue | undefined = frame.bindings.find(binding => {

0 commit comments

Comments
 (0)