==> src/place.c <==1/* Placement in the driver (driver.rs's placement section): a placeable program's Placer (the fleet
2 * area's @ajax/mesh-placer) per slot when peered, its acts carried out on the mailbox and the peer
3 * links, the mode's run state, and the commands and frames that reach it. */
4#include "driver_int.h"
5
6#include "mesh/place_compose.h"
7#include "mesh/rsfmt.h"
8
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12
13static uint64_t now_us(void) { return (uint64_t)mesh_mono_us(); }
14
15static size_t ctl(uint32_t s, size_t off) { return mesh_prog_ctl(s) + off; }
16
17const char *drv_role_word(mpm_role r) {
18 switch (r) {
19 case MPM_ROLE_LOCAL: return "Local";
20 case MPM_ROLE_RUNNER: return "Runner";
21 case MPM_ROLE_MIRROR: return "Mirror";
22 case MPM_ROLE_OWNER: return "Owner";
23 case MPM_ROLE_FOLLOWER: return "Follower";
24 }
25 return "?";
26}
27
28static bool local(const mpm_mode *m) { return m->kind == MPM_LOCAL; }
29
30/* The committed placement request for name (FleetView's), as the placer takes it; NULL: none. */
31static bool fleet_request(const mesh_driver *d, const char *name, mt_strs *out) {
32 for (size_t i = 0; i < d->nfleet_places; i++) {
33 const mesh_place_req *q = &d->fleet_places[i];
34 if (strcmp(q->name, name)) continue;
35 *out = (mt_strs){0};
36 for (size_t j = 0; j < q->nwords; j++) mt_push(out, q->words[j]);
37 return true;
38 }
39 return false;
40}
41
42static void strs_free(mt_strs *l) {
43 for (size_t i = 0; i < l->n; i++) free(l->v[i]);
44 free(l->v);
45 *l = (mt_strs){0};
46}
47
48static void placer_log(void *ctx, const char *line) {
49 (void)ctx;
50 mesh_info("%s", line);
51}
52
53static void dplace_free(dplace *p) {
54 if (!p) return;
55 mpl_free(p->p);
56 free(p);
57}
58
59/* Some(why) when slot s's run state belongs to its placement here (pin elsewhere, or split follower):
60 * start/stop/reset are refused. */
61char *drv_placed_here(const mesh_driver *d, uint32_t s) {
62 const dplace *p = d->place[s];
63 if (!p) return NULL;
64 mpm_role r = mpl_role(p->p);
65 if (r != MPM_ROLE_MIRROR && r != MPM_ROLE_FOLLOWER) return NULL;
66 const char *name = drv_name(d->slots[s]);
67 char *label = mpm_label(mpl_mode(p->p));
68 char *why = mesh_buf_fmt("%s is placed (%s): system %u runs it; `place %s local` first, or change it there", name, label,
69 mpm_input_to(mpl_mode(p->p), d->me), name);
70 free(label);
71 return why;
72}
73
74static char *place_report(const mesh_driver *d, uint32_t s) {
75 const dplace *p = d->place[s];
76 const mpl_stats *st = mpl_stats_of(p->p);
77 char *label = mpm_label(mpl_mode(p->p));
78 char *r = mesh_buf_fmt("%s role %s system %u paused %s shown %llu lat_n %llu lat_sum_us %llu lat_max_us %u ignored %llu part_ns %u uptime_ms %llu", label,
79 drv_role_word(mpl_role(p->p)), d->me, mpl_paused(p->p) ? "true" : "false", (unsigned long long)st->shown,
80 (unsigned long long)st->lat_n, (unsigned long long)st->lat_sum_us, st->lat_max_us, (unsigned long long)st->ignored, p->part_ns,
81 (unsigned long long)mesh_host_block_uptime_ms(&d->sh->host));
82 free(label);
83 return r;
84}
85
86/* PX_PLACE (the table extension) and the show header's mode words. */
87void drv_place_words(mesh_driver *d, uint32_t s) {
88 const dplace *p = d->place[s];
89 const dslot *x = d->slots[s];
90 if (!p || !x) return;
91 mesh_mailbox mb = d->sh->mb;
92 uint32_t m, sys;
93 mpm_code(mpl_mode(p->p), &m, &sys);
94 mesh_mailbox_set(mb, mesh_prog_ext(s) + MESH_PX_PLACE, m);
95 mesh_mailbox_set(mb, mesh_prog_ext(s) + MESH_PX_PLACE_SYS, sys);
96 uint8_t a[MESH_PLACE_PARTS_MAX];
97 mpm_assign_bytes(mpl_mode(p->p), a);
98 mesh_mailbox_write(mb, mesh_prog_ext(s) + MESH_PX_PLACE_ASSIGN, a, sizeof a, sizeof a);
99 size_t show = (size_t)x->view_off + mpl_spec(p->p)->show;
100 mesh_mailbox_set(mb, show + MESH_SH_MODE, m);
101 mesh_mailbox_publish(mb, show + MESH_SH_SYS, sys);
102}
103
104typedef struct {
105 mesh_mailbox mb;
106 size_t view;
107} vio;
108
109static void vread(void *c, uint32_t off, uint32_t len, uint8_t *out) {
110 vio *v = c;
111 mesh_mailbox_read(v->mb, v->view + off, out, len);
112}
113static void vwrite(void *c, uint32_t off, const uint8_t *b, uint32_t len) {
114 vio *v = c;
115 mesh_mailbox_write(v->mb, v->view + off, b, len, len);
116}
117/* The mode changed here: the run state (a mirror's kernel stops; every other placed role runs), the
118 * published words, the cadence. */
119static void apply_mode(mesh_driver *d, uint32_t s, const mpm_mode *m) {
120 dplace *p = d->place[s];
121 dslot *x = d->slots[s];
122 if (!p || !x) return;
123 mpm_role role = mpm_role_of(m, d->me);
124 p->has_follow = false;
125 if (!mpm_runs(role)) {
126 if (x->want_run) x->want_run = false, p->stopped = true;
127 } else if (role != MPM_ROLE_LOCAL || p->stopped) {
128 x->want_run = true;
129 p->stopped = false;
130 }
131 mesh_mailbox_set(d->sh->mb, ctl(s, MESH_P_RUN), x->want_run);
132 x->last_auto = mesh_mono_ns();
133 char *label = mpm_label(m);
134 mesh_info("place %s: %s here (system %u: %s)%s", drv_name(x), label, d->me, drv_role_word(role), x->want_run ? "" : "; its kernel stops here");
135 free(label);
136 drv_place_words(d, s);
137 d->state_dirty = true;
138}
139
140static void dest_text(const mpl_dest *dst, char *out, size_t cap) { snprintf(out, cap, dst->to_mask ? "Mask(%u)" : "To(%u)", dst->k); }
141
142/* Carries out a placer's acts for slot s (has_s false: the slot is gone). Takes acts. */
143void drv_place_acts(mesh_driver *d, bool has_s, uint32_t s, mpl_acts acts) {
144 mesh_mailbox mb = d->sh->mb;
145 for (size_t i = 0; i < acts.n; i++) {
146 mpl_act *a = &acts.v[i];
147 switch (a->kind) {
148 case MPL_SEND: {
149 bool ok = a->dest.to_mask ? mesh_shared_send_mask(d->sh, a->dest.k, a->bytes.b, a->bytes.n) : mesh_shared_send_peer(d->sh, a->dest.k, a->bytes.b, a->bytes.n);
150 if (!ok) {
151 char t[32];
152 dest_text(&a->dest, t, sizeof t);
153 mesh_error("place: a frame for %s was dropped (no session, or its queue is full)", t);
154 }
155 break;
156 }
157 case MPL_ANSWER: {
158 for (size_t j = 0; j < d->nplace_replies; j++) {
159 if (d->place_replies[j].h != a->handle) continue;
160 drv_answer(d->place_replies[j].reply, a->ok, a->text ? a->text : "");
161 d->place_replies[j] = d->place_replies[--d->nplace_replies];
162 break;
163 }
164 break;
165 }
166 case MPL_LOG: mesh_info("%s", a->text ? a->text : ""); break;
167 case MPL_WRITE_STATE: {
168 if (!has_s) break;
169 dplace *p = d->place[s];
170 dslot *x = d->slots[s];
171 if (!p || !x) break;
172 vio v = {mb, x->view_off};
173 char *err = NULL;
174 if (!mpc_write_state(mpl_spec(p->p), a->bytes.b, a->bytes.n, vwrite, &v, &err)) {
175 mesh_error("place %s: %s", drv_name(x), err ? err : "");
176 free(err);
177 break;
178 }
179 /* A stopped copy now holds the whole state: it is initialised (no OP_INIT over it). */
180 if (!drv_live(x)) x->needs_init = false;
181 break;
182 }
183 case MPL_SHOW: {
184 if (!has_s) break;
185 dplace *p = d->place[s];
186 dslot *x = d->slots[s];
187 if (!p || !x) break;
188 size_t show = (size_t)x->view_off + mpl_spec(p->p)->show;
189 uint32_t seq = mesh_mailbox_get(mb, show + MESH_SH_SEQ) + 1;
190 size_t k = seq & 1;
191 mesh_mailbox_write(mb, show + MESH_SH_DATA + k * a->bytes.n, a->bytes.b, a->bytes.n, a->bytes.n);
192 mesh_mailbox_set(mb, show + MESH_SH_BUF + MESH_SH_BUF_BYTES * k + MESH_SHB_FRAME, a->frame);
193 mesh_mailbox_set(mb, show + MESH_SH_BUF + MESH_SH_BUF_BYTES * k + MESH_SHB_SYS, a->sys);
194 mesh_mailbox_set(mb, show + MESH_SH_LAT_US, a->lat_us);
195 uint32_t m, sys;
196 mpm_code(mpl_mode(p->p), &m, &sys);
197 mesh_mailbox_set(mb, show + MESH_SH_MODE, m);
198 mesh_mailbox_set(mb, show + MESH_SH_SYS, sys);
199 mesh_mailbox_publish(mb, show + MESH_SH_SEQ, seq);
200 break;
201 }
202 case MPL_FOLLOW:
203 if (has_s && d->place[s]) d->place[s]->has_follow = true, d->place[s]->follow = a->frame;
204 break;
205 case MPL_MODE:
206 if (has_s) apply_mode(d, s, &a->mode);
207 break;
208 }
209 }
210 mpl_acts_free(&acts);
211}
212
213static bool spec_eq(const mps_spec *a, const mps_spec *b) {
214 if (a->cap != b->cap || a->frame != b->frame || a->show != b->show || a->fanout != b->fanout || a->nstate != b->nstate || a->nout != b->nout) return false;
215 for (size_t i = 0; i < a->nstate; i++)
216 if (a->state[i].off != b->state[i].off || a->state[i].len != b->state[i].len) return false;
217 for (size_t i = 0; i < a->nout; i++)
218 if (a->out[i].off != b->out[i].off || a->out[i].len != b->out[i].len) return false;
219 return true;
220}
221
222/* The manifest's placement as the placer takes it (its fanout the manifest's). */
223static mps_spec spec_of(const mesh_manifest *m) {
224 const mesh_manifest_place *p = m->place;
225 mps_region st[p->nstate ? p->nstate : 1], out[p->nout ? p->nout : 1];
226 for (size_t i = 0; i < p->nstate; i++) st[i] = (mps_region){p->state[i].off, p->state[i].len};
227 for (size_t i = 0; i < p->nout; i++) out[i] = (mps_region){p->out[i].off, p->out[i].len};
228 return mps_new(p->cap, st, p->nstate, p->frame, out, p->nout, p->show, m->fanout);
229}
230
231/* A placer for slot s's manifest (peered, `place` declared), or none. After an upgrade or rollback: kept
232 * when the declaration is unchanged. */
233void drv_sync_place(mesh_driver *d, uint32_t s) {
234 dslot *x = d->slots[s];
235 bool want = d->cfg.peered && x && x->image.manifest.place;
236 mps_spec spec = {0};
237 if (want) spec = spec_of(&x->image.manifest);
238 dplace *old = d->place[s];
239 if (old && want && spec_eq(mpl_spec(old->p), &spec)) {
240 mps_free(&spec);
241 drv_place_words(d, s);
242 return;
243 }
244 if (!old && !want) return;
245 if (old) {
246 d->place[s] = NULL;
247 char *why = mesh_buf_fmt("%s: its placement declaration changed", old->name);
248 drv_place_acts(d, false, 0, mpl_drop_all(old->p, why));
249 free(why);
250 if (old->stopped && x) {
251 x->want_run = true;
252 mesh_mailbox_set(d->sh->mb, ctl(s, MESH_P_RUN), 1);
253 }
254 if (!local(mpl_mode(old->p))) mesh_info("place %s: its manifest's placement changed: local here", old->name);
255 free(old->name);
256 dplace_free(old);
257 }
258 if (want) {
259 dplace *p = mesh_xcalloc(1, sizeof *p);
260 p->name = mesh_xstrdup(drv_name(x));
261 p->p = mpl_new(p->name, spec, d->me, d->n);
262 mpl_set_log(p->p, placer_log, NULL);
263 d->place[s] = p;
264 mt_strs req;
265 bool has = fleet_request(d, p->name, &req);
266 mpl_acts acts = mpl_view(p->p, d->fleet_up, d->has_fleet_leader, d->fleet_leader, has ? &req : NULL, now_us());
267 if (has) strs_free(&req);
268 drv_place_acts(d, true, s, acts);
269 }
270 drv_place_words(d, s);
271}
272
273/* Unloaded: what waits on the placement is answered. */
274void drv_place_drop(mesh_driver *d, uint32_t s, const char *name) {
275 dplace *p = d->place[s];
276 if (!p) return;
277 d->place[s] = NULL;
278 char *why = mesh_buf_fmt("%s was unloaded", name);
279 drv_place_acts(d, false, 0, mpl_drop_all(p->p, why));
280 free(why);
281 free(p->name);
282 dplace_free(p);
283}
284
285void drv_place_free_all(mesh_driver *d) {
286 for (uint32_t s = 0; s < MESH_PROG_SLOTS; s++) {
287 if (!d->place[s]) continue;
288 free(d->place[s]->name);
289 dplace_free(d->place[s]);
290 d->place[s] = NULL;
291 }
292 for (size_t i = 0; i < d->nplace_replies; i++) mesh_reply_drop(d->place_replies[i].reply);
293 free(d->place_replies);
294 d->place_replies = NULL;
295 d->nplace_replies = d->cplace_replies = 0;
296 for (size_t i = 0; i < d->nfleet_places; i++) {
297 free(d->fleet_places[i].name);
298 for (size_t j = 0; j < d->fleet_places[i].nwords; j++) free(d->fleet_places[i].words[j]);
299 free(d->fleet_places[i].words);
300 }
301 free(d->fleet_places);
302 d->fleet_places = NULL;
303 d->nfleet_places = 0;
304}
305
306/* A placed program's frames hop between the machines: the driver ticks fast. */
307bool drv_place_busy(const mesh_driver *d) {
308 for (uint32_t s = 0; s < MESH_PROG_SLOTS; s++) {
309 const dplace *p = d->place[s];
310 if (p && (!local(mpl_mode(p->p)) || mpl_busy(p->p))) return true;
311 }
312 return false;
313}
314
315/* The load sampler's mean busy time per task, for `split auto` (PL_TASK_US: the last known value while
316 * the program ran no task). */
317void drv_place_part_ns(mesh_driver *d) {
318 mesh_host_block *h = &d->sh->host;
319 for (uint32_t s = 0; s < MESH_PROG_SLOTS; s++) {
320 dplace *p = d->place[s];
321 if (!p || !d->slots[s]) continue;
322 size_t at = mesh_pload(s);
323 if (!(mesh_host_block_get_u32(h, at + MESH_PL_FLAGS) & MESH_PLF_TASK)) continue;
324 double ns = (double)mesh_host_block_get_f32(h, at + MESH_PL_TASK_US) * 1e3;
325 p->part_ns = ns >= 4294967295.0 ? UINT32_MAX : ns > 0 ? (uint32_t)ns : 0;
326 mpl_set_part_ns(p->p, p->part_ns);
327 }
328}
329
330/* Every 10 s: each placed program's frames and their added latency. */
331void drv_place_log(mesh_driver *d) {
332 for (uint32_t s = 0; s < MESH_PROG_SLOTS; s++) {
333 const dplace *p = d->place[s];
334 if (!p || local(mpl_mode(p->p))) continue;
335 const mpl_stats *st = mpl_stats_of(p->p);
336 char *label = mpm_label(mpl_mode(p->p));
337 mesh_buf mean = MESH_BUF_INIT;
338 mesh_rs_fixed(&mean, (double)st->lat_sum_us / (double)(st->lat_n ? st->lat_n : 1), 0);
339 mesh_info("place %s: %s (%s): %llu frames shown, added latency mean %s us max %u us over %llu frames, %llu peer frames unused", p->name, label,
340 drv_role_word(mpl_role(p->p)), (unsigned long long)st->shown, mesh_buf_str(&mean), st->lat_max_us, (unsigned long long)st->lat_n,
341 (unsigned long long)st->ignored);
342 mesh_buf_free(&mean);
343 free(label);
344 }
345}
346
347typedef struct {
348 vio v;
349 const mps_spec *spec;
350} sio;
351
352static mpw_bytes sstate(void *c) {
353 sio *x = c;
354 return mpc_read_state(x->spec, vread, &x->v);
355}
356
357/* Per tick: each placer's timeouts, pause acks and queued follow frames. */
358void drv_place_tick(mesh_driver *d) {
359 uint64_t now = now_us();
360 for (uint32_t s = 0; s < MESH_PROG_SLOTS; s++) {
361 dplace *p = d->place[s];
362 dslot *x = d->slots[s];
363 if (!p || !x) continue;
364 bool drained = !drv_live(x) || mesh_pring_idle(&x->ring);
365 sio io = {{d->sh->mb, x->view_off}, mpl_spec(p->p)};
366 mpl_acts acts = mpl_tick(p->p, now, drained, sstate, &io);
367 if (acts.n) drv_place_acts(d, true, s, acts);
368 else mpl_acts_free(&acts);
369 }
370}
371
372/* Slot s finished a batch of its own frame (Runner, Owner) or its follow batch. */
373void drv_place_frame(mesh_driver *d, uint32_t s, bool own, bool follow) {
374 dplace *p = d->place[s];
375 dslot *x = d->slots[s];
376 if (!p || !x) return;
377 mesh_mailbox mb = d->sh->mb;
378 const mps_spec *spec = mpl_spec(p->p);
379 vio v = {mb, x->view_off};
380 uint64_t now = now_us();
381 uint32_t mask = mpl_my_parts(p->p);
382 mpm_role role = mpl_role(p->p);
383 if (own && mesh_pring_auto_in_flight(&x->ring) == 0 && (role == MPM_ROLE_RUNNER || role == MPM_ROLE_OWNER)) {
384 uint32_t frame = mesh_mailbox_get(mb, (size_t)x->view_off + spec->frame);
385 mpw_bytes st = mpc_read_state(spec, vread, &v), data = mpc_gather(spec, mask, vread, &v);
386 drv_place_acts(d, true, s, mpl_own_frame(p->p, frame, st, data, now));
387 p = d->place[s];
388 x = d->slots[s];
389 if (!p || !x) return;
390 }
391 if (follow && mesh_pring_follow_in_flight(&x->ring) == 0) {
392 mpw_bytes data = mpc_gather(spec, mask, vread, &v);
393 drv_place_acts(d, true, s, mpl_follow_done(p->p, data, now));
394 }
395}
396
397void drv_place_follow_lost(mesh_driver *d, uint32_t s) {
398 if (d->place[s]) mpl_follow_lost(d->place[s]->p);
399}
400
401/* The program's run state is held by its placement (inventory: a placement-stopped one counts as run). */
402bool drv_place_stopped(const mesh_driver *d, const char *name) {
403 for (uint32_t s = 0; s < MESH_PROG_SLOTS; s++)
404 if (d->place[s] && d->place[s]->stopped && !strcmp(d->place[s]->name, name)) return true;
405 return false;
406}
407
408/* publish_slot's placement part: the follow batch (split, a follower's parts) on the owner's state; and
409 * the role, whether it is paused, and its parts (an Owner publishes those only). */
410void drv_place_publish(mesh_driver *d, uint32_t s, bool *paused, mpm_role *role, uint32_t *parts) {
411 dplace *p = d->place[s];
412 dslot *x = d->slots[s];
413 *paused = false;
414 *role = MPM_ROLE_LOCAL;
415 *parts = 0;
416 if (!p) return;
417 *role = mpl_role(p->p);
418 *paused = mpl_paused(p->p);
419 *parts = mpl_my_parts(p->p);
420 if (p->has_follow && !*paused && *role == MPM_ROLE_FOLLOWER) {
421 uint32_t n = (uint32_t)__builtin_popcount(*parts);
422 if (mesh_pring_room(&x->ring) >= n) {
423 p->has_follow = false;
424 for (uint32_t b = 0; b < 32; b++)
425 if (*parts >> b & 1) mesh_pring_publish(&x->ring, d->sh->mb, MESH_OP_FOLLOW, b, mesh_kind_of(MESH_TK_FOLLOW), NULL);
426 }
427 }
428}
429
430/* ---- commands */
431
432void drv_cmd_place(mesh_driver *d, mesh_cmd *c, mesh_reply *reply) {
433 int s = drv_find(d, c->name);
434 if (s < 0) {
435 mesh_reply_errf(reply, "no program %s (`programs` lists them)", c->name);
436 return;
437 }
438 dplace *p = d->place[s];
439 if (!p && !d->cfg.peered) {
440 mesh_reply_err(reply, "not peered: placement needs a cluster (--cluster, --system)");
441 return;
442 }
443 if (!p) {
444 mesh_reply_errf(reply, "%s declares no `place` in its manifest", c->name);
445 return;
446 }
447 if (!c->has_words) {
448 char *r = place_report(d, (uint32_t)s);
449 mesh_reply_ok(reply, r);
450 free(r);
451 return;
452 }
453 mpm_target t;
454 char *err = NULL;
455 if (!mpl_parse(p->p, c->words, c->nwords, &t, &err)) {
456 mesh_reply_err(reply, err ? err : "");
457 free(err);
458 return;
459 }
460 uint64_t h = ++d->place_handle;
461 if (d->nplace_replies == d->cplace_replies) {
462 d->cplace_replies = d->cplace_replies ? d->cplace_replies * 2 : 4;
463 d->place_replies = realloc(d->place_replies, d->cplace_replies * sizeof *d->place_replies);
464 if (!d->place_replies) abort();
465 }
466 d->place_replies[d->nplace_replies++] = (dplace_reply){h, reply};
467 mt_strs w = {0};
468 for (size_t i = 0; i < c->nwords; i++) mt_push(&w, c->words[i]);
469 mpl_acts acts = mpl_request(p->p, &w, h, now_us());
470 strs_free(&w);
471 drv_place_acts(d, true, (uint32_t)s, acts);
472}
473
474void drv_cmd_peer_place(mesh_driver *d, mesh_cmd *c) {
475 mpw_place *m = c->msg;
476 int s = drv_find(d, m->name);
477 if (s < 0 || !d->place[s]) {
478 mesh_error("place: F_PLACE op %u for %s from system %u, which is not placeable here; ignored", m->op, m->name, c->from);
479 return;
480 }
481 mpw_place taken = *m;
482 memset(m, 0, sizeof *m);
483 drv_place_acts(d, true, (uint32_t)s, mpl_peer_place(d->place[s]->p, taken, c->from, now_us()));
484}
485
486void drv_cmd_peer_frame(mesh_driver *d, mesh_cmd *c) {
487 mpw_frame *m = c->msg;
488 int s = drv_find(d, m->name);
489 if (s < 0 || !d->place[s]) return;
490 mpw_frame taken = *m;
491 memset(m, 0, sizeof *m);
492 drv_place_acts(d, true, (uint32_t)s, mpl_peer_frame(d->place[s]->p, taken, now_us()));
493}
494
495void drv_cmd_peer_input(mesh_driver *d, mesh_cmd *c) {
496 int s = drv_find(d, c->name);
497 if (s >= 0 && d->place[s] && mpm_input_to(mpl_mode(d->place[s]->p), d->me) == d->me) {
498 char *text = NULL;
499 if (drv_input(d, (uint32_t)s, c->bytes, c->len, &text) != 0) mesh_error("place: input from the peer: %s", text ? text : "");
500 free(text);
501 return;
502 }
503 mesh_error("place: %zu byte(s) of input for %s from a member, which does not apply its input here; dropped", c->len, c->name);
504}
505
506void drv_cmd_fleet_view(mesh_driver *d, mesh_cmd *c) {
507 d->fleet_up = c->up;
508 d->has_fleet_leader = c->has_leader;
509 d->fleet_leader = c->leader;
510 for (size_t i = 0; i < d->nfleet_places; i++) {
511 free(d->fleet_places[i].name);
512 for (size_t j = 0; j < d->fleet_places[i].nwords; j++) free(d->fleet_places[i].words[j]);
513 free(d->fleet_places[i].words);
514 }
515 free(d->fleet_places);
516 d->fleet_places = c->places;
517 d->nfleet_places = c->nplaces;
518 c->places = NULL;
519 c->nplaces = 0;
520 uint64_t now = now_us();
521 for (uint32_t s = 0; s < MESH_PROG_SLOTS; s++) {
522 dplace *p = d->place[s];
523 if (!p) continue;
524 mt_strs req;
525 bool has = fleet_request(d, p->name, &req);
526 mpl_acts acts = mpl_view(p->p, d->fleet_up, d->has_fleet_leader, d->fleet_leader, has ? &req : NULL, now);
527 if (has) strs_free(&req);
528 drv_place_acts(d, true, s, acts);
529 }
530}
531
532/* Where slot s's input is applied: this machine, or the member its placement names. */
533uint32_t drv_input_to(const mesh_driver *d, uint32_t s) { return d->place[s] ? mpm_input_to(mpl_mode(d->place[s]->p), d->me) : d->me; }