skip to content
==> 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; }