1

默认情况下,我从 websocket 检索数据,每 2 秒采样一次。我使用 zustand 传播这些数据以进行状态管理。我有多个依赖于此数据的动画/组件。为了补偿 2 秒的间隔间隙,我计算了不是由 zustand 而是由 useState 处理的每一帧的数据。

我可以使用 CustomEvent 来更新每一帧的所有依赖组件,或者我可以使用自定义钩子,我认为这是我更喜欢的。

问题是每当 api > zustand > useStore 更新钩子的本地状态(样本)时,useFrame 都会丢失本地状态。

这是钩子...

/**
 * merge two streams of similar data
 * one stream comes from api and has a default sample-rate of about 2 seconds
 * the second stream updates every frame change, roughly 60 frames / second
 */
export default () => {
  const { dc, status, metrics } = useStore((state) => state.api);
  const [sample, setSample] = useState(metrics.slice(-1)[0]);
  const advanceSample = (duration, sample) => {
    if (status === 'open') {
      const radiansPerSecond = (sample.rpm / 60) * 2 * Math.PI;
      const radiansPerFrame = radiansPerSecond * duration;
      const r = (sample.r + radiansPerFrame) % (2 * Math.PI);
      const active = degreesToActive[r * (180 / Math.PI)];
      return {
        ms: Date.now(),
        r,
        rpm: sample.rpm,
        c1: { mA: mA(active, dc), t: t(active, dc) },
        c2: { mA: mA(active, dc), t: t(active, dc) },
        c3: { mA: mA(active, dc), t: t(active, dc) },
        c4: { mA: mA(active, dc), t: t(active, dc) },
        c5: { mA: mA(active, dc), t: t(active, dc) },
        c6: { mA: mA(active, dc), t: t(active, dc) },
      };
    }
    return sample;
  };
  useFrame(({ clock }, delta) => setSample(advanceSample(delta, sample)));

  // const sampleEvent = new CustomEvent('sample', { detail: { sample } });
  // document.dispatchEvent(sampleEvent);
  return sample;
};

快速回答是否定的,在这种情况下,useFrame、zustand 和 React.useState 都没有冲突

我的解决方案是通过 zustand 在每一帧上更新托管状态,并通过状态管理器传播状态,如下所示......存储方法:

// increment state
next: (duration) => {
  const { dc, metrics } = get().api;
  const [sample] = metrics.slice(-1);
  const ms = Date.now();
  if (get().api.status === 'open') {
    const radiansPerSecond = (sample.rpm / 60) * 2 * Math.PI;
    const radiansPerFrame = radiansPerSecond * duration;
    const r = (sample.r + radiansPerFrame) % (2 * Math.PI);
    const active = activeCoilIndex[r * (180 / Math.PI)];
    set({
      api: {
        ...get().api,
        metrics: [
          ...get().api.metrics,
          {
            ms,
            r,
            rpm: sample.rpm,
            ...range(1, 6).reduce((accumulator, coil, index) => ({ ...accumulator, [`c${coil}`]: { mA: mA(active, dc), t: t(active, dc) } }), {}),
          },
        ].slice(-512),
      },
    });
  }

然后在一个单独的组件中:

// increment state at generally 60fps
const nextSample = useStore((state) => state.api.next);
useFrame(({ clock }, delta) => nextSample(delta));

最后是传播:

// use current state within each component as follows
const unsubscribe = useStore.subscribe(
  (metrics) => {
    const [sample] = metrics.slice(-1);
    if (crankshaftRef.current) crankshaftRef.current.rotation.z = sample.r;

    const sqrRodLength = Math.pow(rodLength, 2);
    const sqrCrankRadius = Math.pow(crankRadius, 2);
    const calcMotion = (offsetRotation) => {
      const radians = sample.r + offsetRotation;
      const cosR = Math.cos(radians);
      const sinR = Math.sin(radians);
      const displacement = crankRadius * cosR + Math.sqrt(sqrRodLength - sqrCrankRadius * Math.pow(sinR, 2));
      const moment = Math.asin(crankRadius / (rodLength / sinR)) * -1;
      return [displacement, moment];
    };

    if (conrod1Ref.current && plunger1Ref.current) {
      const [displacement, moment] = calcMotion(pistonsModels['piston1'].offsetRotation);
      conrod1Ref.current.position.x = displacement;
      conrod1Ref.current.rotation.z = moment;
      plunger1Ref.current.position.x = displacement;
    }
    ...
  },
  (state) => state.api.metrics
);
4

0 回答 0