Skip to content

Commit cb2b83f

Browse files
committed
snowcap: lua: decoration: 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 d179078 commit cb2b83f

1 file changed

Lines changed: 110 additions & 51 deletions

File tree

snowcap/api/lua/snowcap/decoration.lua

Lines changed: 110 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,20 @@ local decoration_handle = {}
1515

1616
---@class snowcap.decoration.DecorationHandle
1717
---@field id integer
18-
---@field private update fun(msg: any)
18+
---@field private _update fun(msg: any)
19+
---@field private _operate fun(oper: snowcap.widget.operation.Operation)
1920
local DecorationHandle = {}
2021

2122
---@param id integer
2223
---@param update fun(msg: any?)
24+
---@param operate fun(oper: snowcap.widget.operation.Operation)
2325
---@return snowcap.decoration.DecorationHandle
24-
function decoration_handle.new(id, update)
26+
function decoration_handle.new(id, update, operate)
2527
---@type snowcap.decoration.DecorationHandle
2628
local self = {
2729
id = id,
28-
update = update,
30+
_update = update,
31+
_operate = operate,
2932
}
3033
setmetatable(self, { __index = DecorationHandle })
3134
return self
@@ -80,29 +83,30 @@ function decoration.new_widget(args)
8083

8184
local decoration_id = response.decoration_id or 0
8285

86+
local sender, receiver = require("snowcap.util.channel").spsc()
87+
8388
---@type fun(msg: any?)
8489
local update_on_msg = function(msg)
8590
if msg ~= nil then
86-
args.program:update(msg)
87-
end
88-
89-
---@diagnostic disable-next-line: redefined-local
90-
local _, err = client:snowcap_decoration_v1_DecorationService_RequestView({
91-
decoration_id = decoration_id,
92-
})
93-
94-
if err then
95-
log.error(err)
91+
sender:send({
92+
message = msg,
93+
})
94+
else
95+
sender:send({
96+
redraw = {}
97+
})
9698
end
9799
end
98100

99-
local handle = decoration_handle.new(decoration_id, update_on_msg)
100-
101101
---@type fun(oper: snowcap.widget.operation.Operation)
102-
local forward_operation = function(oper)
103-
handle:operate(oper)
102+
local operate_surface = function(oper)
103+
sender:send({
104+
operation = oper,
105+
})
104106
end
105107

108+
local handle = decoration_handle.new(decoration_id, update_on_msg, operate_surface)
109+
106110
---@type fun(): snowcap.signal.HandlerPolicy
107111
local close_surface = function()
108112
handle:close()
@@ -112,48 +116,110 @@ function decoration.new_widget(args)
112116

113117
args.program:connect(widget_signal.redraw_needed, update_on_msg)
114118
args.program:connect(widget_signal.send_message, update_on_msg)
115-
args.program:connect(widget_signal.operation, forward_operation)
119+
args.program:connect(widget_signal.operation, operate_surface)
116120
args.program:connect(widget_signal.request_close, close_surface)
117121

118122
args.program:event({
119123
created = widget.SurfaceHandle.from_decoration_handle(handle),
120124
})
121125

