Skip to content

Commit d179078

Browse files
committed
snowcap: lua: popup: enforce sequential handling of events
On the rust-side, we had to ensure operations went through the message channel so they can't be sent to the server while an update is in progress. This avoid cases where we're sending a focus event as part of an update, but the widget we're trying to focus has not yet been seen. This commit implements the same logic on lua, which needed refactoring the code to have the same kind of "mainloop" we have in rust. The event stream is implemented using the newly added `snowcap.util.channel` which uses cqueues promise to have an async channel/stream.
1 parent 840d36f commit d179078

1 file changed

Lines changed: 116 additions & 76 deletions

File tree

snowcap/api/lua/snowcap/popup.lua

Lines changed: 116 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ popup.parent = parent
6666
---@class snowcap.popup.PopupHandle
6767
---@field id integer Popup's id.
6868
---@field private _update fun(msg:any)
69+
---@field private _operate fun(oper: snowcap.widget.operation.Operation)
6970
local PopupHandle = {}
7071

7172
---Convert a PopupHandle into a Popup's ParentHandle
@@ -79,12 +80,14 @@ end
7980
---@package
8081
---@param id integer
8182
---@param update fun(msg: any)
83+
---@param operate fun(oper: snowcap.widget.operation.Operation)
8284
---@return snowcap.popup.PopupHandle
83-
function popup_handle.new(id, update)
85+
function popup_handle.new(id, update, operate)
8486
---@type snowcap.popup.PopupHandle
8587
local self = {
8688
id = id,
8789
_update = update,
90+
_operate = operate
8891
}
8992
setmetatable(self, { __index = PopupHandle })
9093
return self
@@ -265,29 +268,30 @@ function popup.new_widget(args)
265268

266269
local popup_id = response.popup_id --[[@as integer]]
267270

271+
local sender, receiver = require("snowcap.util.channel").spsc()
272+
268273
---@type fun(msg: any?)
269274
local update_on_msg = function(msg)
270275
if msg ~= nil then
271-
args.program:update(msg)
272-
end
273-
274-
---@diagnostic disable-next-line: redefined-local
275-
local _, err = client:snowcap_popup_v1_PopupService_RequestView({
276-
popup_id = popup_id,
277-
})
278-
279-
if err then
280-
log.error(err)
276+
sender:send({
277+
message = msg
278+
})
279+
else
280+
sender:send({
281+
redraw = {}
282+
})
281283
end
282284
end
283285

284-
local handle = popup_handle.new(popup_id, update_on_msg)
285-
286286
---@type fun(oper: snowcap.widget.operation.Operation)
287-
local forward_operation = function(oper)
288-
handle:operate(oper)
287+
local operate_surface = function(oper)
288+
sender:send({
289+
operation = oper,
290+
})
289291
end
290292

293+
local handle = popup_handle.new(popup_id, update_on_msg, operate_surface)
294+
291295
---@type fun(): snowcap.signal.HandlerPolicy
292296
local close_surface = function()
293297
handle:close()
@@ -297,7 +301,7 @@ function popup.new_widget(args)
297301

298302
args.program:connect(widget_signal.redraw_needed, update_on_msg)
299303
args.program:connect(widget_signal.send_message, update_on_msg)
300-
args.program:connect(widget_signal.operation, forward_operation)
304+
args.program:connect(widget_signal.operation, operate_surface)
301305
args.program:connect(widget_signal.request_close, close_surface)
302306

