简体   繁体   English

服务器发送事件 (SSE) 在简单的 3D 立方体 model 上使用 Three.JS 很慢

[英]Server sent events (SSE) is slow using Three.JS on a simple 3D cube model

I am currently developing a client-server application that takes accelerometer data from the data using SSE and pass them to three.js model and then render the results on the browser.我目前正在开发一个客户端-服务器应用程序,它使用 SSE 从数据中获取加速度计数据,并将它们传递给 three.js model,然后在浏览器上呈现结果。

Technically, the application should visualize accelerometer data in only one direction in real-time on the browser, so latency is important.从技术上讲,应用程序应该只在浏览器上实时显示一个方向的加速度计数据,因此延迟很重要。

Since I am only passing a single data (ie only accelerometerX), the process should be quick and instantaneous;因为我只传递一个数据(即只有 accelerometerX),所以这个过程应该是快速和即时的; however, it is taking really long to pass the accX value to object.position.x and even the console.log("Accelerometer X-Axis Data: " + (sensor value / 16384));但是,将 accX 值传递给 object.position.x 甚至 console.log("Accelerometer X-Axis Data: " + (sensor value / 16384)); show data once in a while.偶尔显示数据。 Sometimes, the whole browser crash showing "WebGL scene doesn't render because of lost context" error and a very long "precision" error.有时,整个浏览器崩溃显示“WebGL 场景由于丢失上下文而无法呈现”错误和很长的“精度”错误。 To this point, I have tried every single method, but I was never able to fix this long lagging issue.到目前为止,我已经尝试了每一种方法,但我始终无法解决这个长期滞后的问题。

This is what I get from the server, a JSON file that has this particular format:这是我从服务器得到的,一个具有这种特殊格式的 JSON 文件:

 "DMObjectsCompleteObject": [
        {
            "DataMapAddress": 1,
            "DataType": 9,
            "DefaultValue": 0,
            "Description": "Accelerometer X Axis Data",
            "MaxValue": 66,
            "MinValue": 18446744073709552000,
            "ReadOnly": false,
            "Value": -18706.4
        },
        {
            "DataMapAddress": 2,
            "DataType": 9,
            "DefaultValue": 0,
            "Description": "Accelerometer Y Axis Data",
            "MaxValue": 66,
            "MinValue": 18446744073709552000,
            "ReadOnly": false,
            "Value": 128
        }
]

and this the code for my client side:这是我客户端的代码:

