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

fix(visualizer): paused visualizer behaves weirdly when reaching max blocks #1202

Merged
merged 8 commits into from
Mar 7, 2024
18 changes: 11 additions & 7 deletions client/src/features/visualizer-threejs/VisualizerInstance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { RouteComponentProps } from "react-router-dom";
import * as THREE from "three";
import { FAR_PLANE, features, NEAR_PLANE, DIRECTIONAL_LIGHT_INTENSITY, VISUALIZER_BACKGROUND } from "./constants";
import Emitter from "./Emitter";
import { useTangleStore, useConfigStore } from "./store";
import { useTangleStore, useConfigStore, IAddToColorQueueBulkItem } from "./store";
import { BPSCounter } from "./BPSCounter";
import { VisualizerRouteProps } from "../../app/routes/VisualizerRouteProps";
import { ServiceFactory } from "../../factories/serviceFactory";
Expand All @@ -23,7 +23,6 @@ import CameraControls from "./CameraControls";
import useVisualizerTimer from "~/helpers/nova/hooks/useVisualizerTimer";
import { getBlockInitPosition, getBlockTargetPosition } from "./blockPositions";
import { getBlockColorByState, getCurrentTiltValue } from "./utils";
import useSearchStore from "~features/visualizer-threejs/store/search";
import { useSearch } from "~features/visualizer-threejs/hooks/useSearch";
import "./Visualizer.scss";

Expand Down Expand Up @@ -56,10 +55,11 @@ const VisualizerInstance: React.FC<RouteComponentProps<VisualizerRouteProps>> =
const addBlock = useTangleStore((s) => s.addToBlockQueue);
const addToEdgeQueue = useTangleStore((s) => s.addToEdgeQueue);
const addToColorQueue = useTangleStore((s) => s.addToColorQueue);
const addToColorQueueBulk = useTangleStore((s) => s.addToColorQueueBulk);
const blockMetadata = useTangleStore((s) => s.blockMetadata);
const updateBlockMetadata = useTangleStore((s) => s.updateBlockMetadata);
const indexToBlockId = useTangleStore((s) => s.indexToBlockId);
const clickedInstanceId = useTangleStore((s) => s.clickedInstanceId);
const matchingBlockIds = useSearchStore((state) => state.matchingBlockIds);

const blockIdToState = useTangleStore((s) => s.blockIdToState);
const setBlockIdToBlockState = useTangleStore((s) => s.setBlockIdToBlockState);
Expand Down Expand Up @@ -215,6 +215,7 @@ const VisualizerInstance: React.FC<RouteComponentProps<VisualizerRouteProps>> =
}

const blockColor = getBlockColorByState(themeMode, "pending");
const blockMetadata = useTangleStore.getState().blockMetadata;
blockMetadata.set(blockData.blockId, { ...blockData, treeColor: blockColor });

// edges
Expand Down Expand Up @@ -257,11 +258,9 @@ const VisualizerInstance: React.FC<RouteComponentProps<VisualizerRouteProps>> =

if (!wasConfirmedBeforeAccepted) {
setBlockIdToBlockState(metadataUpdate.blockId, metadataUpdate.blockState);
if (!matchingBlockIds.includes(metadataUpdate.blockId)) {
addToColorQueue(metadataUpdate.blockId, selectedColor);
}
}

addToColorQueue(metadataUpdate.blockId, selectedColor);
const acceptedStates: BlockState[] = ["confirmed", "accepted"];

if (acceptedStates.includes(metadataUpdate.blockState)) {
Expand All @@ -276,13 +275,18 @@ const VisualizerInstance: React.FC<RouteComponentProps<VisualizerRouteProps>> =
const blocks = confirmedBlocksBySlot.get(slot);

if (blocks?.length) {
const colorQueue: IAddToColorQueueBulkItem[] = [];
blocks.forEach((blockId) => {
const selectedColor = getBlockColorByState(themeMode, "finalized");
if (selectedColor) {
addToColorQueue(blockId, selectedColor);
setBlockIdToBlockState(blockId, "finalized");
updateBlockMetadata(blockId, { treeColor: selectedColor });

colorQueue.push({ id: blockId, color: selectedColor });
}
});

addToColorQueueBulk(colorQueue);
}

removeConfirmedBlocksSlot(slot);
Expand Down
33 changes: 33 additions & 0 deletions client/src/features/visualizer-threejs/hooks/usePlayPause.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useTangleStore, useConfigStore, IAddToColorQueueBulkItem } from "~features/visualizer-threejs/store";