126+
err = client:snowcap_decoration_v1_DecorationService_GetDecorationEvents({
127+
decoration_id = decoration_id,
128+
}, function(response) ---@diagnostic disable-line:redefined-local
129+
sender:send({
130+
decoration_events = response.decoration_events or {}
131+
})
132+
end)
133+
122134
err = client:snowcap_widget_v1_WidgetService_GetWidgetEvents({
123135
decoration_id = decoration_id,
124136
}, function(response) ---@diagnostic disable-line: redefined-local
125-
for _, event in ipairs(response.widget_events) do
126-
---@diagnostic disable-next-line:invisible
127-
local msg = widget._message_from_event(callbacks, event)
128-
129-
if msg then
130-
local ok, update_err = pcall(function()
131-
args.program:update(msg)
132-
end)
133-
if not ok then
134-
log.error(update_err)
137+
sender:send({
138+
widget_events = response.widget_events or {}
139+
})
140+
end)
141+
142+
client.loop:wrap(function()
143+
local pending_operations = {}
144+
145+
local msg = receiver:recv()
146+
while msg do
147+
local update_view = false
148+
149+
if msg.widget_events then
150+
for _, event in ipairs(msg.widget_events) do
151+
---@diagnostic disable-next-line:invisible
152+
local msg = widget._message_from_event(callbacks, event)
153+
154+
if msg then
155+
local ok, update_err = pcall(function()
156+
args.program:update(msg)
157+
end)
158+
if not ok then
159+
log.error(update_err)
160+
end
161+
end
162+
end
163+
164+
update_view = true
165+
elseif msg.message then
166+
args.program:update(msg)
167+
elseif msg.operation then
168+
table.insert(pending_operations, msg.operation)
169+
elseif msg.decoration_events then
170+
for _, decoration_event in ipairs(msg.decoration_events) do
171+
if decoration_event.closing ~= nil then
172+
goto main_loop_break
173+
end
135174
end
136175
end
137-
end
138176

139-
---@diagnostic disable-next-line:redefined-local
140-
local widget_def = args.program:view() or widget.row({ children = {} })
141-
callbacks = {}
177+
if not update_view then
178+
---@diagnostic disable-next-line: redefined-local
179+
local _, err = client:snowcap_decoration_v1_DecorationService_RequestView({
180+
decoration_id = decoration_id,
181+
})
142182

143-
widget._traverse_widget_tree(widget_def, callbacks, widget._collect_callbacks)
183+
if err then
184+
log.error(err)
185+
end
186+
else
187+
---@diagnostic disable-next-line:redefined-local
188+
local widget_def = args.program:view() or widget.row({ children = {} })
189+
callbacks = {}
144190

145-
---@diagnostic disable-next-line:redefined-local
146-
local _, err = client:snowcap_decoration_v1_DecorationService_UpdateDecoration({
147-
decoration_id = decoration_id,
148-
widget_def = widget.widget_def_into_api(widget_def),
149-
})
191+
widget._traverse_widget_tree(widget_def, callbacks, widget._collect_callbacks)
150192

151-
if err then
152-
log.error(err)
193+
---@diagnostic disable-next-line:redefined-local
194+
local _, err = client:snowcap_decoration_v1_DecorationService_UpdateDecoration({
195+
decoration_id = decoration_id,
196+
widget_def = widget.widget_def_into_api(widget_def),
197+
})
198+
199+
if err then
200+
log.error(err)
201+
end
202+
203+
for _, oper in ipairs(pending_operations) do
204+
---@diagnostic disable-next-line:redefined-local
205+
local _, err = client:snowcap_decoration_v1_DecorationService_OperateDecoration({
206+
decoration_id = decoration_id,
207+
operation = require("snowcap.widget.operation")._to_api(oper), ---@diagnostic disable-line: invisible
208+
})
209+
210+
if err then
211+
log.error(err)
212+
end
213+
end
214+
pending_operations = {}
215+
end
216+
217+
msg = receiver:recv()
153218
end
154-
end, function()
219+
::main_loop_break::
220+
155221
args.program:event({
156-
closing = {},
222+
closing = {}
157223
})
158224
end)
159225

@@ -178,20 +244,13 @@ end
178244
---
179245
---@param message any
180246
function DecorationHandle:send_message(message)
181-
self.update(message)
247+
self._update(message)
182248
end
183249

184250
---Sends an `Operation` to this decoration.
185251
---@param operation snowcap.widget.operation.Operation
186252
function DecorationHandle:operate(operation)
187-
local _, err = client:snowcap_decoration_v1_DecorationService_OperateDecoration({
188-
decoration_id = self.id,
189-
operation = require("snowcap.widget.operation")._to_api(operation), ---@diagnostic disable-line: invisible
190-
})
191-
192-
if err then
193-
log.error(err)
194-
end
253+
self._operate(operation)
195254
end
196255

197256
---Sets the z-index at which this decoration will render.

0 commit comments

Comments
 (0)