colyseus / colyseus

⚔ Multiplayer Framework for Node.js
https://colyseus.io
MIT License
5.8k stars 549 forks source link

[Bug]: Uncaught Error: ChangeTree: missing index for field "undefined" #689

Open WeylonSantana opened 8 months ago

WeylonSantana commented 8 months ago

Context

I'm getting this uncaught error on client side, caused by a Map Schema (or a map inside another map maybe)

Bug description

obtain instances of schemas through map get, and modify their values (or in my case, specifically the name: string or revealed:boolean value, causes an error on the client side, it is not even necessary to send a packet to the client, the error happens instantly when modifying the property

Uncaught Error: ChangeTree: missing index for field "undefined"
    at ChangeTree2.assertValidIndex (colyseus__js.js?v=5fb6481a:1048:21)
    at ChangeTree2.touch (colyseus__js.js?v=5fb6481a:956:18)
    at ChangeTree2.touchParents (colyseus__js.js?v=5fb6481a:965:39)
    at ChangeTree2.change (colyseus__js.js?v=5fb6481a:952:18)
    at _.set [as name] (colyseus__js.js?v=5fb6481a:1840:31)
    at Schema2.decode (colyseus__js.js?v=5fb6481a:2928:34)
    at SchemaSerializer.patch (colyseus__js.js?v=5fb6481a:4344:27)
    at _Room.patch (colyseus__js.js?v=5fb6481a:3698:25)
    at _Room.onMessageCallback (colyseus__js.js?v=5fb6481a:3681:16)

Reproduction

i have this code

async changePlayerCards(reveals: boolean = false, targets?: Array<Target>) {
    const { selectedTargets } = this.state;
    if (!selectedTargets?.length && !targets) return;
    const realTargets = targets ?? selectedTargets;

    const deckTarget = realTargets.find((t) => t.playerId === 'deck');
    if (deckTarget) {
      const target = realTargets.find((t) => t.playerId !== 'deck');
      if (!target) return;
      const targetPlayer = this.state.GetPlayerById(target.playerId);
      const targetCard = targetPlayer?.cards.get(target.cardId);
      if (!targetCard) return;

      if (reveals) await this.killHero(target);

      this.state.deck.push(targetCard.name);
      targetCard.name = this.state.deck.shift()  // #### <<===== error happens in this line
      this.state.addTask(new Task(TaskType.ChangeCard, { sourceId: target.cardId, targetId: 'deck' }));
    }

//rest of code
}

Environment & Versions

Colyseus.js: 0.15.17

Workarounds

When obtaining the instance of schema through get

do not change its values

instead const targetCard = targetPlayer?.cards.get(target.cardId); targetCard.name = this.state.deck.shift()!

do a re-set of value again, my solution: targetPlayer?.cards.set(target.cardId, new Card(this.state.deck.shift()!, false));

with that i have no problems for now.

PhuocTri0106and1608 commented 3 weeks ago

I encountered a problem where game states were being shared between different game rooms in Colyseus. The root cause was that variables such as NORMAL_PATH, BOT_LEFT_GOAL, TOP_LEFT_GOAL, BOT_RIGHT_GOAL, and TOP_RIGHT_GOAL were defined as constant arrays, and they were being shared across all rooms. image To fix this issue, I ensured that each game room has its own independent instance of these variables. Instead of using shared constants, I created new instances of the arrays for each room when the game is initialized. For example, you can clone arrays using methods like Array.from() or the spread operator to ensure each room has its own copy: image image This approach prevents multiple rooms from sharing the same array instance, avoiding potential overwrites or conflicts between different game sessions.

I think my solution can be help you