const usePlayPause = () => {
const isPlaying = useConfigStore((state) => state.isPlaying);
const setIsPlaying = useConfigStore((state) => state.setIsPlaying);
const addToColorQueueBulk = useTangleStore((state) => state.addToColorQueueBulk);
const setVisibleBlockIdsOnPause = useTangleStore((state) => state.setVisibleBlockIdsOnPause);
const blockMetadata = useTangleStore((state) => state.blockMetadata);

const onToggle = () => {
if (isPlaying) {
const visibleBlockIds = blockMetadata.keys();
setIsPlaying(false);
setVisibleBlockIdsOnPause([...visibleBlockIds]);
} else {
const colorsQueue: IAddToColorQueueBulkItem[] = [];
blockMetadata.forEach((metadata, id) => {
if (metadata.treeColor) {
colorsQueue.push({ id, color: metadata.treeColor });
}
});
addToColorQueueBulk(colorsQueue);
setVisibleBlockIdsOnPause(undefined);
setIsPlaying(true);
}
};

return {
onToggle,
};
};

export default usePlayPause;
30 changes: 28 additions & 2 deletions client/src/features/visualizer-threejs/store/tangle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ export interface IBlockAnimationPosition {
elapsedTime: number;
}

export interface IAddToColorQueueBulkItem {
id: string;
color: Color;
}

