Skip to content

Commit

Permalink
fix(visualizer): paused visualizer behaves weirdly when reaching max …
Browse files Browse the repository at this point in the history
…blocks (#1202)

* fix: colorQueue leak

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* fix: colorQueue on pause. Prevent overwrite colors by new block.

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* fix: colorQueue on play again. Checkpoint.

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* fix: colorQueue, check conditions in loop.

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* fix: update block metadata when slot finalized.

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

* fix: update all active blocks when unpause.

Signed-off-by: Eugene Panteleymonchuk <[email protected]>

---------

Signed-off-by: Eugene Panteleymonchuk <[email protected]>
Co-authored-by: Begoña Álvarez de la Cruz <[email protected]>
  • Loading branch information
panteleymonchuk and begonaalvarezd authored Mar 7, 2024
1 parent 82b4c21 commit cfb5f7d
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 21 deletions.
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

0 comments on commit cfb5f7d

Please sign in to comment.