// Importing libraries and data
import * as THREE from "three";
if (!!window.EventSource) {
    var source = new EventSource("/sse");

    source.addEventListener('message', function (event) {
        // Parameters initialization
        const canvas = document.querySelector('#canvas');
        const accelPanel = document.querySelector('#accelPanel');
        const renderer = new THREE.WebGLRenderer({ canvas });
        const fov = 70;
        const aspect = 2;  // the canvas default
        const near = 20;
        const far = 500;

        // Initialize camera perspective
        const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);

        // The camera FOV when the model starts to move
        camera.position.z = 25;
        camera.up.set(0, 0, 1);
        camera.lookAt(0, 0, 0);

        // Add background grid and light
        const scene = new THREE.Scene();
        {
            const color = 0x00afaf;
            const intensity = 10;
            const light = new THREE.PointLight(color, intensity);
            scene.add(light);
        }

        // Make the 3D cube model with the XYZ axis
        const boxGeometry = new THREE.BoxGeometry();
        const boxMaterial = new THREE.MeshBasicMaterial({ color: "green", wireframe: false });
        const object = new THREE.Mesh(boxGeometry, boxMaterial);

        var cubeAxis = new THREE.AxesHelper(3);
        object.add(cubeAxis);

        object.scale.set(5, 5, 5)
        scene.add(object);
        scene.background = new THREE.Color(0.22, 0.23, 0.22);

        let currentIndex = 0
        let time = 0
        let velocity = new THREE.Vector3()
        requestAnimationFrame(render);

        // Rendering function responsible of creating the translation motion
        function render(dt) {
            dt *= 0.0001 // in seconds
            time += dt
            document.querySelector("#time").textContent = time.toFixed(2)


            // JSON.parse twice due to over-stringified string from SSE
            var obj = JSON.parse(JSON.parse(event.data));
            
            if (obj !== null) {
                // Sensor data
                if (
                    obj.hasOwnProperty("DataMapChangedObjectsAddressValue") &&
                    obj["DataMapChangedObjectsAddressValue"][0]["DataMapAddress"] !==
                    undefined
                ) {
                    let sensorAddr =
                        obj["DataMapChangedObjectsAddressValue"][0]["DataMapAddress"];
                    let sensorValue =
                        obj["DataMapChangedObjectsAddressValue"][0]["Value"];

                    //Accelerometer X Axis
                    //if(sensorAddr === this.despToAddrMap.get("Accelerometer X Axis Data")){
                    if (sensorAddr === 1) {
                        // console.log(obj["DataMapChangedObjectsAddressValue"][2]["Value"])
                        console.log("Accelerometer X Axis Data: " + (sensorValue / 16384));
                    }

                    object.position.x = (sensorValue / 16384) * 500;
                    document.querySelector("#accX").textContent = (sensorValue / 16384) * 500;
                    object.rotation.y = -70.68;
                    var relativeCameraOffset = new THREE.Vector3(5, 10, 1);
                    var cameraOffset = relativeCameraOffset.applyMatrix4(object.matrixWorld);
                    camera.position.x = cameraOffset.x;
                    // camera.position.y = cameraOffset.y;
                    // camera.position.z = cameraOffset.z;
                    camera.lookAt(object.position);
                }
            }

            // // Find datapoint matching current time
            // while (data[currentIndex].time < time) {
            //     currentIndex++
            //     if (currentIndex >= data.length) return
            // }
            // const { rotX, rotY, rotZ, accX, accY, accZ } = data[currentIndex]
            // document.querySelector("#accX").textContent = accX;

            // const acceleration = new THREE.Vector3(accX, accY, accZ)
            // object.position.x = accX * 30;
            // object.rotation.y = -70.68;

            resizeToClient();
            renderer.render(scene, camera);
            requestAnimationFrame(render);
        }

        function resizeToClient() {
            const needResize = resizeRendererToDisplaySize()
            if (needResize) {
                const canvas = renderer.domElement;
                camera.aspect = canvas.clientWidth / canvas.clientHeight;
                camera.updateProjectionMatrix();
            }
        }

        function resizeRendererToDisplaySize() {
            const canvas = renderer.domElement;
            const width = canvas.clientWidth;
            const height = canvas.clientHeight;
            const needResize = canvas.width !== width || canvas.height !== height;
            if (needResize) {
                renderer.setSize(width, height, false);
            }
            return needResize;
        }
    }, false)

    source.addEventListener('open', function (e) {
        // document.getElementById('state').innerHTML = "Connected"
    }, false)

    source.addEventListener('error', function (e) {
        const id_state = document.getElementById('state')
        if (e.eventPhase == EventSource.CLOSED)
            source.close()
        if (e.target.readyState == EventSource.CLOSED) {
            id_state.innerHTML = "Disconnected"
        }
        else if (e.target.readyState == EventSource.CONNECTING) {
            id_state.innerHTML = "Connecting..."
        }
    }, false)
} else {
    console.log("Your browser doesn't support SSE")
}

This is how the client renders the 3D cube:这是客户端渲染 3D 立方体的方式: 在此处输入图像描述

But it barely moves, and it is too laggy.但它几乎不动,而且太迟钝了。 Can someone please suggest a solution to this problem?有人可以建议解决这个问题吗? It would be much appreciated.将不胜感激。

Its also important how your SSE server is implemented.如何实现 SSE 服务器也很重要。 If you use expressjs style one, like this one (sorry for not very clean code, its from live project)如果您使用 expressjs 样式之一,例如这个(抱歉代码不是很干净,它来自实时项目)

'use strict';

const util = require('util');
// const config = require('../lib/config');
const requireAuthorization = require('../middlewares/requireAuthorization');
const router = require('express').Router();
// const thinky = require('../lib/thinky');
// const r = thinky.r;
// const Errors = thinky.Errors;
const logger = require('../lib/logger');
const helpers = require('../lib/helpers');

// redis feed for events

const subscriber = require('../lib/redis').createClient();
const EventEmitter = require('events');
const spine = new EventEmitter();
subscriber.on('message', function (channel, message) {
  spine.emit(channel, message);
});

// setInterval(function () {
//   spine.emit('scubamailer_feed', JSON.stringify({time: Date.now()}));
// }, 500);

subscriber.subscribe('scubamailer_feed');


router.use(requireAuthorization);