export interface IBlockState extends Omit<IBlockAnimationPosition, "elapsedTime"> {
id: string;
color: Color;
Expand Down Expand Up @@ -47,14 +52,15 @@ interface TangleState {

colorQueue: Pick<IBlockState, "id" | "color">[];
addToColorQueue: (blockId: string, color: Color) => void;
addToColorQueueBulk: (list: { id: string; color: Color }[]) => void;
addToColorQueueBulk: (list: IAddToColorQueueBulkItem[]) => void;
removeFromColorQueue: (blockIds: string[]) => void;

// Map of blockId to index in Tangle 'InstancedMesh'
blockIdToIndex: Map<string, number>;
blockIdToEdges: Map<string, EdgeEntry>;
blockIdToPosition: Map<string, [x: number, y: number, z: number]>;
blockMetadata: Map<string, IFeedBlockData & { treeColor: Color }>;
updateBlockMetadata: (blockId: string, metadata: Partial<IFeedBlockData & { treeColor: Color }>) => void;

indexToBlockId: string[];
updateBlockIdToIndex: (blockId: string, index: number) => void;
Expand Down Expand Up @@ -83,6 +89,10 @@ interface TangleState {

blockIdToState: Map<BlockId, BlockState>;
setBlockIdToBlockState: (blockId: BlockId, blockState: BlockState) => void;

// visible block ids on pause
visibleBlockIdsOnPause?: string[];
setVisibleBlockIdsOnPause: (blockIds: string[] | undefined) => void;
}

const INITIAL_STATE = {
Expand All @@ -101,10 +111,11 @@ const INITIAL_STATE = {
clickedInstanceId: null,
confirmedBlocksBySlot: new Map(),
blockIdToState: new Map(),
visibleBlockIdsOnPause: undefined,
};

export const useTangleStore = create<TangleState>()(
devtools((set) => ({
devtools((set, get) => ({
...INITIAL_STATE,
resetConfigState: () =>
// hard cleanup of the store
Expand Down Expand Up @@ -303,5 +314,20 @@ export const useTangleStore = create<TangleState>()(
};
});
},

setVisibleBlockIdsOnPause: (blockIds) => {
set((state) => ({
...state,
visibleBlockIdsOnPause: blockIds,
}));
},
updateBlockMetadata: (blockId, metadata) => {
const blockMetadata = get().blockMetadata;
const currentMetadata = blockMetadata.get(blockId);
if (currentMetadata) {
const newMetadata = { ...currentMetadata, ...metadata };
blockMetadata.set(blockId, newMetadata);
}
},
})),
);
34 changes: 23 additions & 11 deletions client/src/features/visualizer-threejs/useRenderTangle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useRenderEdges } from "./useRenderEdges";
import useVisualizerTimer from "~/helpers/nova/hooks/useVisualizerTimer";
import { positionToVector } from "./utils";
import { getVisualizerConfigValues } from "~features/visualizer-threejs/ConfigControls";
import useSearchStore from "~features/visualizer-threejs/store/search";

const SPHERE_GEOMETRY = new THREE.SphereGeometry(NODE_SIZE_DEFAULT, 32, 16);
const SPHERE_MATERIAL = new THREE.MeshPhongMaterial();
Expand All @@ -25,6 +26,7 @@ export const useRenderTangle = () => {
const removeFromBlockQueue = useTangleStore((s) => s.removeFromBlockQueue);

const colorQueue = useTangleStore((s) => s.colorQueue);
const addToColorQueue = useTangleStore((s) => s.addToColorQueue);
const removeFromColorQueue = useTangleStore((s) => s.removeFromColorQueue);

const blockIdToIndex = useTangleStore((s) => s.blockIdToIndex);
Expand All @@ -44,7 +46,7 @@ export const useRenderTangle = () => {
updateBlockIdToIndex(block.id, objectIndexRef.current);

tangleMeshRef.current.setMatrixAt(objectIndexRef.current, SPHERE_TEMP_OBJECT.matrix);
tangleMeshRef.current.setColorAt(objectIndexRef.current, block.color);
addToColorQueue(block.id, block.color);

// Reuses old indexes when MAX_INSTANCES is reached
// This also makes it so that old nodes are removed
Expand Down Expand Up @@ -129,20 +131,30 @@ export const useRenderTangle = () => {
* Update block colors
*/
useEffect(() => {
if (colorQueue.length > 0) {
const removeIds: string[] = [];
for (const { id, color } of colorQueue) {
const indexToUpdate = blockIdToIndex.get(id);
const updateColorOnMesh = (id: string, color: THREE.Color) => {
const indexToUpdate = blockIdToIndex.get(id);

if (indexToUpdate) {
tangleMeshRef.current.setColorAt(indexToUpdate, color);
if (indexToUpdate) {
tangleMeshRef.current.setColorAt(indexToUpdate, color);

if (tangleMeshRef.current.instanceColor) {
tangleMeshRef.current.instanceColor.needsUpdate = true;
}
if (tangleMeshRef.current.instanceColor) {
tangleMeshRef.current.instanceColor.needsUpdate = true;
}
}
};

removeIds.push(id);
if (colorQueue.length > 0) {
const removeIds: string[] = [];
const isPlaying = useConfigStore.getState().isPlaying;
const visibleBlockIdsOnPause = useTangleStore.getState().visibleBlockIdsOnPause;
const matchingBlockIds = useSearchStore.getState().matchingBlockIds;

for (const { id, color } of colorQueue) {
const shouldUpdateColor = !matchingBlockIds.includes(id) && (isPlaying || visibleBlockIdsOnPause?.includes(id));
if (shouldUpdateColor) {
updateColorOnMesh(id, color);
}
removeIds.push(id);
}

removeFromColorQueue(removeIds);
Expand Down
4 changes: 3 additions & 1 deletion client/src/features/visualizer-threejs/wrapper/Wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import useSearchStore from "~features/visualizer-threejs/store/search";
import { useTangleStore } from "~features/visualizer-threejs/store/tangle";
import { SEARCH_RESULT_COLOR, features } from "~features/visualizer-threejs/constants";
import { isSearchMatch } from "~features/visualizer-threejs/hooks/useSearch";
import usePlayPause from "~features/visualizer-threejs/hooks/usePlayPause";
import { ThemeMode } from "../enums";

export const Wrapper = ({
Expand Down Expand Up @@ -42,6 +43,7 @@ export const Wrapper = ({
const setMatchingBlockIds = useSearchStore((state) => state.setMatchingBlockIds);
const blockMetadata = useTangleStore((state) => state.blockMetadata);
const addToColorQueueBulk = useTangleStore((s) => s.addToColorQueueBulk);
const { onToggle } = usePlayPause();

React.useEffect(() => {
const colorsQueue = [];
Expand Down Expand Up @@ -100,7 +102,7 @@ export const Wrapper = ({
{children}
<div className="action-panel-container">
<div className="card">
<button className="pause-button" type="button" onClick={() => setIsPlaying(!isPlaying)}>
<button className="pause-button" type="button" onClick={onToggle}>
{isPlaying ? (
<span className="material-icons">pause</span>
) : (
Expand Down
Loading