303307
args.program:event({
@@ -307,73 +311,116 @@ function popup.new_widget(args)
307311
err = client:snowcap_popup_v1_PopupService_GetPopupEvents({
308312
popup_id = popup_id,
309313
}, function(response) ---@diagnostic disable-line:redefined-local
310-
response.popup_events = response.popup_events or {}
311-
312-
for _, popup_event in ipairs(response.popup_events) do
313-
local focus = popup_event.focus --[[@as snowcap.popup.FocusEvent]]
314-
---@type snowcap.widget.SurfaceEvent?
315-
local event = nil
316-
317-
if focus == focus_event.GAINED then
318-
event = {
319-
focus_gained = {},
320-
}
321-
elseif focus == focus_event.LOST then
322-
event = {
323-
focus_lost = {},
324-
}
325-
end
326-
327-
if event then
328-
args.program:event(event)
329-
end
330-
end
331-
332-
---@diagnostic disable-next-line: redefined-local
333-
local _, err = client:snowcap_popup_v1_PopupService_RequestView({
334-
popup_id = popup_id,
314+
sender:send({
315+
popup_events = response.popup_events or {}
335316
})
336-
337-
if err then
338-
log.error(err)
339-
end
340317
end)
341318

342319
err = client:snowcap_widget_v1_WidgetService_GetWidgetEvents({
343320
popup_id = popup_id,
344321
}, function(response) ---@diagnostic disable-line:redefined-local
345-
for _, event in ipairs(response.widget_events) do
346-
local msg = widget._message_from_event(callbacks, event)
347-
348-
if msg then
349-
local ok, update_err = pcall(function()
350-
args.program:update(msg)
351-
end)
322+
sender:send({
323+
widget_events = response.widget_events or {}
324+
})
325+
end)
352326

353-
if not ok then
354-
log.error(update_err)
327+
client.loop:wrap(function()
328+
local pending_operations = {}
329+
330+
local msg = receiver:recv()
331+
while msg do
332+
local update_view = false
333+
334+
if msg.widget_events then
335+
for _, event in ipairs(msg.widget_events) do
336+
local message = widget._message_from_event(callbacks, event)
337+
338+
if message then
339+
local ok, update_err = pcall(function()
340+
args.program:update(message)
341+
end)
342+
if not ok then
343+
log.error(update_err)
344+
end
345+
end
346+
end
347+
update_view = true
348+
elseif msg.message then
349+
args.program:update(msg.message)
350+
elseif msg.operation then
351+
table.insert(pending_operations, msg.operation)
352+
elseif msg.popup_events then
353+
for _, popup_event in ipairs(msg.popup_events) do
354+
if popup_event.closing ~= nil then
355+
goto main_loop_break
356+
end
357+
358+
local focus = popup_event.focus --[[@as snowcap.popup.FocusEvent]]
359+
---@type snowcap.widget.SurfaceEvent?
360+
local event = nil
361+
362+
if focus == focus_event.GAINED then
363+
event = {
364+
focus_gained = {},
365+
}
366+
elseif focus == focus_event.LOST then
367+
event = {
368+
focus_lost = {},
369+
}
370+
end
371+
372+
if event then
373+
args.program:event(event)
374+
end
355375
end
356376
end
357-
end
358377

359-
---@diagnostic disable-next-line:redefined-local
360-
local widget_def = args.program:view() or widget.row({ children = {} })
361-
callbacks = {}
378+
if not update_view then
379+
---@diagnostic disable-next-line: redefined-local
380+
local _, err = client:snowcap_popup_v1_PopupService_RequestView({
381+
popup_id = popup_id,
382+
})
362383

363-
widget._traverse_widget_tree(widget_def, callbacks, widget._collect_callbacks)
384+
if err then
385+
log.error(err)
386+
end
387+
else
388+
---@diagnostic disable-next-line:redefined-local
389+
local widget_def = args.program:view() or widget.row({ children = {} })
390+
callbacks = {}
364391

365-
---@diagnostic disable-next-line:redefined-local
366-
local _, err = client:snowcap_popup_v1_PopupService_UpdatePopup({
367-
popup_id = popup_id,
368-
widget_def = widget.widget_def_into_api(widget_def),
369-
})
392+
widget._traverse_widget_tree(widget_def, callbacks, widget._collect_callbacks)
393+
394+
---@diagnostic disable-next-line:redefined-local
395+
local _, err = client:snowcap_popup_v1_PopupService_UpdatePopup({
396+
popup_id = popup_id,
397+
widget_def = widget.widget_def_into_api(widget_def),
398+
})
399+
400+
if err then
401+
log.error(err)
402+
end
403+
404+
for _, oper in pairs(pending_operations) do
405+
---@diagnostic disable-next-line:redefined-local
406+
local _, err = client:snowcap_popup_v1_PopupService_OperatePopup({
407+
popup_id = popup_id,
408+
operation = require("snowcap.widget.operation")._to_api(oper), ---@diagnostic disable-line: invisible
409+
})
410+
411+
if err then
412+
log.error(err)
413+
end
414+
end
415+
pending_operations = {}
416+
end
370417

371-
if err then
372-
log.error(err)
418+
msg = receiver:recv()
373419
end
374-
end, function()
420+
::main_loop_break::
421+
375422
args.program:event({
376-
closing = {},
423+
closing = {}
377424
})
378425
end)
379426

@@ -429,14 +476,7 @@ end
429476
---Sends an `Operation` to this popup.
430477
---@param operation snowcap.widget.operation.Operation
431478
function PopupHandle:operate(operation)
432-
local _, err = client:snowcap_popup_v1_PopupService_OperatePopup({
433-
popup_id = self.id,
434-
operation = require("snowcap.widget.operation")._to_api(operation), ---@diagnostic disable-line: invisible
435-
})
436-
437-
if err then
438-
log.error(err)
439-
end
479+
self._operate(operation)
440480
end
441481

442482
---PopupHandle:popup parameters.

0 commit comments

Comments
 (0)