Skip to content

Commit

Permalink
changed states to refs in Tile.tsx to prevent rerenders
Browse files Browse the repository at this point in the history
  • Loading branch information
Resaki1 committed May 26, 2024
1 parent c2ebfe4 commit 49f3b9a
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 52 deletions.
8 changes: 7 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,13 @@ const App = () => {

return (
<div className="App">
<Canvas frameloop="demand" camera={{ fov: 25, near: 0.1, far: 1000, position: [6, 5, 6] }} flat shadows>
<Canvas
frameloop="demand"
camera={{ fov: 25, near: 0.1, far: 1000, position: [6, 5, 6] }}
dpr={[0.5, 2]}
flat
shadows
>
<ambientLight intensity={0.6} color={'#91AFFF'} />
<directionalLight
color="#ffdf91"
Expand Down
20 changes: 20 additions & 0 deletions src/components/Tile/SelectedTileContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { createContext, useContext, useRef, MutableRefObject, ReactNode } from 'react';
import { ColoredGroup } from './Tile';

type SelectedTileRefType = MutableRefObject<ColoredGroup | undefined> | undefined;

const SelectedTileRef = createContext<SelectedTileRefType>(undefined);

export const useSelectedTile = () => {
return useContext(SelectedTileRef);
};

interface SelectedTileProviderProps {
children: ReactNode;
}

export const SelectedTileProvider = ({ children }: SelectedTileProviderProps) => {
const globalRef = useRef<any>(null);

return <SelectedTileRef.Provider value={globalRef}>{children}</SelectedTileRef.Provider>;
};
84 changes: 44 additions & 40 deletions src/components/Tile/Tile.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
import { Instance } from '@react-three/drei';
import { ThreeEvent, useFrame } from '@react-three/fiber';
import { ThreeEvent, invalidate, useFrame } from '@react-three/fiber';
import { memo, startTransition, Suspense, useRef, useState } from 'react';
import { useStore } from '../../store/store';
import { Building } from '../Building/Building';
import { Color, Group } from 'three';
import { getTerrainHeight } from '../../helpers/terrain';
import { useSelectedTile } from './SelectedTileContext';

export interface ColoredGroup extends Group {
color: Color;
}

const lightGray = new Color(0xa0a0a0);
const grayScale = new Color('hsla(62, 0%, 20%)');
const noColor = new Color();

const TileComponent = () => {
const ref = useRef<Group>(null!);
const ref = useRef<ColoredGroup>(null!);

const globalSelected = useSelectedTile();

const [, setValue] = useState(0);
const [selected, setSelected] = useState(false);
const [isUnlocked, setUnlocked] = useState(false);

const forceUpdate = () => setValue((value) => value + 1);

const buildings = useStore((state) => state.buildings);
const setGlobalSelected = useStore((state) => state.setSelected);
const unlocked = useStore((state) => state.unlocked);
const setGlobalSelected = useStore((state) => state.setSelected);

const hasBuilding =
ref?.current &&
Expand All @@ -31,80 +36,79 @@ const TileComponent = () => {
let x: number;
let y: number;

const updateTile = () => {
const updateColor = () => {
x = ref.current.position.x;
y = ref.current.position.z;

if (isUnlocked) {
if (unlocked[x][y] < 1) {
// tile is shown as unlocked but should be locked
startTransition(() => setUnlocked(false));
}
} else if (!isUnlocked && unlocked[x]?.[y] > 0) {
if (globalSelected?.current?.id === ref.current.id) {
ref.current.color = noColor;
return;
}

if (unlocked[x]?.[y] === undefined || unlocked[x]?.[y] < 1) {
// tile is shown as unlocked but should be locked
ref.current.color = grayScale;
} else {
// tile is shown as locked but should be unlocked
startTransition(() => setUnlocked(true));
ref.current.color = lightGray;
}
};

const updateTile = () => {
x = ref.current.position.x;
y = ref.current.position.z;

updateColor();

ref.current.position.y = getTerrainHeight(x, y) - 0.5;
};

const onRefChange = () => {
if (
hasBuilding ||
(buildings[ref.current.position.x] && buildings[ref.current.position.x][ref.current.position.z] !== undefined)
)
if (hasBuilding || buildings[ref.current.position.x]?.[ref.current.position.z] !== undefined)
startTransition(() => forceUpdate());

updateTile();
selected && deselect();
globalSelected?.current?.id === ref.current.id && deselect();
};

const handleClick = (e: ThreeEvent<MouseEvent>) => {
e.stopPropagation();
e.intersections.forEach((intersection, index) => {
if (index > 0)
intersection.object.userData.deselect && startTransition(() => intersection.object.userData.deselect());
});
if (!selected) {
if (globalSelected?.current?.id !== ref.current.id) {
select();
} else {
deselect();
}
};

const select = () => {
setGlobalSelected({ type: 'tile', object: ref.current });
setSelected(true);
if (globalSelected) {
globalSelected.current = ref.current;
setGlobalSelected({ type: 'tile', object: ref.current });
invalidate();
}
};

const deselect = () => {
updateTile();
setGlobalSelected(undefined);
setSelected(false);
if (globalSelected) {
globalSelected.current = undefined;
setGlobalSelected(undefined);
invalidate();
}
};

useFrame(() => {
if (
!isUnlocked &&
unlocked[ref.current.position.x] &&
unlocked[ref.current.position.x][ref.current.position.z] > 0
) {
startTransition(() => setUnlocked(true));
} else if (isUnlocked && unlocked[ref.current.position.x]?.[ref.current.position.z] < 1) {
startTransition(() => setUnlocked(false));
}
updateColor();
});

return (
<Suspense>
<Instance
ref={(el: Group) => {
ref={(el: ColoredGroup) => {
ref.current = el;
}}
userData={{ update: () => onRefChange(), deselect: () => deselect() }}
onClick={(e) => handleClick(e)}
onPointerMissed={() => selected && deselect()}
color={selected ? noColor : isUnlocked ? lightGray : grayScale}
color={grayScale}
>
{hasBuilding && <Building type={buildings[ref.current.position.x][ref.current.position.z]} />}
</Instance>
Expand Down
23 changes: 12 additions & 11 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import { register } from "./serviceWorkerRegistration";
import reportWebVitals from "./reportWebVitals";
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { register } from './serviceWorkerRegistration';
import reportWebVitals from './reportWebVitals';
import { SelectedTileProvider } from './components/Tile/SelectedTileContext';

const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
<SelectedTileProvider>
<App />
</SelectedTileProvider>
</React.StrictMode>,
);

// If you want your app to work offline and load faster, you can change
Expand Down

0 comments on commit 49f3b9a

Please sign in to comment.