当前位置: 首页 > article >正文

Colyseus的room.onStateChange重复触发问题

一个简单的Colyseus应用的例子,服务端程序如下:

SceneRoom.ts:
 

import { Room, Client } from "colyseus";
import { Schema, type } from "@colyseus/schema";

// 定义状态
class SceneState extends Schema {
  @type("string")
  sceneId: string = "001";
}

export class SceneRoom extends Room<SceneState> {
  onCreate() {
    this.setState(new SceneState()); // 设置初始状态

    // 监听来自客户端的消息
    this.onMessage("changeScene", (client, newSceneId: string) => {
      if (this.state.sceneId !== newSceneId) {
        // 避免重复更新
        this.state.sceneId = newSceneId;
        console.log(
          `Client ${client.sessionId} changed sceneId to: ${newSceneId}`
        );
      }
    });
  }

  onJoin(client: Client) {
    console.log(`Client joined: ${client.sessionId}`);
  }

  onLeave(client: Client) {
    console.log(`Client left: ${client.sessionId}`);
  }

  onDispose() {
    console.log("Room disposed");
  }
}

设了一个状态变量sceneId: string = "001";

客户端程序ColyseusTest.vue:
 

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import * as Colyseus from 'colyseus.js';

const client = ref<Colyseus.Client | null>(null);
const room = ref<Colyseus.Room | null>(null);
const currentSceneId = ref<string>('未连接');
const newSceneId = ref<string>('');
const connectionStatus = ref<string>('未连接');

const connectToRoom = async () => {
  connectionStatus.value = '正在连接...';
  client.value = new Colyseus.Client('ws://localhost:5173/ws');

  try {
    room.value = await client.value.joinOrCreate('scene_room');
    connectionStatus.value = '已连接';

    // 初始状态同步
    currentSceneId.value = room.value.state.sceneId;

    // 使用 onStateChange 监听整个状态变化
    let lastState = JSON.stringify({});
    room.value.onStateChange((state) => {
      const currentState = JSON.stringify(state);
      if (currentState === lastState) return; // 忽略重复状态
      lastState = currentState;

      console.log('State updated:', state);
      console.log('Raw state:', JSON.stringify(state));
    });

    console.log('Connected to room:', room.value);
  } catch (error) {
    console.error('Failed to connect to room:', error);
    connectionStatus.value = '连接失败';
  }
};

const changeScene = () => {
  if (room.value) {
    room.value.send('changeScene', newSceneId.value);
    console.log('Sent new Scene ID:', newSceneId.value);
  } else {
    console.error('Room not connected');
  }
};

onMounted(() => {
  connectToRoom();
});
</script>


<template>
  <div class="colyseus-client">
    <h1>Colyseus Vue Client</h1>
    <p>
      当前场景 ID: <strong>{{ currentSceneId }}</strong>
    </p>
    <div>
      <input v-model="newSceneId" placeholder="输入新的场景 ID" type="text" />
      <button @click="changeScene">修改场景 ID</button>
    </div>
    <p v-if="connectionStatus">状态: {{ connectionStatus }}</p>
  </div>
</template>

用两个不同的浏览器分别打开了这个测试页面。 其中一个浏览器页面进行了场景ID的修改,其控制台输出如下:

Connected to room: Proxy(_Room) {hasJoined: true, onStateChange: ƒ, onError: ƒ, onLeave: ƒ, onJoin: ƒ, …}
ColyseusTest.vue:29 State updated: _ {$changes: ChangeTree2, $callbacks: undefined, _sceneId: '001', onChange: ƒ}
ColyseusTest.vue:30 Raw state: {"sceneId":"001"}
ColyseusTest.vue:53 Sent new Scene ID: 002
ColyseusTest.vue:29 State updated: _ {$changes: ChangeTree2, $callbacks: undefined, _sceneId: '002', onChange: ƒ}
ColyseusTest.vue:30 Raw state: {"sceneId":"002"}
ColyseusTest.vue:29 State updated: _ {$changes: ChangeTree2, $callbacks: undefined, _sceneId: '002', onChange: ƒ}
ColyseusTest.vue:30 Raw state: {"sceneId":"002"}
ColyseusTest.vue:53 Sent new Scene ID: 003
ColyseusTest.vue:29 State updated: _ {$changes: ChangeTree2, $callbacks: undefined, _sceneId: '003', onChange: ƒ}
ColyseusTest.vue:30 Raw state: {"sceneId":"003"}
ColyseusTest.vue:29 State updated: _ {$changes: ChangeTree2, $callbacks: undefined, _sceneId: '003', onChange: ƒ}
ColyseusTest.vue:30 Raw state: {"sceneId":"003"}
ColyseusTest.vue:53 Sent new Scene ID: 004
ColyseusTest.vue:29 State updated: _ {$changes: ChangeTree2, $callbacks: undefined, _sceneId: '004', onChange: ƒ}
ColyseusTest.vue:30 Raw state: {"sceneId":"004"}
ColyseusTest.vue:29 State updated: _ {$changes: ChangeTree2, $callbacks: undefined, _sceneId: '004', onChange: ƒ}
ColyseusTest.vue:30 Raw state: {"sceneId":"004"}

 服务器输出如下:

node-1   | Client joined: 9N5h-irJg
node-1   | Client joined: soEPwRQay
node-1   | Client soEPwRQay changed sceneId to: 002
node-1   | Client soEPwRQay changed sceneId to: 003
node-1   | Client soEPwRQay changed sceneId to: 004

 另外一个客户端控制台输出如下:

Connected to room: 
Proxy { <target>: {…}, <handler>: {…} }
ColyseusTest.vue:43:12
State updated: 
Object { sceneId: Getter & Setter, onChange: onChange(changes)
, … }
ColyseusTest.vue:29:14
Raw state: {"sceneId":"001"} ColyseusTest.vue:30:14
State updated: 
Object { sceneId: Getter & Setter, onChange: onChange(changes)
, … }
ColyseusTest.vue:29:14
Raw state: {"sceneId":"002"} ColyseusTest.vue:30:14
State updated: 
Object { sceneId: Getter & Setter, onChange: onChange(changes)
, … }
ColyseusTest.vue:29:14
Raw state: {"sceneId":"002"} ColyseusTest.vue:30:14
State updated: 
Object { sceneId: Getter & Setter, onChange: onChange(changes)
, … }
ColyseusTest.vue:29:14
Raw state: {"sceneId":"003"} ColyseusTest.vue:30:14
State updated: 
Object { sceneId: Getter & Setter, onChange: onChange(changes)
, … }
ColyseusTest.vue:29:14
Raw state: {"sceneId":"003"} ColyseusTest.vue:30:14
State updated: 
Object { sceneId: Getter & Setter, onChange: onChange(changes)
, … }
ColyseusTest.vue:29:14
Raw state: {"sceneId":"004"} ColyseusTest.vue:30:14
State updated: 
Object { sceneId: Getter & Setter, onChange: onChange(changes)
, … }
ColyseusTest.vue:29:14
Raw state: {"sceneId":"004"}

由上述输出可见,每次修改sceneId, 函数onStateChange都被触发了两次。

我猜测这可能是Colyseus 的“增量补丁”多次触发 onStateChange。

解决方案是采用字段级的更新监听。 通常情况下,Colyseus 的 onChange / listen API 可以实现更轻量级、局部性的更新监听(只在真正有变更的字段时处理),也就不会出现多次全量触发的问题。
具体到这个例子里,就是直接监听sceneId,就不会重复触发了:
 

    // 使用字段级别的监听
    room.value.state.listen('sceneId', (newValue, oldValue) => {
      currentSceneId.value = newValue
      console.log('Scene ID changed from', oldValue, 'to', newValue)
    })

总之,序列化整个 State 对象在 State 比较庞大时可能会有性能开销。使用更“精确监听”方式,能够提高性能且不会出现重复触发的情况。


http://www.kler.cn/a/458510.html

相关文章:

  • YOLOv5部署到web端(flask+js简单易懂)
  • 常见端口(22、25、53、80、443、110、143、3306、6379、21)和服务的安装与配置手册
  • css 关于flex布局中子元素的属性flex
  • WPF中的Microsoft XAML Behaviors包功能详解
  • pandas删除值全部为0的整行和整列,还有0.0,0.000000也要删除
  • Redis--持久化策略(AOF与RDB)
  • Redis 集群架构:高可用与扩展性
  • 苍穹外卖day07缓存部分分析
  • 深入理解 Docker 网桥配置与网络管理
  • C#编写的金鱼趣味小应用 - 开源研究系列文章
  • 博通收购VMware后,新旧VMware兼容性列表查询方案对比
  • 未来网络技术的新征程:5G、物联网与边缘计算(10/10)
  • 【小程序】wxss与rpx单位以及全局样式和局部样式
  • PG备份恢复--pg_dump
  • SpringBoot -- Docker Compose的支持
  • RK356x bsp 7 - PCF8563 RTC调试记录
  • Unity 读Excel,读取xlsx文件解决方案
  • 【Rabbitmq篇】高级特性----事务,消息分发
  • 【每日学点鸿蒙知识】Web跳转系统应用、页面动态跳转、非UI中观测变化、MVVM模式、循环中使用定时问题
  • .net core 的计算机基础
  • B站推荐模型数据流的一致性架构
  • MetaRename for Mac,适用于 Mac 的文件批量重命名工具
  • 抽象工厂设计模式的理解和实践
  • C++Primer 控制流
  • Element-ui的使用教程 基于HBuilder X
  • 数据仓库工具箱—读书笔记02(Kimball维度建模技术概述04、使用一致性维度集成)