router.get('/subscribe', function (req, res, next) {

  // Good read -
  // https://learn.javascript.ru/server-sent-events#tipy-sobytiy
  // https://www.terlici.com/2015/12/04/realtime-node-expressjs-with-sse.html
  // http://stackoverflow.com/questions/27898622/server-sent-events-stopped-work-after-enabling-ssl-on-proxy
  // http://stackoverflow.com/a/33414096/1885921
  // https://github.com/expressjs/compression/issues/17

  // how to remove listeners
  // https://odetocode.com/blogs/scott/archive/2013/07/16/angularjs-listening-for-destroy.aspx

  logger.info('User %s subscribed to event feed from IP %s.', req.user.email, helpers.extractIPfromReq(req), {
    user: req.user.email,
    type: 'user/unsubFromEvents'
  });

  req.isSSE = true; // PLEASE, DO NOT TOUCH IT, OK???
  req.socket.setTimeout(24 * 60 * 60 * 1000);
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');
  res.setHeader('X-Accel-Buffering', 'no');

  // redis feed events
  const listen = function (message) {
    res.write(util.format('event: notification\ndata: %s\n\n', message));
    // res.flush(); // https://github.com/expressjs/compression#server-sent-events
  };
  const tickerInterval = setInterval(function () {
    res.write(util.format('event: time\ndata: %s\n\n', Date.now()));
  }, 500);


  const stopListening = function () {
    logger.info('User %s [%s] unsubscribed from event feed...', req.user.email, helpers.extractIPfromReq(req), {
      user: req.user.email,
      type: 'user/unsubFromEvents'
    });
    clearInterval(tickerInterval);
    spine.removeListener('scubamailer_feed', listen);
  };
  res.once('close', stopListening);
  res.once('finish', stopListening);
  spine.on('scubamailer_feed', listen);


  const entitiesToMonitor = [
    'Campaign',
    'EmailImport',
    'EmailExport',
    'User',
    'Server'
  ];

  return Promise.all(entitiesToMonitor.map(function (entity) {
    return req.model[entity].changes()
      .then(function (feed) {
        feed.each(function (error, doc) {
          if (error) {
            throw error;
          }
          if (doc.isSaved()) { // send updates only if document is persisted in database
            if (entity === 'User') {
              res.write(util.format('event: %s\ndata: %s\n\n', entity, JSON.stringify(doc.formatToJSON())));
            } else {
              res.write(util.format('event: %s\ndata: %s\n\n', entity, JSON.stringify(doc)));
            }

            // res.flush(); // https://github.com/expressjs/compression#server-sent-events
          }
        });
        return Promise.resolve();
      });
  }))
    .catch(next);
});

module.exports = exports = router;

as you can see, yo need to provide all headers required如您所见,您需要提供所需的所有标题

  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');
  res.setHeader('X-Accel-Buffering', 'no');

X-Accel-Buffering is required to nginx to process SSE feeds properly. nginx 需要 X-Accel-Buffering 才能正确处理 SSE 馈送。

Also, if you use nodejs, it can be importnt to ensure compression middleware does not compress endpoint of SSE stream, since it makes event delivery laggy此外,如果您使用 nodejs,确保压缩中间件不会压缩 SSE laggy的端点可能很重要,因为它会使事件传递滞后

There are too many moving parts here to know what the problem is.这里有太多活动部件,不知道问题出在哪里。

Replace your message handler with something like:将您的消息处理程序替换为以下内容:

source.addEventListener('message', function (event) {
console.log(event)
}

Now confirm your accelerometer data is arriving in the console log in real time.现在确认您的加速度计数据实时到达控制台日志。 (This is also a good time to confirm the data structure is exactly as expected.) (这也是确认数据结构完全符合预期的好时机。)

If it does work quickly and reliably, then SSE is working fine and your question is just about ThreeJS.如果它确实可以快速可靠地运行,那么 SSE 运行良好,您的问题只是关于 ThreeJS。 (I'd move your static functions outside the message handler for a start - if only to make the code easier to read.) (我会将您的 static 函数移到消息处理程序之外作为开始 - 如果只是为了使代码更易于阅读。)

But if your data stream is very quiet, then a big chunk of data arrives, then it is quiet again before another big chunk arrives, that probably means your server is buffering.但是,如果您的数据 stream 非常安静,那么一大块数据到达,然后在另一个大块到达之前再次安静,这可能意味着您的服务器正在缓冲。 You haven't described the server-side system, but look into options to flush data, or switch off output buffering.您尚未描述服务器端系统,但请查看刷新数据的选项,或关闭 output 缓冲。

If the data arrives irregularly, sometimes working, sometimes not, then I'd look into network issues.如果数据不规律地到达,有时工作,有时不工作,那么我会调查网络问题。 The developer tools in the browser can help diagnose these kind of issues.浏览器中的开发者工具可以帮助诊断这类问题。 Or removing the browser from the equation, and using curl (or similar) to debug the SSE stream.或者从等式中删除浏览器,并使用 curl(或类似的)来调试 SSE stream。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM