Sweep: Improve performance
Romaixn opened this issue Β· comments
I have some issue of performance when confetti component is triggered by the useGameStore
created by zustand
. When the score increase, there is a little delay before the goal and confetti appears.
Here's the PR! #24.
β‘ Sweep Free Trial: I used GPT-4 to create this ticket. You have 5 GPT-4 tickets left. For more GPT-4 tickets, visit our payment portal.To get Sweep to recreate this ticket, leave a comment prefixed with "sweep:" or edit the issue.
Step 1: π Code Search
I found the following snippets in your repository. I will now analyze these snippets and come up with a plan.
Some code snippets I looked at (click to expand). If some file is missing from here, you can mention the path in the ticket description.
mini-basket/src/Components/Table.jsx
Lines 1 to 190 in 12230b4
import {useGLTF} from "@react-three/drei"; | |
import {MeshTransmissionMaterial} from "@react-three/drei" | |
import {CuboidCollider, RigidBody, vec3} from '@react-three/rapier' | |
import {useEffect, useRef, useState} from "react"; | |
import useGame from '../stores/useGame' | |
import {useControls} from "leva"; | |
export default function Table(props) { | |
const {nodes, materials} = useGLTF("/models/table.gltf"); | |
const controlA = useRef(null) | |
const controlB = useRef(null) | |
const thrusterA = useRef(null) | |
const thrusterB = useRef(null) | |
const [isScored, setIsScored] = useState(false) | |
const {tableRestitution, tableFriction, glassRestitution, glassFriction} = useControls('table', { | |
tableRestitution: {label: 'Table Restitution', value: 0.6, min: 0, max: 1, step: 0.1}, | |
tableFriction: {label: 'Table Friction', value: 0, min: 0, max: 10}, | |
glassRestitution: {label: 'Glass Restitution', value: 0.2, min: 0, max: 1, step: 0.1}, | |
glassFriction: {label: 'Glass Friction', value: 0, min: 0, max: 10}, | |
}, {collapsed: true}) | |
const increaseScore = useGame((state) => state.increment) | |
const goal = () => { | |
if(!isScored) { | |
setIsScored(true) | |
increaseScore() | |
useGame.setState({ isScored: true }) | |
} | |
} | |
const clickUp = (controlRef, isControlAPushed) => { | |
if (controlRef.current) { | |
useGame.setState({ [isControlAPushed ? 'isControlAPushed' : 'isControlBPushed']: false }) | |
controlRef.current.position.y = 0.128 | |
} | |
} | |
const clickDown = (controlRef, isControlAPushed) => { | |
if (controlRef.current) { | |
useGame.setState({ [isControlAPushed ? 'isControlAPushed' : 'isControlBPushed']: true }) | |
controlRef.current.position.y = 0.128 - 0.1 | |
} | |
} | |
useEffect(() => { | |
const upY = 0.5 | |
const unsuscribeA = useGame.subscribe( | |
(state) => state.isControlAPushed, | |
(isControlAPushed) => { | |
if (thrusterA.current) { | |
const position = vec3(thrusterA.current.translation()) | |
if (isControlAPushed) { | |
thrusterA.current.setNextKinematicTranslation({ | |
x: position.x, | |
y: position.y + upY, | |
z: position.z | |
}) | |
} else { | |
thrusterA.current.setNextKinematicTranslation({ | |
x: position.x, | |
y: position.y - upY, | |
z: position.z | |
}) | |
} | |
} | |
} | |
) | |
const unsuscribeB = useGame.subscribe( | |
(state) => state.isControlBPushed, | |
(isControlBPushed) => { | |
if (thrusterB.current) { | |
const position = vec3(thrusterB.current.translation()) | |
if (isControlBPushed) { | |
thrusterB.current.setNextKinematicTranslation({ | |
x: position.x, | |
y: position.y + upY, | |
z: position.z | |
}) | |
} else { | |
thrusterB.current.setNextKinematicTranslation({ | |
x: position.x, | |
y: position.y - upY, | |
z: position.z | |
}) | |
} | |
} | |
} | |
) | |
const handleKeyDown = (event) => { | |
if (event.key === 'a') { | |
clickDown(controlA, true) | |
} else if (event.key === 'd') { | |
clickDown(controlB, false) | |
} | |
} | |
const handleKeyUp = (event) => { | |
if (event.key === 'a') { | |
clickUp(controlA, true) | |
} else if (event.key === 'd') { | |
clickUp(controlB, false) | |
} | |
} | |
window.addEventListener('keydown', handleKeyDown) | |
window.addEventListener('keyup', handleKeyUp) | |
return () => { | |
window.removeEventListener('keydown', handleKeyDown) | |
window.removeEventListener('keyup', handleKeyUp) | |
unsuscribeA() | |
unsuscribeB() | |
} | |
}, []) | |
return ( | |
<group {...props} dispose={null} rotation={[0, -Math.PI / 2, 0]}> | |
<RigidBody type="fixed" colliders="trimesh" restitution={tableRestitution} friction={tableFriction}> | |
<mesh | |
castShadow | |
receiveShadow | |
geometry={nodes.Table.geometry} | |
material={materials.Wood} | |
material-color="#DEB887" | |
material-envMapIntensity={0.5} | |
position={[0, 0.07, 0]} | |
/> | |
<CuboidCollider | |
args={[0, 2, 1.5]} | |
position={[1.5, 1.5, 0]} | |
sensor | |
onIntersectionExit={() => { | |
setIsScored(false) | |
}} | |
/> | |
</RigidBody> | |
<RigidBody type="fixed" colliders="trimesh" restitution={glassRestitution} friction={glassFriction}> | |
<mesh | |
castShadow | |
receiveShadow | |
geometry={nodes.Glass.geometry} | |
position={[0.497, 1.54, 0.005]} | |
> | |
<MeshTransmissionMaterial anisotropy={0.1} chromaticAberration={0.04} distortionScale={0} | |
temporalDistortion={0}/> | |
</mesh> | |
</RigidBody> | |
<mesh | |
castShadow | |
receiveShadow | |
geometry={nodes.Controls.geometry} | |
material={materials.Wood} | |
position={[4.135, 0.092, -0.003]} | |
/> | |
<mesh | |
ref={controlA} | |
castShadow | |
receiveShadow | |
geometry={nodes.Control_A.geometry} | |
material={materials.Red} | |
position={[4.184, 0.128, 0.744]} | |
onPointerUp={() => clickUp(controlA, true)} | |
onPointerDown={() => clickDown(controlA, true)} | |
> | |
<mesh | |
castShadow | |
receiveShadow | |
geometry={nodes.Control_A_Text.geometry} | |
material={materials.White} | |
position={[0.237, 0.046, 0.21]} | |
rotation={[Math.PI / 2, 1.179, -Math.PI / 2]} | |
/> | |
</mesh> | |
<mesh | |
ref={controlB} | |
castShadow | |
receiveShadow | |
geometry={nodes.Control_B.geometry} | |
material={materials.Green} | |
position={[4.183, 0.128, -0.754]} | |
onPointerUp={() => clickUp(controlB, false)} | |
onPointerDown={() => clickDown(controlB, false)} |
mini-basket/src/Experience.jsx
Lines 1 to 88 in 12230b4
import { Suspense, useEffect, useRef, useState } from "react" | |
import { useFrame } from "@react-three/fiber" | |
import { PresentationControls, Center, Environment } from "@react-three/drei" | |
import { Physics } from '@react-three/rapier' | |
import { easing } from 'maath' | |
import { Perf } from "r3f-perf" | |
import { useControls } from 'leva' | |
import Lights from "./Lights" | |
import Level from "./Level" | |
import Confetti from "./Components/Confetti.jsx"; | |
import useGame from "./stores/useGame.js"; | |
const Experience = () => { | |
const { perfVisible, debugPhysics } = useControls('debug', { | |
perfVisible: { label: 'Performance', value: false }, | |
debugPhysics: { label: 'Physics', value: false }, | |
}) | |
const [isExploding, setIsExploding] = useState() | |
useEffect(() => { | |
const unsuscribeIsScored = useGame.subscribe( | |
(state) => state.score, | |
(score) => { | |
if(score) { | |
setIsExploding(true) | |
setTimeout(() => { | |
setIsExploding(false) | |
}, 2000) | |
} | |
} | |
) | |
return () => { | |
unsuscribeIsScored() | |
} | |
}, []) | |
return <> | |
<color attach="background" args={["#ddc28d"]} /> | |
{ perfVisible && <Perf position="top-left" /> } | |
<Environment preset="city" /> | |
<Lights /> | |
<PresentationControls | |
global | |
cursor={false} | |
// rotation={[0, -Math.PI / 8, 0]} | |
azimuth={[-Math.PI / 2, Math.PI / 2]} | |
> | |
<group> | |
<Suspense fallback={<Fallback />}> | |
<Confetti isExploding={isExploding} amount={200} rate={2} areaWidth={5} areaHeight={3} fallingHeight={6} /> | |
<Physics debug={debugPhysics}> | |
<Center> | |
<Level /> | |
</Center> | |
</Physics> | |
<Zoom /> | |
</Suspense> | |
</group> | |
</PresentationControls> | |
</> | |
} | |
const Fallback = () => { | |
const ref = useRef(null) | |
useFrame((state) => (ref.current.position.x = Math.sin(state.clock.elapsedTime * 2))) | |
return ( | |
<mesh ref={ref}> | |
<sphereGeometry args={[0.15, 64, 64]} /> | |
<meshBasicMaterial color="#556" /> | |
</mesh> | |
) | |
} | |
const Zoom = () => { | |
useFrame((state, delta) => { | |
easing.damp3(state.camera.position, [0, 1, 8], 1, delta) | |
state.camera.lookAt(0, 0, 0) | |
}) | |
return <></> | |
} | |
export default Experience |
mini-basket/src/stores/useGame.js
Lines 1 to 18 in 12230b4
import { create } from 'zustand' | |
import { subscribeWithSelector } from 'zustand/middleware' | |
export default create(subscribeWithSelector((set) => { | |
return { | |
/** | |
* Score | |
*/ | |
score: 0, | |
increment: () => set((state) => ({ score: state.score + 1 })), | |
/** | |
* Controls | |
*/ | |
isControlAPushed: false, | |
isControlBPushed: false, | |
} | |
})) |
mini-basket/src/Components/Confetti.jsx
Lines 1 to 140 in 12230b4
// CONFETTI COMPONENT BY ANDERSON MANCINI AND ROMAIN HERAULT | |
// Based on: https://github.com/JamesChan21/threejs-confetti | |
// Based on: https://github.com/daniel-lundin/dom-confetti | |
import React, { useRef, useState, useEffect } from 'react' | |
import { useFrame } from '@react-three/fiber' | |
import * as THREE from 'three' | |
/** | |
* @param {Object} options | |
* @param {Boolean | undefined} options.isExploding Enable exploding | |
* @param {Number | undefined} options.amount The amount of particles | |
* @param {Number | undefined} options.rate Increases or decreases the frequency for particles. Don't set it too high. | |
* @param {Number | undefined} options.radius The radius of each explosion. | |
* @param {Number | undefined} options.areaWidth The qrea width for explosion. | |
* @param {Number | undefined} options.areaHeight The qrea height for explosion. | |
* @param {Number | undefined} options.fallingHeight Height for the particles to fall from | |
* @param {Number | undefined} options.fallingSpeed The speed of particles | |
* @param {(Number)[] | undefined} options.colors Array of Hex color codes for particles. Example: [0x0000ff, 0xff0000, 0xffff00] | |
* @param {Number | String | undefined} options.duration Duration of the particles in Milliseconds. Set as 'forever' string for infinity explosion | |
* @param {Boolean | undefined} options.enableShadows Enable particle shadows. Set false for better performance. | |
* | |
*/ | |
export default function ExplosionConfetti( | |
{ | |
isExploding = false, | |
amount = 100, | |
rate = 3, | |
radius = 15, | |
areaWidth = 3, | |
areaHeight = 1, | |
fallingHeight = 10, | |
fallingSpeed = 8, | |
colors = ["#a864fd", "#29cdff", "#78ff44", "#ff718d", "#fdff6a"], | |
duration = 10000, | |
enableShadows = false | |
}, | |
props | |
) { | |
const groupRef = useRef() | |
const [booms, setBooms] = useState([]) | |
rate = rate / 100 | |
const geometry = new THREE.PlaneGeometry(0.03, 0.03, 1, 1) | |
function explode() { | |
const boom = new THREE.Object3D() | |
boom.life = Math.random() * 5 + 5 | |
boom.position.x = -(areaWidth / 2) + areaWidth * Math.random() | |
boom.position.y = fallingHeight + areaHeight - fallingSpeed | |
boom.position.z = -(areaWidth / 2) + areaWidth * Math.random() | |
groupRef.current.add(boom) | |
booms.push(boom) | |
for (let i = 0; i < amount; i++) { | |
const material = new THREE.MeshBasicMaterial({ | |
color: colors[Math.floor(Math.random() * colors.length)], | |
side: THREE.DoubleSide | |
}) | |
const particle = new THREE.Mesh(geometry, material) | |
particle.castShadow = enableShadows | |
boom.add(particle) | |
particle.life = 1 | |
particle.destination = {} | |
particle.destination.x = (Math.random() - 0.5) * (radius * 2) * Math.random() | |
particle.destination.y = (Math.random() - 0.5) * (radius * 2) * Math.random() | |
particle.destination.z = (Math.random() - 0.5) * (radius * 2) * Math.random() | |
particle.rotation.x = Math.random() * 360 | |
particle.rotation.y = Math.random() * 360 | |
particle.rotation.z = Math.random() * 360 | |
const size = Math.random() * 2 + 1 | |
particle.scale.set(size, size, size) | |
particle.rotateSpeedX = Math.random() * 0.8 - 0.4 | |
particle.rotateSpeedY = Math.random() * 0.8 - 0.4 | |
particle.rotateSpeedZ = Math.random() * 0.8 - 0.4 | |
} | |
boom.dispose = function () { | |
for (let i = 0; i < boom.children.length; i++) { | |
const particle = boom.children[i] | |
particle.material.dispose() | |
particle.geometry.dispose() | |
boom.remove(particle) | |
} | |
groupRef.current.remove(boom) | |
} | |
} | |
useFrame(() => { | |
if (isExploding && Math.random() < rate) explode() | |
let particleAmount = 0 | |
for (let i = 0; i < booms.length; i++) { | |
const boom = booms[i] | |
for (let k = 0; k < boom.children.length; k++) { | |
let particle = boom.children[k] | |
particle.destination.y -= THREE.MathUtils.randFloat(0.1, 0.3) | |
particle.life -= THREE.MathUtils.randFloat(0.005, 0.01) | |
const speedX = (particle.destination.x - particle.position.x) / 200 | |
const speedY = (particle.destination.y - particle.position.y) / 200 | |
const speedZ = (particle.destination.z - particle.position.z) / 200 | |
particle.position.x += speedX | |
particle.position.y += speedY | |
particle.position.z += speedZ | |
particle.rotation.y += particle.rotateSpeedY | |
particle.rotation.x += particle.rotateSpeedX | |
particle.rotation.z += particle.rotateSpeedZ | |
particle.material.opacity -= THREE.MathUtils.randFloat(0.005, 0.01) | |
if (particle.position.y < -fallingHeight) { | |
particle.material.dispose() | |
particle.geometry.dispose() | |
boom.remove(particle) | |
particle = null | |
} | |
} | |
if (boom.children.length <= 0) { | |
boom.dispose() | |
setBooms(booms.filter((b) => b !== boom)) | |
} | |
particleAmount += boom.children.length | |
} | |
}) | |
return <mesh ref={groupRef} position={[0, 2, -8]} /> | |
} |
mini-basket/public/models/table.gltf
Lines 1029 to 1149 in 12230b4
}, | |
{ | |
"componentType":5126, | |
"count":429, | |
"type":"VEC2" | |
}, | |
{ | |
"componentType":5123, | |
"count":2304, | |
"type":"SCALAR" | |
}, | |
{ | |
"componentType":5126, | |
"count":156, | |
"max":[ | |
2.994999885559082, | |
1.6636722087860107, | |
1.4550000429153442 | |
], | |
"min":[ | |
-2.994999885559082, | |
-1.1749999523162842, | |
-1.4550000429153442 | |
], | |
"type":"VEC3" | |
}, | |
{ | |
"componentType":5126, | |
"count":156, | |
"type":"VEC3" | |
}, | |
{ | |
"componentType":5126, | |
"count":156, | |
"type":"VEC2" | |
}, | |
{ | |
"componentType":5123, | |
"count":438, | |
"type":"SCALAR" | |
} | |
], | |
"bufferViews":[ | |
{ | |
"buffer":0, | |
"byteLength":998, | |
"byteOffset":0 | |
}, | |
{ | |
"buffer":0, | |
"byteLength":1055, | |
"byteOffset":1000 | |
}, | |
{ | |
"buffer":0, | |
"byteLength":265, | |
"byteOffset":2056 | |
}, | |
{ | |
"buffer":0, | |
"byteLength":3438, | |
"byteOffset":2324 | |
}, | |
{ | |
"buffer":0, | |
"byteLength":596, | |
"byteOffset":5764 | |
}, | |
{ | |
"buffer":0, | |
"byteLength":3394, | |
"byteOffset":6360 | |
}, | |
{ | |
"buffer":0, | |
"byteLength":351, | |
"byteOffset":9756 | |
}, | |
{ | |
"buffer":0, | |
"byteLength":351, | |
"byteOffset":10108 | |
}, | |
{ | |
"buffer":0, | |
"byteLength":176, | |
"byteOffset":10460 | |
}, | |
{ | |
"buffer":0, | |
"byteLength":2530, | |
"byteOffset":10636 | |
}, | |
{ | |
"buffer":0, | |
"byteLength":824, | |
"byteOffset":13168 | |
}, | |
{ | |
"buffer":0, | |
"byteLength":1156, | |
"byteOffset":13992 | |
}, | |
{ | |
"buffer":0, | |
"byteLength":3015, | |
"byteOffset":15148 | |
}, | |
{ | |
"buffer":0, | |
"byteLength":1211, | |
"byteOffset":18164 | |
} | |
], | |
"buffers":[ | |
{ | |
"byteLength":19376, | |
"uri":"data:application/octet-stream;base64,RFJBQ08CAgEBAAAAeJIBApIBEQIREUBKAS/f931f1/bN33X9uqaquq7ruy5VpW+8T9sF1cf6L82jWqVVVaVV+lSrPN7QZW+WAP8CZkD/AlpQ/wJaUAP/AAAAAAABAAABAAkDAAACAQEJAwABAwEDCQIAAgIBAQEBBtpYySb/f+QRAfdcV7j/d4hc/381A8kBAyz//3tcUyz/N4j/////Syz/////cxEB/xNciP///////////x9cXJssEywHuLjDLP////8XuIjnuLj/kywziE+I/////////////8Ms//////////////8LXCz/x1z//////////////////////z8szyz/////q1wDLP////9biIj/////////////S1z//////////weIXP9/LP////////////dc////////////6yz/fyz//98sXP//////////////////////////////c1z///////////9fLP//////////////lyyLAXxf0++KKMJ4EEXePWMibLzFGog4MJcaaK1TicB3+OmIVqW22JyUOOMqPGKpuRUNM6tuDMJa3WdToPzpQFoYzQF0e5GFJb+hBmskqpld72k0cuICVA8iwa+pBfNYBP1kPu/bIRP47fL8Qrs6sASWgMwvATtJqtw/F46HYL/vWQGZkmHXDQ047ffaDosAAAAA/z8AAGfIIsC/CvC+AADAvxEQw0AOAAMBAQb0B40fEQF/RAuIi8xjRAtEA4gT3QE/RBuILyUCU0QHmQEvRFclAi9E/69E/5tE/39EU4j/C0STzP9HRERrVQE7RB+IL90Bi8wDRERTeQMvRF9VAUQPzBtET0QHiC9EPxEBE1UBE0RLEQFsuH4zgJ/5liFhqPoHtojxoy7eQvvqnuy/KYkmAQHPdE/0kRBidejtXkOOqnKLTqupPDMvnfUvTFcmndlZ6XIlWTMZv5Y/77ucKl5Pi3tp5u1Ibcx6GAmbKDtObAj4CrGh8Uz1yRrP4eXCRnyD/wMAAP8BAAAKAQEBAQaAIAUrA8z/64iI6xEBEQH/iIj/34iIR5kBiP/vRP//e8wRAYiI///////TRP///w+ZAf+rRET/////e0RLRP/fRP/ziAeI/+eIiJkBEQEDRP///////x9E/4dE///////bRP////////////////////9jRP/3RE54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/wMAAP8BAAAKAQEBAQSAIAk8uQHdAf///+8UFP///////6MU/////////////////1MU////////////////////////////////////////////////////9xQxQ4UlGpIs/h4t9Kz7INBb/JOPXtHz+MzMpK/QAEeuRRfD+yf5TU40rSzv8uc/+/cvgQAAAAD/DwAAAAAAAAAAAAAAAIA/DABEUkFDTwICAQEAAACcAZIBApIBJAA337qu67qu6/Z3Xdd1Xdc1//Mnf/7kz58kf/4kefY/f/LnT/78SfLnT5Jn77qu67qu77qu67quC/8CVUD/AiZM/wImTAP/AAAAAAABAAABAAkDAAACAQEJAwABAwEDCQIAAgIBAQEADwOZD2gLQQqlAQPUzQdpEB0EaJEGNx738mjEWSpCTSg97ImvplwdZwfETA33aNyJF7s4ID/6qkchhBTQLRwRQmmFq5xpXJ5WrDqbP4UBAADg4gsAjiMAAAA0jgAAAoCeMAQAYDAEIBEE4BEEEPUAsh7AlgMIZwDjDAAgw06gAwDHEQAAABpHAAAAsKM5EAFIQzAARhpASAPYdACZDyDyAXgIASQiAAA0jgAAAOBdigAAPBkCALwNEwCCPAUAlNYVAFpdIwBA2IUA0n8GgFX5CoDJNhQAcOQ0AE91CQCwCR4Au6s5ANCICQJwyLIAYA+CCABfEYYAAIBvAeCgWwCoB0EEAClWCwAzYoIAwBMqAL4meADw6/YAeCOnASDtQABUla8AUGh3AfBgFwIA4kkAmLSuAIDmmgGg2zABALLL4x4AAABM+wAA3mUIAMCTIgDA2y8BIEhUAEDpXAGg1TYCAIRbCCA9aABYda8AmKxDAQDHTQPwlJcAAFvgAbA7mwMAjZQgAIcwCwD2EIgA8BViCAAA9BYADrwFgHoIRACQgrUAMCMlCAA8ogLga4EHAL9wD4A3bhoA0hAEQFX3CgCFehcAD24hACCiBIBJ5woAaIYJALr9EgAguxQBAAAAoLQXACxvLSaAvWoUAKwogJcE8IIAZgvATAGoGQC1AQAQUXylAHvZGADrCeAdAbwfgLkCME8A6gVAXQAAAAAA/z8AABSuP8BmZpa/cT26vxSuv0AOAAMBAAsD5SsH1Ato1A0SGJCBhv4IZ/Oud0h+sUtSccGg8JqjBUnrgQD+uzsQvzv2J0D9CmhfAt6HgPgdQP4GmJ8B52MA+hdgErYOxB+/Aw/BewH6oH/B+RhgfgaQvwHid4D3IaB9CVC/AtifAL+DAAAAAAAAAAAA/P8PAAAAAAAAAABATz2jfwEXoI/B+VyAPhmYjwbl04H4fAA+G5DPcu4F6IP+BedjAPoXYH4GlK8B4ncA+B9A/gYA/wMAAP8BAAAKAQEBAA0D3RKFBD0BB9SRBj0R7QRokQbhAjcX21+kWJ+uLvroOunLMo7eyAr0b6b4zT8Uq/EnNFDAAFaryLeZJTNxMe8JeUaPtRyjIddA85+GAACAsPz/KwFAGAEgEBHDMGgD8H8BCMv+vxIA6gaARfwPgP6jDcAwDBHBKwHgTwI0B+goy4q3VngSs+Jf09Lullpqr5py9n3tTBhGtiAwvpgIwEvofsBokbuEIagah2/WX4mNC87SqvYuIyJL3VrYJ+co/RwAANwPAALoTwI0B+gmy4q3VvgRs+Jf09runlpqr5py+H3tTBxGuiA0vqgI4EvofsBokbuEIagaZ2/WX6mNG87aqgYvKyJL3VrYaeco/Zw/ANz//5fxf8wAIAAAQCkMw4gAAGkCYGX8HwAAOAoIDIIACAAAAAD/DwAAAADAPgAAAAAAAIA/DAA=" | |
} | |
] | |
} |
Step 2: π§ Snippet Analysis
From looking through the relevant snippets, I decided to make the following modifications:
File Path | Proposed Changes |
---|---|
src/Experience.jsx |
In the useEffect hook that subscribes to the score state from the useGameStore , add a conditional statement to only trigger the useEffect hook when the score increases by a certain amount, or when a goal is scored. This will prevent unnecessary re-renders of the confetti component. |
src/Components/Confetti.jsx |
Optimize the rendering of the confetti component by only rendering it when it is needed, and then removing it from the DOM when it is not. This can be achieved by using the useEffect hook to add and remove the confetti component from the DOM based on certain conditions. |
Step 3: π Planning
I have created a plan for writing the pull request. I am now working my plan and coding the required changes to address this issue. Here is the planned pull request:
Improve performance of confetti component
sweep/optimize-confetti-performance
Description
This PR aims to improve the performance of the confetti component in the mini-basket project. Currently, there is a delay when the confetti component is triggered by the
useGameStore
created byzustand
when the score increases. This delay is caused by unnecessary re-renders of the confetti component.To address this issue, the following changes have been made:
- In the
src/Experience.jsx
file, theuseEffect
hook that subscribes to thescore
state from theuseGameStore
has been optimized to only trigger when the score increases by a certain amount or when a goal is scored. This prevents unnecessary re-renders of the confetti component.- In the
src/Components/Confetti.jsx
file, the rendering of the confetti component has been optimized by using theuseEffect
hook to add and remove the confetti component from the DOM based on certain conditions. This ensures that the confetti component is only rendered when it is needed, and then removed from the DOM when it is not.Summary of Changes
- Added conditional statement in
src/Experience.jsx
to trigger theuseEffect
hook only when the score increases by a certain amount or when a goal is scored.- Optimized rendering of the confetti component in
src/Components/Confetti.jsx
by using theuseEffect
hook to add and remove the confetti component from the DOM based on certain conditions.
Step 4: β¨οΈ Coding
File | Instructions | Progress |
---|---|---|
src/Experience.jsx |
In the useEffect hook that subscribes to the score state from the useGameStore , add a conditional statement to only trigger the useEffect hook when the score increases by a certain amount, or when a goal is scored. This will prevent unnecessary re-renders of the confetti component. |
β
Done with commit 43d1057 |
src/Components/Confetti.jsx |
Optimize the rendering of the confetti component by only rendering it when it is needed, and then removing it from the DOM when it is not. This can be achieved by using the useEffect hook to add and remove the confetti component from the DOM based on certain conditions. |
β
Done with commit d06b6e9 |
Step 5: π Code Review
Here are the my self-reviews of my changes at sweep/optimize-confetti-performance
.
Here is the 1st review
Hi there, great job on your first pull request! I've noticed a few areas that might need some attention:
In
src/Components/Confetti.jsx
:
- The change from
useFrame
touseEffect
on lines 92-130 significantly alters the logic of the confetti explosion effect.useEffect
doesn't provide the continuous update functionality thatuseFrame
does. You might want to reconsider this change if the continuous update is necessary for the desired effect.- The disposal of the particles' material and geometry has been removed on lines 92-130. This could potentially lead to memory leaks if the particles are not properly disposed of elsewhere. Please ensure that this is handled correctly.
- The
particleAmount
variable has been removed on lines 92-130. If this variable is used elsewhere in the code, this could cause issues. Please check this.- The
groupRef
in thereturn
statement on line 132 is not defined in the provided code snippet. If it's not defined elsewhere in the file, this could cause a reference error.In
src/Experience.jsx
:
- The new code on lines 22-31 only triggers an explosion if the new score is greater than the previous score. This changes the behavior of the code. If the score can decrease, this new code will not trigger an explosion in that case. Please verify if this is the intended behavior.
- The
Zoom
component on lines 41-42 is empty and does not render anything. If this is not intentional, it could be considered an unimplemented section. Please check this.Keep up the good work!
I finished incorporating these changes.
To recreate the pull request, leave a comment prefixed with "sweep:" or edit the issue.
Join Our Discord