Optimized track rendering

*Optimized track rendering to not always render both scaled and unscaled versions of the track
This commit is contained in:
izawartka 2025-08-13 13:36:13 +02:00
parent d7fdf0fc7e
commit 0ca6f6b9e0
4 changed files with 55 additions and 14 deletions

View File

@ -2,8 +2,9 @@ import React, { useRef, useEffect, useCallback } from 'react';
import './ZoomPanWrapper.css'; import './ZoomPanWrapper.css';
import Constants from '../../helpers/constants'; import Constants from '../../helpers/constants';
import { useZoomPanSubscriber, viewBox$, clientRect$, camera$ } from '../../hooks/useZoomPubSub'; import { useZoomPanSubscriber, viewBox$, clientRect$, camera$ } from '../../hooks/useZoomPubSub';
import {mapRotation$} from '../../services/mapRotationService'; import {setMapRotation} from '../../services/mapRotationService';
import AngleHelper from "../../helpers/angleHelper"; import AngleHelper from "../../helpers/angleHelper";
import { setMapZoom } from '../../services/mapZoomService';
function boundZoom(zoom) { function boundZoom(zoom) {
return Math.max(Constants.map.zoomMin, Math.min(Constants.map.zoomMax, zoom)); return Math.max(Constants.map.zoomMin, Math.min(Constants.map.zoomMax, zoom));
@ -44,7 +45,8 @@ export default function ZoomPanWrapper({children}) {
rafScheduledRef.current = false; rafScheduledRef.current = false;
cameraWrapperRef.current.setAttribute('transform', getCameraTransformString()); cameraWrapperRef.current.setAttribute('transform', getCameraTransformString());
camera$.next(cameraRef.current); camera$.next(cameraRef.current);
mapRotation$.next(cameraRef.current.rotation); setMapRotation(cameraRef.current.rotation);
setMapZoom(cameraRef.current.zoom);
}); });
}, []); }, []);

View File

@ -1,4 +1,4 @@
import React, {useContext} from "react"; import React, {useEffect, useCallback, useContext} from "react";
import SettingsContext from "../../../contexts/SettingsContext"; import SettingsContext from "../../../contexts/SettingsContext";
import Constants from "../../../helpers/constants"; import Constants from "../../../helpers/constants";
import {ElectrificationStatus} from "../../../model/electrification-status"; import {ElectrificationStatus} from "../../../model/electrification-status";
@ -7,6 +7,7 @@ import {setHoveredTrack, unsetHoveredTrack} from "../../../services/trackHoverIn
import GradientsContext from "../../../contexts/GradientsContext"; import GradientsContext from "../../../contexts/GradientsContext";
import { TrackShape, TrackSource } from "../../../model/tracks/track"; import { TrackShape, TrackSource } from "../../../model/tracks/track";
import {useZoomPanEmitter} from "../../../hooks/useZoomPubSub"; import {useZoomPanEmitter} from "../../../hooks/useZoomPubSub";
import { mapZoomTrackDetails$ } from "../../../services/mapZoomService";
export default function TrackRenderer(props) { export default function TrackRenderer(props) {
const { object } = props; const { object } = props;
@ -32,25 +33,47 @@ const MemoizedTrackRenderer = React.memo(StatelessTrackRenderer);
function StatelessTrackRenderer(props) { function StatelessTrackRenderer(props) {
const {object, trackColorMode, gradientDef, onAlign} = props; const {object, trackColorMode, gradientDef, onAlign} = props;
const unscaledPathRef = React.useRef(null);
const scaledPathRef = React.useRef(null);
if (object.points.start.distanceSq(object.points.end) < 0.001) { const onMouseEnter = useCallback(() => {
return null;
}
const onMouseEnter = () => {
setHoveredTrack(object); setHoveredTrack(object);
}; }, [object]);
const onMouseLeave = () => { const onMouseLeave = useCallback(() => {
unsetHoveredTrack(null); unsetHoveredTrack(null);
}; }, []);
const onClick = (event) => { const onClick = useCallback((event) => {
if (object.shape === TrackShape.STRAIGHT && event.detail === 2) { if (object.shape === TrackShape.STRAIGHT && event.detail === 2) {
event.preventDefault(); event.preventDefault();
onAlign(event); onAlign(event);
} }
}; }, [object, onAlign]);
const handleDetailsUpdate = useCallback((details) => {
if (!unscaledPathRef.current || !scaledPathRef.current) return;
if(details) {
unscaledPathRef.current.style.display = 'none';
scaledPathRef.current.style.display = 'block';
} else {
unscaledPathRef.current.style.display = 'block';
scaledPathRef.current.style.display = 'none';
}
}, []);
useEffect(() => {
const subscription = mapZoomTrackDetails$.subscribe(details => {
handleDetailsUpdate(details);
});
return () => subscription.unsubscribe();
}, [handleDetailsUpdate]);
if (object.points.start.distanceSq(object.points.end) < 0.001) {
return null;
}
const path = getTrackPath(object); const path = getTrackPath(object);
const color = getTrackColor(object, trackColorMode, gradientDef); const color = getTrackColor(object, trackColorMode, gradientDef);
@ -60,11 +83,13 @@ function StatelessTrackRenderer(props) {
<g id={`track-${object.id}`}> <g id={`track-${object.id}`}>
{defs} {defs}
<path <path
ref={unscaledPathRef}
d={path} d={path}
stroke={color} stroke={color}
className="track-unscaled" className="track-unscaled"
/> />
<path <path
ref={scaledPathRef}
d={path} d={path}
stroke={color} stroke={color}
className="track" className="track"

View File

@ -8,7 +8,8 @@ const Constants = {
rotationSensitivity: 0.2, rotationSensitivity: 0.2,
forcePointerEvents: false, forcePointerEvents: false,
platformMaxTilt: 0.5, platformMaxTilt: 0.5,
showDebugTrackIds: false showDebugTrackIds: false,
mapZoomTrackDetailsThreshold: 1 / 1.44
}, },
parser: { parser: {
logSceneryAfterFinished: true, logSceneryAfterFinished: true,

View File

@ -0,0 +1,13 @@
import { BehaviorSubject } from 'rxjs';
import Constants from '../helpers/constants';
export const mapZoom$ = new BehaviorSubject(null);
export const mapZoomTrackDetails$ = new BehaviorSubject(true);
export function setMapZoom(zoom) {
mapZoom$.next(zoom);
const newZoomTrackDetails = zoom > Constants.map.mapZoomTrackDetailsThreshold;
if (mapZoomTrackDetails$.getValue() !== newZoomTrackDetails) {
mapZoomTrackDetails$.next(newZoomTrackDetails);
}
}