@mcmhomes/panorama-viewer
Version:
Provides React components to render panoramas.
148 lines (119 loc) • 3.96 kB
JSX
/*eslint-disable react-compiler/react-compiler*/
import React from 'react';
import {useThree} from '@react-three/fiber';
import {PanoramaRendererLayers} from './PanoramaRendererLayers.jsx';
import {loadMultiresTexture} from '../utils/PanoramaRendererUtils.jsx';
import {PanoramaRenderingLayerMinimumLoadTime} from './PanoramaRenderingLayer.jsx';
import {each, mapToArray, purgeErrorMessage, setAnimationFrameIntervalRemovable, STRING, uniqueId} from '../utils/PanoramaUtils.jsx';
import {memo, useEffect, useRef, useState} from '../utils/PanoramaUtilsReact.jsx';
export const PanoramaRendererTexturePreloader = memo(({src, homeId, host, homeUrl, basisTranscoderPath, setError, setLoading}) =>
{
const {gl} = useThree();
const currentLayersRef = useRef([]);
const [currentLayers, setCurrentLayers] = useState([]);
const readyLayersRef = useRef([]);
const [readyLayers, setReadyLayers] = useState([]);
useEffect(() =>
{
if(!gl)
{
return;
}
/** create a lookup table **/
let newSrc = {};
each(src, (item, index) =>
{
newSrc[item.basePath] = item;
});
/** dispose layers that are not in the new src **/
each(currentLayersRef.current, (layer, index) =>
{
if(newSrc[layer.src.basePath])
{
/** exists, don't dispose **/
delete newSrc[layer.src.basePath];
return;
}
/** removed, dispose **/
layer.removed = true;
currentLayersRef.current = currentLayersRef.current.filter(item => (item.key !== layer.key));
/** if not in readyLayers, dispose textures now **/
if(!readyLayersRef.current.find(item => (item.key === layer.key)))
{
layer.src.dispose();
}
});
/** add new layers **/
each(newSrc, (item, basePath) =>
{
let layer = {key:uniqueId()};
const onLoadingError = ({level, error}) =>
{
if(!layer.removed && (level <= 0))
{
setError({canRetry:true, id:'could-not-load-home', message:'Couldn\'t load the home: ' + homeId, reason:STRING(purgeErrorMessage(error)), data:{homeId, host, homeUrl}});
}
};
const loader = loadMultiresTexture({gl, basePath:item.basePath, maskBasePath:item.maskBasePath, basisTranscoderPath, minimumLoadTime:(readyLayersRef.current.length <= 0) ? 0 : PanoramaRenderingLayerMinimumLoadTime, onLoadingLevelFail:onLoadingError});
if(loader)
{
layer.src = {...item, ...loader};
currentLayersRef.current.push(layer);
}
});
setCurrentLayers([...currentLayersRef.current]);
// changes made to the other props (that are used in this function) are not reflected here, this useEffect simply checks for added and removed layers
// if the other props are changed (homeId, host, homeUrl, basisTranscoderPath), it won't affect the code here, so for performance reasons, it's safe to ignore
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [gl, src /*, homeId, host, homeUrl, basisTranscoderPath, setError*/]);
useEffect(() =>
{
const timer = setAnimationFrameIntervalRemovable(() =>
{
let ready = true;
each(currentLayers, (layer, index) =>
{
if(!layer.src.isReady(0))
{
ready = false;
return false;
}
});
if(ready)
{
readyLayersRef.current = [...currentLayers];
setReadyLayers(readyLayersRef.current);
timer.remove();
}
});
return timer.remove;
}, [currentLayers]);
const readyLayersIsEmpty = (readyLayers.length <= 0);
useEffect(() =>
{
setLoading(readyLayersIsEmpty);
}, [readyLayersIsEmpty, setLoading]);
useEffect(() =>
{
return () =>
{
/** cleanup **/
each(currentLayersRef.current, (layer, index) =>
{
layer.removed = true;
layer.src.dispose();
});
currentLayersRef.current = [];
readyLayersRef.current = [];
setCurrentLayers([]);
setReadyLayers([]);
};
}, []);
if(readyLayers.length <= 0)
{
return;
}
return (<>
<PanoramaRendererLayers src={mapToArray(readyLayers, layer => layer.src)}/>
</>);
});