diff options
author | Ryan Dwyer <ryandwyer1@gmail.com> | 2018-06-23 16:24:11 +1000 |
---|---|---|
committer | Ryan Dwyer <ryandwyer1@gmail.com> | 2018-06-23 16:24:11 +1000 |
commit | 38398e2d77d57dc06b67ec88a54091c897915602 (patch) | |
tree | c80935807865fd96ab7d037070287d4dfaba1863 /sway/desktop/transaction.c | |
parent | Preserve buffers during transactions (diff) | |
download | sway-38398e2d77d57dc06b67ec88a54091c897915602.tar.gz sway-38398e2d77d57dc06b67ec88a54091c897915602.tar.zst sway-38398e2d77d57dc06b67ec88a54091c897915602.zip |
Implement atomic layout updates for tree operations
This implements atomic layout updates for when views map, reparent or
unmap.
Diffstat (limited to 'sway/desktop/transaction.c')
-rw-r--r-- | sway/desktop/transaction.c | 176 |
1 files changed, 114 insertions, 62 deletions
diff --git a/sway/desktop/transaction.c b/sway/desktop/transaction.c index 77377a18..6e09537a 100644 --- a/sway/desktop/transaction.c +++ b/sway/desktop/transaction.c | |||
@@ -9,6 +9,7 @@ | |||
9 | #include "sway/output.h" | 9 | #include "sway/output.h" |
10 | #include "sway/tree/container.h" | 10 | #include "sway/tree/container.h" |
11 | #include "sway/tree/view.h" | 11 | #include "sway/tree/view.h" |
12 | #include "sway/tree/workspace.h" | ||
12 | #include "list.h" | 13 | #include "list.h" |
13 | #include "log.h" | 14 | #include "log.h" |
14 | 15 | ||
@@ -18,6 +19,13 @@ | |||
18 | */ | 19 | */ |
19 | #define TIMEOUT_MS 200 | 20 | #define TIMEOUT_MS 200 |
20 | 21 | ||
22 | /** | ||
23 | * If enabled, sway will always wait for the transaction timeout before | ||
24 | * applying it, rather than applying it when the views are ready. This allows us | ||
25 | * to observe the rendered state while a transaction is in progress. | ||
26 | */ | ||
27 | #define TRANSACTION_DEBUG false | ||
28 | |||
21 | struct sway_transaction { | 29 | struct sway_transaction { |
22 | struct wl_event_source *timer; | 30 | struct wl_event_source *timer; |
23 | list_t *instructions; // struct sway_transaction_instruction * | 31 | list_t *instructions; // struct sway_transaction_instruction * |
@@ -29,7 +37,9 @@ struct sway_transaction_instruction { | |||
29 | struct sway_transaction *transaction; | 37 | struct sway_transaction *transaction; |
30 | struct sway_container *container; | 38 | struct sway_container *container; |
31 | struct sway_container_state state; | 39 | struct sway_container_state state; |
40 | struct wlr_buffer *saved_buffer; | ||
32 | uint32_t serial; | 41 | uint32_t serial; |
42 | bool ready; | ||
33 | }; | 43 | }; |
34 | 44 | ||
35 | struct sway_transaction *transaction_create() { | 45 | struct sway_transaction *transaction_create() { |
@@ -40,44 +50,55 @@ struct sway_transaction *transaction_create() { | |||
40 | return transaction; | 50 | return transaction; |
41 | } | 51 | } |
42 | 52 | ||
53 | static void remove_saved_view_buffer( | ||
54 | struct sway_transaction_instruction *instruction) { | ||
55 | if (instruction->saved_buffer) { | ||
56 | wlr_buffer_unref(instruction->saved_buffer); | ||
57 | instruction->saved_buffer = NULL; | ||
58 | } | ||
59 | } | ||
60 | |||
61 | static void save_view_buffer(struct sway_view *view, | ||
62 | struct sway_transaction_instruction *instruction) { | ||
63 | if (!sway_assert(instruction->saved_buffer == NULL, | ||
64 | "Didn't expect instruction to have a saved buffer already")) { | ||
65 | remove_saved_view_buffer(instruction); | ||
66 | } | ||
67 | if (view->surface && wlr_surface_has_buffer(view->surface)) { | ||
68 | wlr_buffer_ref(view->surface->buffer); | ||
69 | instruction->saved_buffer = view->surface->buffer; | ||
70 | } | ||
71 | } | ||
72 | |||
43 | static void transaction_destroy(struct sway_transaction *transaction) { | 73 | static void transaction_destroy(struct sway_transaction *transaction) { |
44 | int i; | ||
45 | // Free instructions | 74 | // Free instructions |
46 | for (i = 0; i < transaction->instructions->length; ++i) { | 75 | for (int i = 0; i < transaction->instructions->length; ++i) { |
47 | struct sway_transaction_instruction *instruction = | 76 | struct sway_transaction_instruction *instruction = |
48 | transaction->instructions->items[i]; | 77 | transaction->instructions->items[i]; |
49 | if (instruction->container->type == C_VIEW) { | 78 | struct sway_container *con = instruction->container; |
50 | struct sway_view *view = instruction->container->sway_view; | 79 | for (int j = 0; j < con->instructions->length; ++j) { |
51 | for (int j = 0; j < view->instructions->length; ++j) { | 80 | if (con->instructions->items[j] == instruction) { |
52 | if (view->instructions->items[j] == instruction) { | 81 | list_del(con->instructions, j); |
53 | list_del(view->instructions, j); | 82 | break; |
54 | break; | ||
55 | } | ||
56 | } | 83 | } |
57 | } | 84 | } |
85 | if (con->destroying && !con->instructions->length) { | ||
86 | container_free(con); | ||
87 | } | ||
88 | remove_saved_view_buffer(instruction); | ||
58 | free(instruction); | 89 | free(instruction); |
59 | } | 90 | } |
60 | list_free(transaction->instructions); | 91 | list_free(transaction->instructions); |
61 | 92 | ||
62 | // Free damage | 93 | // Free damage |
63 | for (i = 0; i < transaction->damage->length; ++i) { | 94 | list_foreach(transaction->damage, free); |
64 | struct wlr_box *box = transaction->damage->items[i]; | ||
65 | free(box); | ||
66 | } | ||
67 | list_free(transaction->damage); | 95 | list_free(transaction->damage); |
68 | 96 | ||
69 | free(transaction); | 97 | free(transaction); |
70 | } | 98 | } |
71 | 99 | ||
72 | void transaction_add_container(struct sway_transaction *transaction, | 100 | static void copy_pending_state(struct sway_container *container, |
73 | struct sway_container *container) { | 101 | struct sway_container_state *state) { |
74 | struct sway_transaction_instruction *instruction = | ||
75 | calloc(1, sizeof(struct sway_transaction_instruction)); | ||
76 | instruction->transaction = transaction; | ||
77 | instruction->container = container; | ||
78 | |||
79 | // Copy the container's main (pending) properties into the instruction state | ||
80 | struct sway_container_state *state = &instruction->state; | ||
81 | state->layout = container->layout; | 102 | state->layout = container->layout; |
82 | state->swayc_x = container->x; | 103 | state->swayc_x = container->x; |
83 | state->swayc_y = container->y; | 104 | state->swayc_y = container->y; |
@@ -87,6 +108,7 @@ void transaction_add_container(struct sway_transaction *transaction, | |||
87 | state->current_gaps = container->current_gaps; | 108 | state->current_gaps = container->current_gaps; |
88 | state->gaps_inner = container->gaps_inner; | 109 | state->gaps_inner = container->gaps_inner; |
89 | state->gaps_outer = container->gaps_outer; | 110 | state->gaps_outer = container->gaps_outer; |
111 | state->parent = container->parent; | ||
90 | 112 | ||
91 | if (container->type == C_VIEW) { | 113 | if (container->type == C_VIEW) { |
92 | struct sway_view *view = container->sway_view; | 114 | struct sway_view *view = container->sway_view; |
@@ -101,8 +123,44 @@ void transaction_add_container(struct sway_transaction *transaction, | |||
101 | state->border_left = view->border_left; | 123 | state->border_left = view->border_left; |
102 | state->border_right = view->border_right; | 124 | state->border_right = view->border_right; |
103 | state->border_bottom = view->border_bottom; | 125 | state->border_bottom = view->border_bottom; |
126 | } else if (container->type == C_WORKSPACE) { | ||
127 | state->ws_fullscreen = container->sway_workspace->fullscreen; | ||
128 | state->ws_floating = container->sway_workspace->floating; | ||
129 | state->children = create_list(); | ||
130 | list_cat(state->children, container->children); | ||
131 | } else { | ||
132 | state->children = create_list(); | ||
133 | list_cat(state->children, container->children); | ||
134 | } | ||
135 | } | ||
136 | |||
137 | static bool transaction_has_container(struct sway_transaction *transaction, | ||
138 | struct sway_container *container) { | ||
139 | for (int i = 0; i < transaction->instructions->length; ++i) { | ||
140 | struct sway_transaction_instruction *instruction = | ||
141 | transaction->instructions->items[i]; | ||
142 | if (instruction->container == container) { | ||
143 | return true; | ||
144 | } | ||
145 | } | ||
146 | return false; | ||
147 | } | ||
148 | |||
149 | void transaction_add_container(struct sway_transaction *transaction, | ||
150 | struct sway_container *container) { | ||
151 | if (transaction_has_container(transaction, container)) { | ||
152 | return; | ||
104 | } | 153 | } |
154 | struct sway_transaction_instruction *instruction = | ||
155 | calloc(1, sizeof(struct sway_transaction_instruction)); | ||
156 | instruction->transaction = transaction; | ||
157 | instruction->container = container; | ||
158 | |||
159 | copy_pending_state(container, &instruction->state); | ||
105 | 160 | ||
161 | if (container->type == C_VIEW) { | ||
162 | save_view_buffer(container->sway_view, instruction); | ||
163 | } | ||
106 | list_add(transaction->instructions, instruction); | 164 | list_add(transaction->instructions, instruction); |
107 | } | 165 | } |
108 | 166 | ||
@@ -113,47 +171,29 @@ void transaction_add_damage(struct sway_transaction *transaction, | |||
113 | list_add(transaction->damage, box); | 171 | list_add(transaction->damage, box); |
114 | } | 172 | } |
115 | 173 | ||
116 | static void save_view_buffer(struct sway_view *view) { | ||
117 | if (view->saved_buffer) { | ||
118 | wlr_buffer_unref(view->saved_buffer); | ||
119 | } | ||
120 | wlr_buffer_ref(view->surface->buffer); | ||
121 | view->saved_buffer = view->surface->buffer; | ||
122 | view->saved_surface_width = view->surface->current->width; | ||
123 | view->saved_surface_height = view->surface->current->height; | ||
124 | } | ||
125 | |||
126 | static void remove_saved_view_buffer(struct sway_view *view) { | ||
127 | if (view->saved_buffer) { | ||
128 | wlr_buffer_unref(view->saved_buffer); | ||
129 | view->saved_buffer = NULL; | ||
130 | view->saved_surface_width = 0; | ||
131 | view->saved_surface_height = 0; | ||
132 | } | ||
133 | } | ||
134 | |||
135 | /** | 174 | /** |
136 | * Apply a transaction to the "current" state of the tree. | 175 | * Apply a transaction to the "current" state of the tree. |
137 | * | ||
138 | * This is mostly copying stuff from the pending state into the main swayc | ||
139 | * properties, but also includes reparenting and deleting containers. | ||
140 | */ | 176 | */ |
141 | static void transaction_apply(struct sway_transaction *transaction) { | 177 | static void transaction_apply(struct sway_transaction *transaction) { |
142 | int i; | 178 | int i; |
179 | // Apply the instruction state to the container's current state | ||
143 | for (i = 0; i < transaction->instructions->length; ++i) { | 180 | for (i = 0; i < transaction->instructions->length; ++i) { |
144 | struct sway_transaction_instruction *instruction = | 181 | struct sway_transaction_instruction *instruction = |
145 | transaction->instructions->items[i]; | 182 | transaction->instructions->items[i]; |
146 | struct sway_container *container = instruction->container; | 183 | struct sway_container *container = instruction->container; |
147 | 184 | ||
148 | memcpy(&instruction->container->current, &instruction->state, | 185 | // There are separate children lists for each instruction state, the |
149 | sizeof(struct sway_container_state)); | 186 | // container's current state and the container's pending state |
187 | // (ie. con->children). The list itself needs to be freed here. | ||
188 | // Any child containers which are being deleted will be cleaned up in | ||
189 | // transaction_destroy(). | ||
190 | list_free(container->current.children); | ||
150 | 191 | ||
151 | if (container->type == C_VIEW) { | 192 | memcpy(&container->current, &instruction->state, |
152 | remove_saved_view_buffer(container->sway_view); | 193 | sizeof(struct sway_container_state)); |
153 | } | ||
154 | } | 194 | } |
155 | 195 | ||
156 | // Damage | 196 | // Apply damage |
157 | for (i = 0; i < transaction->damage->length; ++i) { | 197 | for (i = 0; i < transaction->damage->length; ++i) { |
158 | struct wlr_box *box = transaction->damage->items[i]; | 198 | struct wlr_box *box = transaction->damage->items[i]; |
159 | for (int j = 0; j < root_container.children->length; ++j) { | 199 | for (int j = 0; j < root_container.children->length; ++j) { |
@@ -161,8 +201,6 @@ static void transaction_apply(struct sway_transaction *transaction) { | |||
161 | output_damage_box(output->sway_output, box); | 201 | output_damage_box(output->sway_output, box); |
162 | } | 202 | } |
163 | } | 203 | } |
164 | |||
165 | update_debug_tree(); | ||
166 | } | 204 | } |
167 | 205 | ||
168 | static int handle_timeout(void *data) { | 206 | static int handle_timeout(void *data) { |
@@ -182,7 +220,7 @@ void transaction_commit(struct sway_transaction *transaction) { | |||
182 | struct sway_transaction_instruction *instruction = | 220 | struct sway_transaction_instruction *instruction = |
183 | transaction->instructions->items[i]; | 221 | transaction->instructions->items[i]; |
184 | struct sway_container *con = instruction->container; | 222 | struct sway_container *con = instruction->container; |
185 | if (con->type == C_VIEW && | 223 | if (con->type == C_VIEW && !con->destroying && |
186 | (con->current.view_width != instruction->state.view_width || | 224 | (con->current.view_width != instruction->state.view_width || |
187 | con->current.view_height != instruction->state.view_height)) { | 225 | con->current.view_height != instruction->state.view_height)) { |
188 | instruction->serial = view_configure(con->sway_view, | 226 | instruction->serial = view_configure(con->sway_view, |
@@ -191,14 +229,12 @@ void transaction_commit(struct sway_transaction *transaction) { | |||
191 | instruction->state.view_width, | 229 | instruction->state.view_width, |
192 | instruction->state.view_height); | 230 | instruction->state.view_height); |
193 | if (instruction->serial) { | 231 | if (instruction->serial) { |
194 | save_view_buffer(con->sway_view); | ||
195 | list_add(con->sway_view->instructions, instruction); | ||
196 | ++transaction->num_waiting; | 232 | ++transaction->num_waiting; |
197 | } | 233 | } |
198 | } | 234 | } |
235 | list_add(con->instructions, instruction); | ||
199 | } | 236 | } |
200 | if (!transaction->num_waiting) { | 237 | if (!transaction->num_waiting) { |
201 | // This can happen if the transaction only contains xwayland views | ||
202 | wlr_log(L_DEBUG, "Transaction %p has nothing to wait for, applying", | 238 | wlr_log(L_DEBUG, "Transaction %p has nothing to wait for, applying", |
203 | transaction); | 239 | transaction); |
204 | transaction_apply(transaction); | 240 | transaction_apply(transaction); |
@@ -210,31 +246,47 @@ void transaction_commit(struct sway_transaction *transaction) { | |||
210 | transaction->timer = wl_event_loop_add_timer(server.wl_event_loop, | 246 | transaction->timer = wl_event_loop_add_timer(server.wl_event_loop, |
211 | handle_timeout, transaction); | 247 | handle_timeout, transaction); |
212 | wl_event_source_timer_update(transaction->timer, TIMEOUT_MS); | 248 | wl_event_source_timer_update(transaction->timer, TIMEOUT_MS); |
249 | |||
250 | // The debug tree shows the pending/live tree. Here is a good place to | ||
251 | // update it, because we make a transaction every time we change the pending | ||
252 | // tree. | ||
253 | update_debug_tree(); | ||
213 | } | 254 | } |
214 | 255 | ||
215 | void transaction_notify_view_ready(struct sway_view *view, uint32_t serial) { | 256 | void transaction_notify_view_ready(struct sway_view *view, uint32_t serial) { |
216 | // Find the instruction | 257 | // Find the instruction |
217 | struct sway_transaction_instruction *instruction = NULL; | 258 | struct sway_transaction_instruction *instruction = NULL; |
218 | for (int i = 0; i < view->instructions->length; ++i) { | 259 | for (int i = 0; i < view->swayc->instructions->length; ++i) { |
219 | struct sway_transaction_instruction *tmp_instruction = | 260 | struct sway_transaction_instruction *tmp_instruction = |
220 | view->instructions->items[i]; | 261 | view->swayc->instructions->items[i]; |
221 | if (tmp_instruction->serial == serial) { | 262 | if (tmp_instruction->serial == serial && !tmp_instruction->ready) { |
222 | instruction = tmp_instruction; | 263 | instruction = tmp_instruction; |
223 | list_del(view->instructions, i); | ||
224 | break; | 264 | break; |
225 | } | 265 | } |
226 | } | 266 | } |
227 | if (!instruction) { | 267 | if (!instruction) { |
228 | // This can happen if the view acknowledges the configure after the | ||
229 | // transaction has timed out and applied. | ||
230 | return; | 268 | return; |
231 | } | 269 | } |
270 | instruction->ready = true; | ||
271 | |||
232 | // If all views are ready, apply the transaction | 272 | // If all views are ready, apply the transaction |
233 | struct sway_transaction *transaction = instruction->transaction; | 273 | struct sway_transaction *transaction = instruction->transaction; |
234 | if (--transaction->num_waiting == 0) { | 274 | if (--transaction->num_waiting == 0) { |
275 | #if !TRANSACTION_DEBUG | ||
235 | wlr_log(L_DEBUG, "Transaction %p is ready, applying", transaction); | 276 | wlr_log(L_DEBUG, "Transaction %p is ready, applying", transaction); |
236 | wl_event_source_timer_update(transaction->timer, 0); | 277 | wl_event_source_timer_update(transaction->timer, 0); |
237 | transaction_apply(transaction); | 278 | transaction_apply(transaction); |
238 | transaction_destroy(transaction); | 279 | transaction_destroy(transaction); |
280 | #endif | ||
239 | } | 281 | } |
240 | } | 282 | } |
283 | |||
284 | struct wlr_texture *transaction_get_texture(struct sway_view *view) { | ||
285 | if (!view->swayc || !view->swayc->instructions->length) { | ||
286 | return view->surface->buffer->texture; | ||
287 | } | ||
288 | struct sway_transaction_instruction *instruction = | ||
289 | view->swayc->instructions->items[0]; | ||
290 | return instruction->saved_buffer ? | ||
291 | instruction->saved_buffer->texture : NULL; | ||
292 | } | ||