Pause animation when FPS gets too low

Woof, the "Swirl of Power Effect" item tanks my CPU waaay too much

(I bet it's those 7000x7000 PNGs lolol 😬)

Anyway, before thinking about optimizing specific issues, I'm just adding this emergency switch: if we detect FPS < 2 on any layer, we just pause the whole outfit, until the user decides to unpause.
This commit is contained in:
Emi Matchu 2021-06-16 16:26:24 -07:00
parent 307ab932c8
commit bb5ec56752
2 changed files with 44 additions and 5 deletions

View file

@ -10,6 +10,7 @@ function OutfitMovieLayer({
height, height,
isPaused = false, isPaused = false,
onLoad = null, onLoad = null,
onLowFps = null,
}) { }) {
const [stage, setStage] = React.useState(null); const [stage, setStage] = React.useState(null);
const [library, setLibrary] = React.useState(null); const [library, setLibrary] = React.useState(null);
@ -165,6 +166,8 @@ function OutfitMovieLayer({
return; return;
} }
const targetFps = library.properties.fps;
let lastFpsLoggedAtInMs = performance.now(); let lastFpsLoggedAtInMs = performance.now();
let numFramesSinceLastLogged = 0; let numFramesSinceLastLogged = 0;
const intervalId = setInterval(() => { const intervalId = setInterval(() => {
@ -181,16 +184,20 @@ function OutfitMovieLayer({
const roundedFps = Math.round(fps * 100) / 100; const roundedFps = Math.round(fps * 100) / 100;
console.debug( console.debug(
`[OutfitMovieLayer] FPS: ${roundedFps} (Target: ${library.properties.fps}, ${numFramesSinceLastLogged}, ${timeSinceLastFpsLoggedAtInSec}) (${libraryUrl})` `[OutfitMovieLayer] FPS: ${roundedFps} (Target: ${targetFps}) (${libraryUrl})`
); );
if (onLowFps && fps < 2) {
onLowFps(fps);
}
lastFpsLoggedAtInMs = now; lastFpsLoggedAtInMs = now;
numFramesSinceLastLogged = 0; numFramesSinceLastLogged = 0;
} }
}, 1000 / library.properties.fps); }, 1000 / targetFps);
return () => clearInterval(intervalId); return () => clearInterval(intervalId);
}, [libraryUrl, stage, updateStage, movieClip, library, isPaused]); }, [libraryUrl, stage, updateStage, movieClip, library, isPaused, onLowFps]);
// This effect keeps the `movieClip` scaled correctly, based on the canvas // This effect keeps the `movieClip` scaled correctly, based on the canvas
// size and the `library`'s natural size declaration. (If the canvas size // size and the `library`'s natural size declaration. (If the canvas size

View file

@ -1,5 +1,12 @@
import React from "react"; import React from "react";
import { Box, DarkMode, Flex, Text, useColorModeValue } from "@chakra-ui/react"; import {
Box,
DarkMode,
Flex,
Text,
useColorModeValue,
useToast,
} from "@chakra-ui/react";
import LRU from "lru-cache"; import LRU from "lru-cache";
import { WarningIcon } from "@chakra-ui/icons"; import { WarningIcon } from "@chakra-ui/icons";
import { ClassNames } from "@emotion/react"; import { ClassNames } from "@emotion/react";
@ -54,6 +61,9 @@ export function useOutfitPreview({
onChangeHasAnimations = null, onChangeHasAnimations = null,
...props ...props
}) { }) {
const [isPaused, setIsPaused] = useLocalStorage("DTIOutfitIsPaused", true);
const toast = useToast();
const appearance = useOutfitAppearance({ const appearance = useOutfitAppearance({
speciesId, speciesId,
colorId, colorId,
@ -70,7 +80,26 @@ export function useOutfitPreview({
layersHaveAnimations, layersHaveAnimations,
} = usePreloadLayers(visibleLayers); } = usePreloadLayers(visibleLayers);
const [isPaused] = useLocalStorage("DTIOutfitIsPaused", true); const onLowFps = React.useCallback(
(fps) => {
setIsPaused(true);
console.warn(`[OutfitPreview] Pausing due to low FPS: ${fps}`);
if (!toast.isActive("low-fps-warning")) {
toast({
id: "low-fps-warning",
status: "warning",
title: "Sorry, the animation was lagging, so we paused it! 😖",
description:
"We do this to help make sure your machine doesn't lag too much! " +
"You can unpause the preview to try again.",
duration: null,
isClosable: true,
});
}
},
[setIsPaused, toast]
);
React.useEffect(() => { React.useEffect(() => {
if (onChangeHasAnimations) { if (onChangeHasAnimations) {
@ -100,6 +129,7 @@ export function useOutfitPreview({
loadingDelayMs={loadingDelayMs} loadingDelayMs={loadingDelayMs}
spinnerVariant={spinnerVariant} spinnerVariant={spinnerVariant}
onChangeHasAnimations={onChangeHasAnimations} onChangeHasAnimations={onChangeHasAnimations}
onLowFps={onLowFps}
doTransitions doTransitions
isPaused={isPaused} isPaused={isPaused}
{...props} {...props}
@ -122,6 +152,7 @@ export function OutfitLayers({
spinnerVariant = "overlay", spinnerVariant = "overlay",
doTransitions = false, doTransitions = false,
isPaused = true, isPaused = true,
onLowFps = null,
...props ...props
}) { }) {
const [hiResMode] = useLocalStorage("DTIHiResMode", false); const [hiResMode] = useLocalStorage("DTIHiResMode", false);
@ -228,6 +259,7 @@ export function OutfitLayers({
width={canvasSize} width={canvasSize}
height={canvasSize} height={canvasSize}
isPaused={isPaused} isPaused={isPaused}
onLowFps={onLowFps}
/> />
) : ( ) : (
<Box <Box