Timer.delay and message passing order

I’m working on a really simple thing - a settings menu - but having some problems with timing.
I can easily solve it by breaking a rule I’ve set for myself - I don’t want to store the same state in multiple places.

The task is - show a toggle button that reflects the current state. Update the state when pressed, read it back, and update the button text.

My first idea was simple: send a message to the audio controller to toggle its state, and read the state on the next frame:

(on_input):

msg.post("world_view:/audio#audio", "toggle_sfx")
timer.delay(0, false, update_sfx_label)

This didn’t work though. It seems that timer.delay(0) from on_input is processed on the same frame.

Next I tried a small non-zero delay. That worked most of the time, but occasionally it processed it the same frame. I could increase the delay, but a value that feels truly safe will feel laggy.

Then I tried introducing a delay by sending a message to the script:

msg.post("world_view:/audio#audio", "toggle_sfx")
msg.post(".", "update_sfx_label")

But it seems that messages sent to the current game object are always processed before others.

I can probably solve this with some additional layering of timers and messages, but is there a more elegant solution?

I wish, for example, that there was a variant of timer.delay() that allowed me to specify an integer number of frames. It would be no additional code - timer.delay_frames(2) - and the purpose of the magic number 2 could be explained in a comment.

Hmm, I was sure this would happen on the next frame! Strange…

You could let the audio controller send back a message about its new state to the sender:

-- settings.gui_script
function on_input(self, action_id, action)
	if action_id == hash("click") and action.pressed and gui.pick_node(gui.get_node("sound_toggle_button"), action.x, action.y) then
		msg.post("world_view:/audio#audio", "toggle_sfx")
	end
end

function on_message(self, message_id, message, sender)
	if message_id == hash("sound_state_updated") then
		print("Sound is " .. (message.sound and "on" or "off"))
	end
end
-- audio_controller.script
go.property("sound", true)

function on_message(self, message_id, message, sender)
	if message_id == hash("toggle_sfx") then
		self.sound = not self.sound
		msg.post(sender, "sound_state_updated", { sound = self.sound })
	end
end

It’s an extra message but it’s not that bad.

Why do you need it to be delayed at all? Shouldn’t the visual text and the button’s effect happen immediately?

I don’t want it to be delayed, but I’m struggling to find an elegant solution to have it update immediately.

I can make it work by retrieving the current state and storing it locally, and it’s not terrible, but I’d like to increase my knowledge of the engine by not doing that.

I could state the question more generally - in on_input(), how do I make something happen on the next frame?

There’s no API in Defold which allows you to do this currently, but perhaps it should be a function on the timer?

Fyi, the messages are flushed between the updates of each component type, that’s why you don’t see it “the next frame”.

You’ll only get one call to the “update()” function per frame.
So if you want to queue something for the next frame, add it to a table/list, and flush those items during the next update call.

Are you able to confirm my observation that messages are dispatched in the order specified, but that messages posted to the current game object are always dispatched first.

Yes, the message queues are first-in-first out.

but that messages posted to the current game object are always dispatched first.

No, the messages use the same queue.
If you think about it, they cannot all be first.

I didn’t literally mean first, more that it appeared there might be some special processing, e.g. dispatched immediately. Anyway, it appears not.

The behaviour I’ve observed is, I think, because of you other statement:

The messages were posted from a GUI script, which I guess for this purpose is a different component type to a GO script.
Are component types processed in a predictable order or is it subject to change?

The component types are always updated in the same order.

2 Likes