I am using the three.js to get a image from a server and render it, and also pooling data from it to get the newest image available, but for some reason on Firefox in Linux, when I add a canvas with the image, everything freezes and after a while it crashes, the data from the server shows a huge spike in memory to almost 100%.
On any other browsers everything is alright and works perfectly, but on this Firefox, it does not.
Firefox 77, tried regression to 48, still the same issue.
OS - OpenSuse Linux
Library - React
// React
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import store from '../../store';
import * as THREE from 'three';
import OrbitControls from '../../vendors/three/OrbitControls';
import './CanvasWidget.css';
// App
import ImageObject from '../three/ImageObject';
import Pointcloud from '../three/PointcloudObject';
import {
saveCameraPosition,
saveLayout,
removedExternaly
} from '../../actions';
import OverlaysManager from '../three/OverlaysManager';
class CanvasWidget extends React.Component {
static compatibleIDataTypes = ['IDataImage', 'IDataPointcloudXYZI'];
static widgetType = 'CanvasWidget';
static propTypes = {
editingVisualization: PropTypes.bool,
store: PropTypes.object,
widgetRef: PropTypes.object.isRequired
};
constructor(props) {
super(props);
this.state = {
pointClouds: [],
treeNodeData: null
};
this.imgObj = new ImageObject();
this.pclObj = new Pointcloud();
this.dataAvailable = false;
this.hasUnsavedPositionChange = false;
this.timerID = undefined;
this.changedExternally = false;
this.overlaysManager = new OverlaysManager();
}
componentWillMount = () => {
const { widgetRef } = this.props;
const treeNodesData = store.getState().treeNodesData;
if (!!treeNodesData) {
let nodeData = treeNodesData[widgetRef.nodePath];
if (!!nodeData) {
this.updateFrame(widgetRef, treeNodesData);
}
}
}
// Initialize scene
componentDidMount() {
const { widgetRef } = this.props;
this.camera = new THREE.PerspectiveCamera(
75,
this.width / this.height || 1,
0.01,
10000
);
if (widgetRef.nodeType === 'IDataImage') {
this.is2D = true;
this.cameraPosition = new THREE.Vector3(0, 0, 700); //distant Z
this.camera.up.set(0, 0, 1);
} else {
this.cameraPosition = new THREE.Vector3(0, 300, 0); //distant Y
}
this.scene = new THREE.Scene();
this.light = new THREE.AmbientLight(0xffffff, 1);
this.camera.position.x = this.cameraPosition.x;
this.camera.position.y = this.cameraPosition.y;
this.camera.position.z = this.cameraPosition.z;
this.camera.updateMatrix();
this.camera.updateProjectionMatrix();
this.scene.add(this.camera);
this.scene.add(this.light);
this.canvas = document.getElementById('canvas');
this.renderer = new THREE.WebGLRenderer({
antialias: true,
// canvas: canvas,
logarithmicDepthBuffer: true
});
this.renderer.setClearColor('#fff');
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setSize(window.innerWidth, window.innerHeight);
const canvas = this.renderer.domElement;
this.mount.appendChild(this.renderer.domElement);
this.initOrbitControls();
const treeNodesData = store.getState().treeNodesData;
if (!!treeNodesData) {
let nodeData = treeNodesData[widgetRef.nodePath];
if (!!nodeData) {
this.updateData(nodeData);
}
}
this.updateCameraPosition(this.props)
this.refreshSizes();
this.start();
}
start = () => {
if (!this.frameId) {
this.frameId = requestAnimationFrame(this.animate);
}
};
animate = () => {
this.controls.update();
if(this.camera) {
if (!!this.overlaysManager.overlaysVector) {
this.overlaysManager.overlaysVector.map(overlay => {
if (!this.is2D) {
overlay.addaptToPCL();
}
overlay.scaleForZoomLevel(this.camera.position)
});
}
}
this.renderScene();
this.frameId = window.requestAnimationFrame(this.animate);
};
renderScene = () => {
this.renderer.render(this.scene, this.camera);
};
isAvailable = nodeData => {
return (nodeData.type !== 'NotAvailable' && nodeData.error !== true);
};
componentDidUpdate() {
this.refreshSizes();
}
updateFrame = (widgetRef, treeNodesData) => {
const { frame } = widgetRef;
if (frame) {
if (frame.static) {
this.frameColor = frame.color;
} else {
if (!!treeNodesData[frame.nodePath]) {
this.frameColor =
treeNodesData[frame.nodePath].error ||
!treeNodesData[frame.nodePath].inRange
? '#ff0000'
: '#00ff00';
}
}
}
else {
this.frameColor = 'rgb(0, 97, 158)';
}
};
componentWillReceiveProps(nextProps) {
if (!!this.controls) {
this.controls.enabled = nextProps.editingVisualization;
}
const widgetRef = this.props.widgetRef;
let treeNodesData = nextProps.treeNodesData;
let lastPackageID = nextProps.lastPackageID;
this.updateCameraPosition(nextProps);
if (this.packageID !== lastPackageID) {
this.packageID = lastPackageID;
//Refactoring work
if (!!treeNodesData) {
let nodeData = treeNodesData[widgetRef.nodePath];
if (!!nodeData) {
this.updateData(nodeData);
}
}
}
if (!!treeNodesData) {
this.updateFrame(widgetRef, treeNodesData);
this.overlaysManager.updateOrCreateOverlays(
treeNodesData,
this.scene,
widgetRef
);
if (this.overlaysManager.removedExternaly.length !== 0) {
this.overlaysManager.removedExternaly.forEach(overlay =>
this.props.removedExternaly(overlay)
);
this.props.requestSaveLayout(this.props.store.getState().widgets);
}
}
this.changeDocumentTitle();
}
updateData = (nodeData) => {
this.dataAvailable = this.isAvailable(nodeData);
if (this.dataAvailable) {
if (this.is2D) {
this.imgObj.createImage(
nodeData,
this.scene
);
if (this.imgObj.hasError) {
delete this.imgObj.textureLoader;
this.imgObj.textureLoader = null;
}
} else {
this.pclObj.update(
nodeData,
this.scene
);
}
} else {
if (this.is2D) {
this.imgObj.markErrorState();
} else {
this.pclObj.markErrorState();
}
}
}
changeDocumentTitle() {
if (store.getState().needSaving === true) {
document.title = 'uniVision Visualization *';
} else {
document.title = 'uniVision Visualization';
}
}
componentWillUnmount() {
this.controls.removeEventListener('change', this.onCameraChange, false);
this.controls.dispose();
this.overlaysManager.removeAllOverlays();
this.scene.dispose();
cancelAnimationFrame(this.frameId);
this.mount.removeChild(this.renderer.domElement);
}
refreshSizes() {
if (this.mount && this.mount.offsetParent) {
this.width = this.mount.offsetParent.clientWidth - 10;
this.height = this.mount.offsetParent.clientHeight - 10;
this.camera.aspect = this.width / this.height;
this.renderer.setSize(this.width, this.height);
this.camera.updateMatrix();
this.camera.updateProjectionMatrix();
}
}
initOrbitControls = () => {
if (this.camera) {
this.controls = new OrbitControls(
this.camera,
ReactDOM.findDOMNode(this.mount.offsetParent)
);
//this.controls.minDistance = 1;
if (this.props.widgetRef.cameraPosition) {
this.cameraPosition = this.props.widgetRef.cameraPosition;
let pos = this.props.widgetRef.cameraPosition;
this.camera.position.x = Number(pos.x);
this.camera.position.y = Number(pos.y);
this.camera.position.z = Number(pos.z);
this.camera.updateMatrix();
this.camera.updateProjectionMatrix();
}
if (this.props.widgetRef.controlsPosition) {
let pos = this.props.widgetRef.controlsPosition;
this.controls.target.x = Number(pos.x);
this.controls.target.y = Number(pos.y);
this.controls.target.z = Number(pos.z);
this.controls.update();
}
this.controls.enableZoom = true;
this.controls.enablePan = true;
this.controls.enableDamping = true;
this.controls.enableRotate = true;
this.controls.addEventListener('change', this.onCameraChange, false);
this.controls.mouseButtons = {
ORBIT: null,
ZOOM: null,
MIDDLE: THREE.MOUSE.MIDDLE
};
this.controls.enabled = this.props.editingVisualization;
}
};
onCameraChange = () => {
if (this.props.editingVisualization && !this.changedExternally) {
this.cameraPosition = this.camera.position;
this.controlsPosition = this.controls.target;
this.toSendArr = [
this.cameraPosition,
this.controlsPosition,
this.props.widgetRef.id
];
//this.props.saveCameraPosition(toSendArr);
this.hasUnsavedPositionChange = true;
this.restartTimer(this.saveUnchangedPositionChanges);
}
this.changedExternally = false;
};
restartTimer = func => {
if (this.timerID !== undefined) {
window.clearTimeout(this.timerID);
}
this.timerID = window.setTimeout(func, 750);
};
updateCameraPosition = nextProps => {
if (nextProps.widgetRef.cameraPosition) {
this.cameraPosition = nextProps.widgetRef.cameraPosition;
let pos = this.cameraPosition;
this.camera.position.y = Number(pos.y);
this.camera.position.x = Number(pos.x);
this.camera.position.z = Number(pos.z);
this.camera.updateMatrix();
this.camera.updateProjectionMatrix();
this.changedExternally = true;
}
if (nextProps.widgetRef.controlsPosition) {
let pos = nextProps.widgetRef.controlsPosition;
this.controls.target.x = Number(pos.x);
this.controls.target.y = Number(pos.y);
this.controls.target.z = Number(pos.z);
this.controls.enabled = nextProps.editingVisualization;
this.controls.update();
}
};
saveUnchangedPositionChanges = () => {
this.props.saveCameraPosition(this.toSendArr);
if (this.hasUnsavedPositionChange === true) {
this.hasUnsavedPositionChange = false;
this.props.requestSaveLayout(this.props.store.getState().widgets);
this.timerID = undefined;
}
};
render() {
return (
<div
style={{ border: '5px solid ' + this.frameColor, overflow: 'hidden' }}
ref={mount => {
this.mount = mount;
}}
/>
);
}
}
const mapStateToProps = state => {
return {
editingVisualization: state.isEditing,
treeNodesData: state.treeNodesData,
lastPackageID: state.lastPackageID,
widgets: state.widgets,
layoutID: state.layoutID
};
};
const mapDispatchToProps = dispatch => {
return {
saveCameraPosition: serializedCameraInfo =>
dispatch(saveCameraPosition(serializedCameraInfo)),
requestSaveLayout: widgets => dispatch(saveLayout(widgets)),
removedExternaly: overlay => dispatch(removedExternaly(overlay))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(CanvasWidget);