Skip to content
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

playback speed control #445

Open
wants to merge 6 commits into
base: feature/prefetching
Choose a base branch
from

Conversation

interim17
Copy link
Contributor

@interim17 interim17 commented Jan 31, 2025

Time Estimate or Size

small

Problem

Users want to control playback speed
Advances #424
Advances #446

Continuation of prefetching work and pointing at that feature branch pending review.

This feature still needs UX input and was not prioritized, but basically was building it was basically part of debugging the prefetching.

Website implementation pending UX input.

Solution

Added a viewer prop to let the front end set playback speed in frames per second.
Added shouldFrameAdvance and advanceFrame methods to viewport to control playback speed
Refactored the animate loop by pulling some variables out, and splitting it into smaller functions that make explicit what each part of the loop is doing.
Added front end controls in test-bed to preview feature.
Added PlaybackControls component to test-bed to hold previously existing controls, and new speed control.

  • New feature (non-breaking change which adds functionality)

Change summary:

  • Tidy, well formulated commit message
  • Another great commit message
  • Something else I/we did

Steps to Verify:

  1. Try the speed buttons!

Comment on lines +21 to +22
export const DEFAULT_PLAYBACK_SPEED_INDEX = 3;
export const PLAYBACK_SPEEDS: number[] = [5, 10, 15, 20, 30, 45, 60];
Copy link
Contributor Author

@interim17 interim17 Feb 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhat arbitrary set of options any number can be provided to the prop, but the playback speed will be throttled by the animation frame rate to 60 at the highest.

Could use a slider for scrubbing speed, or whatever UX wants in production.

Comment on lines +4 to +22
interface PlaybackControlsProps {
simulariumController: any;
totalSteps: number;
timeStep: number;
firstFrameTime: number;
currentFrame: number;
setSpeed: (speed: number) => void;
currentSpeed: number;
}

const PlaybackControls: React.FC<PlaybackControlsProps> = ({
simulariumController,
totalSteps,
timeStep,
firstFrameTime,
currentFrame,
setSpeed,
currentSpeed,
}) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This component includes the new speed controls, and the other existing playback controls, small refactor as part of cleaning up test-bed.

Comment on lines 118 to 123
private currentSimulationTime: number;
private clockTimePerRender: number;
private clockTimeSinceRender: number;
private totalClockTimeElapsed: number;
private clockTimeSinceFrameAdvance: number;
private clockTimePerFrameAdvance: number;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These will make more sense below, but as part of refactoring the animate method I pulled out variables that were scoped within that function to make them available to the whole class, and tried to name them more explicitly.

Comment on lines 707 to 730
public animate(): void {
const { simulariumController } = this.props;
this.updateAnimationTime();
if (this.clockTimeSinceRender <= this.clockTimePerRender) {
this.animationRequestID = requestAnimationFrame(this.animate);
return;
}
if (simulariumController.isChangingFile) {
this.handleFileChange(this.totalClockTimeElapsed);
return;
}
const currentFrame = simulariumController.visData.currentFrameData;
this.renderCurrentFrame(currentFrame);

if (this.shouldFrameAdvance()) {
this.advanceFrame();
}
this.stats.begin();
this.visGeometry.render(this.totalClockTimeElapsed);
if (this.recorder?.isRecording) {
this.recorder.onFrame();
}
this.stats.end();
this.lastRenderTime = Date.now();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially I was piling new code into this already somewhat hard to read function, but as I was chasing bugs I figured it might be time to factor it out on behalf of readability. It does a lot and I think having helper methods makes this easier to understand what is going on.

@interim17 interim17 marked this pull request as ready for review February 5, 2025 17:27
@interim17 interim17 requested a review from a team as a code owner February 5, 2025 17:27
@interim17 interim17 requested review from meganrm and ShrimpCryptid and removed request for a team February 5, 2025 17:27
@interim17 interim17 changed the title basic playback speed control playback speed control Feb 5, 2025
Copy link
Contributor

@ShrimpCryptid ShrimpCryptid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some non-blocking comments. I think you can get rid of a lot of private variables in viewport/index.tsx!

Comment on lines 118 to 123
private currentSimulationTime: number;
private clockTimePerRender: number;
private clockTimeSinceRender: number;
private totalClockTimeElapsed: number;
private clockTimeSinceFrameAdvance: number;
private clockTimePerFrameAdvance: number;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe needs a comment saying what units these are in, or renaming (ex clockTimePerRenderMs)

Comment on lines 678 to 680
this.clockTimeSinceRender = now - this.lastRenderTime;
this.totalClockTimeElapsed = now - this.startTime;
this.clockTimeSinceFrameAdvance += this.clockTimeSinceRender;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find it a little strange that this.clockTimeSinceRender and this.totalClockTimeElapsed are saved as private variables since they can be derived on the fly and are only used in one or two places. You could have a getClockTimeSinceLastRender() method that does the same thing, or have this method return the calculated values.

this.clockTimeSinceFrameAdvance also seems like it should just be a calculated value since it's only used in one place, and instead you could save the timestamp of the last frame advance as your private variable instead of having to add up the time deltas. (It would also be slightly more accurate since adding up deltas can be inconsistent.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really appreciate this feedback.

Did a second pass after naming methods and they also seemed strange and one off...

Did a small refactor to use local variables inside animate (as before), but also utilize the timestamp from requestAnimationFrame to keep things in sync.

@@ -402,6 +420,9 @@ class Viewport extends React.Component<
this.visGeometry.destroyGui();
}
}
if (playbackSpeed !== prevProps.playbackSpeed) {
this.clockTimePerFrameAdvance = 1000 / playbackSpeed;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need to store clockTImePerFrameAdvance or can we just calculate it off of props.playbackSpeed each time?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants