[Defold] room.state.on_change does not get triggered on state change

Hi all,

I am seeing what seems to me to be inconsistent results when registering callbacks for room state changes.

Here I register a callback for base state changes, like in @endel's example here: https://github.com/endel/colyseus-tic-tac-toe/blob/master/defold/scripts/game_controller.script.

room.state['on_change'] = function (changes)
	for i, change in ipairs(changes) do
		print("base state change")

Here I register a callback for something more specific in the state:

room.state.players.on_add = function(player, sessionId)
	player.character.position.on_change = function(changes)
                local position = {}
		for i, change in ipairs(changes) do
			if change.field == "x" then
				position["x"] = change.value
			if change.field == "y" then
				position["y"] = change.value
                print("new position", inspect(changes))

I know that the state changes are successfully making it from one client, to the server, and back to the other client, because I see the player on one client move on the other (and also from the logs).

When one client moves, therefore updating the state, I see only messages from the more specific callbacks:

DEBUG:SCRIPT: new position	{
  x = 869.49731445313,
  y = 370.85791015625

However I expect to see not only that, but also a message like:

DEBUG:SCRIPT: base state change

Do I misunderstand how these callbacks work? I would assume that if I assign a callback to the root state, it would fire any time anything changes within that state (recursively).

Also, in the docs here (https://docs.colyseus.io/state/overview/) it says that binary patches of the state are sent to the client every 50ms. Is it the case that if there are no changes, it sends nothing, and therefore the callback doesn't fire?

One specific reason I want a generic callback is I want to estimate how often I receive updates from the client so I can lerp the other players' movements accurately, though I think having a base callback might be useful in other ways.

In short, I'm not quite sure how on_change works, and I can't really figure it out from the docs ("this event is triggered when the server updates its state", it doesn't really elaborate on which callbacks get called for which state).

Thanks everyone!

Hi @banool!

I think you're looking for room:on("statechange", ...): https://docs.colyseus.io/client/room/#onstatechange

Thanks for the observation about the documentation, it is not clear indeed. The "on change" callbacks are not recursive since colyseus@0.14 - the new schema implementation introduced a concept of refId and instance references. Changes are triggered directly and only on the instance in which a change has happened by their refId.

I hope this helps! Feel free to post any issues and/or suggestions here or on Discord! Cheers!


Thanks for the super quick response. I see different options in different places:

  • room.state.on_change
  • room.state["on_change"]
  • room:on("statechange")

I'll go with the latter from now on.

Good to know that the on change callbacks are not recursive. I can put up a PR to make this a bit clearer on the docs.


No worries! In fact room.state.on_change and room.state["on_change"] are just different in syntax, they are actually equal!

I can put up a PR to make this a bit clearer on the docs.

That'd be much appreciated! 🙏