chiark / gitweb /
fix uninitialised reference
[trains.git] / hostside / movpos.c
1 /*
2  * Handling of points and other moveable features.
3  */
4
5 #include "realtime.h"
6
7 /*========== declarations ==========*/
8
9 typedef struct {
10   const MovFeatInfo *i;
11   Small posn;
12 } Motion;
13
14 typedef struct KindInfo KindInfo;
15
16 /* Kind-independent code is responsible for determining
17  * the method, doing a bit of cleanup, and adjusting the flow
18  * slightly.  Per-kind code does the actual work and is mostly in
19  * charge - it is also responsible for updating seg->moving and ->motion.
20  */
21 /* The following states exist for each MovPosChange
22  * at points when control flow  passes between kind and indep:
23  *   U  Unallocated  no memory allocated (MovPosChange does not exist)
24  *   A  Allocated    memory allocation done
25  *   R  Reserved     reservation was successful
26  *   C  Confirmed    motion queued and will occur
27  *   D  Done         motion is complete and callback just needs to be made
28  *   E  Erroneous    indep must call destroy straight away
29  * seg->moving and ->motion is in one of the states UC
30  */
31
32 typedef struct MovPosChange {      /* valid in:   filled in by and when:     */
33   const KindInfo *ki;              /*  ARCDE       indep after allocate()    */
34   Segment *move;                   /*  ARCDE       indep after allocate()    */
35   MovPosComb actual;               /*  CD          see below                 */
36   MovPosComb intent;               /*  RCD         indep after allocate()    */
37   /* kind-specific data follows */ /*  varies     kind-specific code, varies */
38 } Change;
39   /* `actual' contains the kind's public opinion about the physical
40    * state.  It is initialised by indep (just before confirm) from
41    * move->motion->actual or move->movposcomb as the case may be.  It
42    * should be updated by the kind, since it is used by indep for
43    * calculating the number and identities of the features which may
44    * need to change when a new move request is intended to replace an
45    * existing one - ie, the contents of the motions[] provided to a
46    * subsequent confirm().  So while a change is Confirmed, the
47    * physical state is recorded only in the relevant change, and not
48    * in the segment's movposcomb.  Once a change goes to Confirmed,
49    * the indep code never untangles it so the kind can manage the
50    * proper transition.  */
51
52 struct KindInfo {
53   const char *pname;
54   Change *(*allocate)(int alloc_motions); /* U->A (always succeeds) */
55   ErrorCode (*reserve)(Change*, Segment*, int ms); /* A->R; error: A->E */
56   ErrorCode (*confirm)(Change*, Segment*, int n_motions,
57                        const Motion*, int ms);   /* [AR]->C; error; [AR]->E */
58   void (*destroy)(Change*);                        /* [ARCE]->U */
59   /* indep guarantees that
60    *   alloc_motions >= move->i->n_motions    on reserve
61    *   alloc_motions >= n_motions             on confirm
62    * and that if on entry to reserve move->motion is non-0,
63    *  it move->motion is non-0 and of the same kind
64    */
65 };
66
67 static const char *posnpname(Segment *move, MovPosComb poscomb) {
68   return poscomb<0 ? "?" : move->i->poscombs[poscomb].pname;
69 }
70
71 static void ouposn_moving(Change *chg) {
72   Segment *move= chg->move;
73   oprintf(UPO, "movpos %s position %s moving\n",
74           move->i->pname, posnpname(move, chg->actual));
75 }
76
77 static void motion_done(Segment *move, MovPosComb actual) {
78   move->moving= 0;
79   move->motion= 0;
80   move->movposcomb= actual;
81   oprintf(UPO, "movpos %s position %s stable\n",
82           move->i->pname, posnpname(move, move->movposcomb));
83 }
84
85 /*========== points ==========*/
86
87 /*
88  * We maintain two queues, one for reserved one for actually confirmed
89  *  requests where we know what we're doing.
90  *
91  * We divide time into discrete slots, numbered with clock arithmetic.
92  *
93  *     cslot         cslot+1      cslot+2
94  *
95  *     currently     next in      after
96  *     changing      line         that
97  *
98  * We increment cslot when we issue a POINT command to the PIC.
99  * In a request, the deadline represents the latest allowable value
100  * of cslot just before that increment.
101  */
102
103 typedef unsigned PtSlot;
104 typedef int PtSlotSigned;
105
106 /* We think there are three states: Allocated, Reserved and Confirmed.
107  * (plus of course Unallocated where we don't have a request at all).
108  * These correspond to the indep code as follows:
109  *
110  *  indep state   pt state    queues checked and plan viable
111  *   Unallocated   n/a         yes
112  *   Allocated     Allocated   yes
113  *   Reserved      Reserved    yes
114  *   Confirmed     Confirmed   yes
115  *   Erroneous     A/R/C       no
116  *
117  * Erroneous exists only after a failed reserve() or confirm() so it's
118  * not that confusing to have this slightly malleable terminology.
119  */
120
121 typedef struct {                 /* Allocated  Reserved   Confirmed       */
122                      /* in queue?    absent     reserved   confirmed      */
123   Change h;
124   PtSlot deadline;   /*              ~0         relative   absolute    <- */
125   MovPosComb actual; /*              undef      undef      see below      */
126   int n_motions;     /*              alloc'd    alloc'd    undone         */
127   Motion motions[];  /*   [0].i:     0          0          non-0       <- */
128                      /*  [..].i:     undef      undef      non-0          */
129                      /*   .posn:     undef      undef      defined        */
130 } PointReq;
131   /* We can determine the the state by looking at the two
132    * `statedet' fields, marked <- above.
133    * There are also intermediate states where the req's
134    *  statedet fields do not agree with the queue it's on.
135    *  We write these as, for example,
136    *      AR   to mean  statedet says Allocated, but queued on pt_reserved
137    *      A?   to mean  statedet says Allocated, but may be queued
138    *  etc.  They are only allowed while we are in a pt_... method function.
139    */
140   /* PointReq.actual is subtly differnet to MovPosChange.actual,
141    * as follows:
142    *                              in MovPosChange     in PointReq
143    *  Position unknown              -1                  0
144    *  Position partly known         -1                  unknown feats are 0
145    *  Position completely known     exact               exact
146    *
147    * The partial knowledge positions can only occur in requests that
148    * are confirmed with as many motions as features, so we know that
149    * if we complete a request we know that we can copy actual out
150    * to MovPosChange.
151    *
152    * If we abandon a half-done change to a multi-feat segment
153    * we lose the partial knowledge.
154    */
155
156 #define CDU_RECHARGE   250 /*ms*/
157 #define POINT_MOVEMENT  50 /*ms*/
158 #define PT_MAX_QUEUE  15
159
160 typedef struct {
161   int n;
162   PointReq *l[PT_MAX_QUEUE];
163 } PointQueue;
164
165 /*
166  * CDU and point queue states:
167  *
168  *
169  *    ____________                  pt_cdu_     conf'd
170  *   /   points_  \                 charged       .n
171  *   |   all_      |
172  *   |   abaondon  |
173  *   |             V
174  *   |from       INACTIVE               -1      0
175  *   |any       <=Sta_Settling
176  *  ^^^^^^^^     (start)
177  *                 |
178  *     ___________ |turning
179  *    /           \| _on
180  *   |             V
181  *   |           CHARGING               0       any
182  *   |          >=Sta_Resolving
183  *   |             |
184  *   |             |on_pic
185  *   |             |_charged
186  *   |             V
187  *   ^           READY                  1       any
188  *   |             |
189  *   |             |pt_check_action
190  *   |             | fires a point
191  *    \___________/
192  *
193  */
194
195 static PtSlot pt_cslot;
196 static int pt_cdu_charged;
197 static PointQueue pt_confirmed, pt_reserved;
198
199 static void pt_check_action(void);
200 static ErrorCode pt_check_plan(void);
201
202 static PtSlotSigned pt_maxdelay_reldeadline(int maxdelay_ms, int n_motions) {
203   if (maxdelay_ms==-1) return PT_MAX_QUEUE*16;
204   return (maxdelay_ms - POINT_MOVEMENT) / CDU_RECHARGE - n_motions;
205 }
206
207 static void pt_queue_remove_index(PointQueue *q, int index) {
208   q->n--;
209   memmove(&q->l[index], &q->l[index+1], sizeof(q->l[0]) * (q->n - index));
210 }
211
212 static int pt_req_compar(const void *av, const void *bv) {
213   PointReq *const *a= av;
214   PointReq *const *b= av;
215   return (PtSlotSigned)((*b)->deadline - (*a)->deadline);
216 }
217
218 static void pt_queue_remove_item(PointQueue *q, PointReq *r) {
219   PointReq **entry;
220   entry= bsearch(r, q->l, q->n, sizeof(q->l[0]), pt_req_compar);
221   assert(entry);
222   pt_queue_remove_index(q, entry - q->l);
223 }
224
225 static void pt_dequeue(PointReq *r) { /* X->XA */
226   if (r->motions[0].i) {
227     pt_queue_remove_item(&pt_confirmed, r);
228   } else if (~r->deadline) {
229     pt_queue_remove_item(&pt_reserved, r);
230   } else {
231     return;
232   }
233   pt_check_plan();
234 }
235
236 static void pt_mark_as_allocated(PointReq *r) { /* AX->X */
237   /* Sets statedet fields for Allocated */
238   r->deadline= ~(PtSlot)0;
239   r->motions[0].i=0;
240 }
241
242 #define WHICH(wh)                               \
243   (whichr= wh##r,                               \
244    whichwhen= wh##when,                         \
245    wh++)
246
247 static ErrorCode pt_check_plan(void) {
248   /* Checks whether we can meet the currently queued commitments */
249   int future, conf, resv, whichwhen;
250   PointReq *whichr;
251
252   conf=resv=0;
253   future=0;
254
255   oprintf(DUPO("movpos/point") "  plan");
256
257   /* If CDU is charged we can't do one right away */
258   if (!pt_cdu_charged) {
259     oprintf(UPO, " +");
260     future++;
261   }
262
263   for (;;) {
264     PointReq *confr= conf < pt_confirmed.n ? pt_confirmed.l[conf] : 0;
265     PointReq *resvr= resv < pt_reserved .n ? pt_reserved .l[resv] : 0;
266     if (!confr && !resvr) break;
267     oprintf(UPO," %d:",future);
268     int confwhen= confr ? confr->deadline - pt_cslot : INT_MAX;
269     int resvwhen= resvr ? resvr->deadline            : INT_MAX;
270     if (future && resvwhen < confwhen) {
271       WHICH(resv);
272       oprintf(UPO,"~");
273     } else if (confr) {
274       WHICH(conf);
275     } else {
276       oprintf(UPO,"-");
277       future++;
278       continue;
279     }
280     oprintf(UPO, "%s/%s[%d@t+%d]", whichr->h.move->i->pname,
281             posnpname(whichr->h.move, whichr->h.intent),
282             whichr->n_motions, whichwhen);
283     if (future > whichwhen) {
284       oprintf(UPO,"!...bad\n");
285       return EC_MovFeatTooLate;
286     }
287     future += whichr->n_motions;
288   }
289   oprintf(UPO," ok\n");
290   return 0;
291 }
292
293 #undef WHICH
294
295 static ErrorCode pt_enqueue(PointQueue *q, PointReq *r) { /* XA -> X */
296   int insat;                 /* ... where X is R or C and corresponds to q */
297                              /* or on error,   XA -> A */
298
299   if (q->n == PT_MAX_QUEUE) {
300     return EC_BufferFull;
301   }
302
303   for (insat= q->n;
304        insat>0 && (PtSlotSigned)(r->deadline - q->l[insat-1]->deadline) < 0;
305        insat--)
306     q->l[insat]= q->l[insat-1];
307   q->l[insat]= r;
308   q->n++;
309
310   return pt_check_plan();
311   /* if this fails, indep machinery calls pt_destroy which dequeues */
312 }
313
314 /*---------- kind method entrypoints ----------*/
315
316 static Change *point_allocate(int alloc_motions) {
317   PointReq *r;
318
319   assert(pt_cdu_charged>=0);
320   if (!alloc_motions)
321     /* we need at least one motion in the table so we can tell
322      *  the difference between the states by looking at motions[0].i */
323     alloc_motions= 1;
324
325   r= mmalloc(sizeof(*r) + alloc_motions * sizeof(r->motions[0]));
326   r->deadline= ~(PtSlot)0;
327   r->n_motions= alloc_motions;
328   r->motions[0].i= 0;
329   return (Change*)r;
330 }
331
332 static ErrorCode point_reserve(Change *chg, Segment *move,
333                                int maxdelay_ms) {
334   PointReq *r= (PointReq*)chg;
335   PtSlotSigned reldeadline;
336
337   reldeadline= pt_maxdelay_reldeadline(maxdelay_ms, r->n_motions);
338   if (reldeadline <= 0) { pt_mark_as_allocated(r); return EC_MovFeatTooLate; }
339   r->deadline= reldeadline;
340   return pt_enqueue(&pt_reserved, r);
341 }
342
343 static ErrorCode point_confirm(Change *chg, Segment *move,
344                                int n_motions, const Motion *motions,
345                                int maxdelay_ms) {
346   PointReq *r= (PointReq*)chg;
347   PtSlotSigned reldeadline;
348   int allow_failure;
349   ErrorCode ec;
350
351   oprintf(DUPO("movpos/point") "confirm %s n=%d maxdelay=%dms"
352           " (res: [%d@t+%d])\n",
353           move->i->pname, n_motions, maxdelay_ms,
354           r->n_motions, r->deadline);
355
356   /* If the segment is moving, these motions are already based on the
357    * actual physical position which is stored in the existing request.
358    * So we try removing the existing request from the queue and put
359    * it back if it doesn't work.
360    */
361
362   if (n_motions > r->n_motions)
363     return EC_MovFeatReservationInapplicable;
364   assert(n_motions <= r->n_motions);
365   if (maxdelay_ms == -1) {
366     reldeadline= r->deadline;
367     if (!~r->deadline) reldeadline= pt_maxdelay_reldeadline(-1, n_motions);
368   } else {
369     reldeadline= pt_maxdelay_reldeadline(maxdelay_ms, n_motions);
370   }
371   allow_failure= reldeadline < (PtSlotSigned)r->deadline;
372   oprintf(DUPO("movpos/point") " reldeadline=[%d@t+%d] allow_failure=%d\n",
373           n_motions, reldeadline, allow_failure);
374
375   /* state A or R */
376   pt_dequeue(r);
377                                            /* states of existing: */
378   PointReq *existing=
379     move->moving ? (PointReq*)move->motion : 0;   /* U or C */
380   if (existing) {
381     oprintf(DUPO("movpos/point")
382             " existing %s n=%d deadline=t+%d\n",
383             existing->h.move->i->pname,
384             existing->n_motions,
385             existing->deadline - pt_cslot);
386     pt_dequeue(existing);                         /* U or CA */
387   }
388
389   /* state A or RA */
390   memcpy(r->motions, motions, sizeof(r->motions[0])*n_motions);
391   if (!n_motions) r->motions[0].i= move->i->movfeats;
392   assert(r->motions[0].i);
393   r->n_motions= n_motions;
394   r->deadline= reldeadline + pt_cslot;
395
396   if (n_motions == move->i->n_movfeats)
397     r->actual= 0;
398   else
399     r->actual= chg->actual;
400   assert(r->actual >= 0);
401
402   /* state CA */
403   ec= pt_enqueue(&pt_confirmed, r);
404   oprintf(DUPO("movpos/point") " pt_enqueue=%s\n", ec2str(ec));
405   assert(allow_failure || !ec);
406
407   if (existing) {                                  /* CA */
408     if (ec) { /* state C but bad */
409       pt_dequeue(r); /* state CA */
410       pt_mark_as_allocated(r); /* state A */
411       ErrorCode ec_putback= pt_enqueue(&pt_confirmed, existing);
412       assert(!ec_putback);                         /* C */
413     } else { /* state C and good */
414       free(existing);                              /* U */
415     }
416   }
417   /* either  ec=0   state C                            U
418    *     or  ec!=0  state A                            C
419    *     or  ec!=0  state C but bad                    C
420    */
421
422   if (ec) return ec;
423
424   move->moving= 1;
425   move->motion= chg;
426   move->movposcomb= -1;
427   ouposn_moving(chg);
428   pt_check_action();
429   return 0;
430 }
431
432 static void point_destroy(Change *chg) { /* X->XA and then free it */
433   PointReq *r= (PointReq*)chg;
434   pt_dequeue(r);
435   free(r);
436 }
437
438 /*---------- actually firing points, yay! ----------*/
439
440 static void pt_check_action(void) {
441   PicInsn piob;
442   ErrorCode ec;
443
444   if (!pt_confirmed.n) {
445     if (sta_state == Sta_Finalising) resolve_motioncheck();
446     return;
447   }
448
449   PointReq *r= pt_confirmed.l[0];
450
451   if (r->n_motions && pt_cdu_charged) {
452     /* look for something to fire */
453     Motion *m= &r->motions[--r->n_motions];
454     assert(m->posn < m->i->posns);
455     enco_pic_point(&piob, m->i->boob[m->posn]);
456     serial_transmit(&piob);
457     oprintf(UPO, "movpos %s point %s%d\n", r->h.move->i->pname,
458             m->i->pname, m->posn);
459     pt_cdu_charged= 0;
460     pt_cslot++;
461
462     MovPosComb above_weight= m->i->weight * m->i->posns;
463     MovPosComb above= r->actual / above_weight;
464     MovPosComb below= r->actual % m->i->weight;
465     r->actual= above*above_weight + m->posn*m->i->weight + below;
466     if (r->h.actual >= 0 || !r->n_motions)
467       r->h.actual= r->actual;
468     ouposn_moving(&r->h);
469   }
470
471   if (!r->n_motions) {
472     /* look for something to report
473      * we can get things here other than from the above
474      * eg if we are asked to move the 
475      */
476     Segment *move= r->h.move;
477     assert(move->moving && move->motion == (Change*)r);
478     pt_queue_remove_index(&pt_confirmed,0);
479     pt_mark_as_allocated(r); /* now state A aka Done */
480     motion_done(move,r->h.actual);
481     free(r);
482     ec= pt_check_plan();  assert(!ec);
483     pt_check_action();
484   }
485 }
486
487 /*---------- entrypoints from rest of program ----------*/
488
489 void points_all_abandon(void) {
490   int i;
491
492   assert(!pt_reserved.n);
493
494   for (i=0; i<pt_confirmed.n; i++) {
495     PointReq *r= pt_confirmed.l[i];
496     Segment *move= r->h.move;
497     assert(move->motion == (Change*)r);
498     motion_done(move,r->h.actual);
499     free(r);
500   }
501   pt_confirmed.n= 0;
502   pt_cdu_charged= -1;
503 }
504
505 void points_turning_on(void) {
506   pt_cdu_charged= 0;
507 }
508
509 void on_pic_charged(const PicInsnInfo *pii, const PicInsn *pi, int objnum) {
510   if (pt_cdu_charged<0) return;
511   pt_cdu_charged= 1;
512   pt_check_action();
513 }
514
515 /*========== dummy `nomove' kind ==========*/
516
517 static Change *nomove_allocate(int alloc_motions) {
518   oprintf(DUPO("movfeatkind-momove") "allocate %d\n",alloc_motions);
519   return mmalloc(sizeof(Change));
520 }
521 static void nomove_destroy(Change *chg) {
522   free(chg);
523 }
524
525 static ErrorCode nomove_reserve(Change *chg, Segment *move, int ms) {
526   oprintf(DUPO("movfeatkind-nomove") "reserve\n");
527   return 0;
528 }
529 static ErrorCode nomove_confirm(Change *chg, Segment *move, int n_motions,
530                        const Motion *motions, int ms) {
531   oprintf(DUPO("movfeatkind-nomove") "confirm\n");
532   nomove_destroy(chg);
533   return 0;
534 }
535
536 /*========== method-independent machinery ==========*/
537
538 static const KindInfo methodinfos[]= {
539  { "nomove", nomove_allocate, nomove_reserve, nomove_confirm, nomove_destroy },
540  { "point",  point_allocate,  point_reserve,  point_confirm,  point_destroy  },
541  { 0 }
542 };
543
544 static Change *mp_allocate(const KindInfo *ki, Segment *move,
545                            int alloc_motions, MovPosComb target) {
546   assert(sta_state >= Sta_Resolving);
547   Change *chg= ki->allocate(alloc_motions);
548   chg->ki=        ki;
549   chg->move=      move;
550   chg->intent=    target;
551   return chg;
552 }
553
554 static int change_needed(const MovFeatInfo *feati, MovPosComb target,
555                         MovPosComb startpoint) {
556   int r;
557   r= startpoint<0 ||
558     (target - startpoint) / feati->weight % feati->posns;
559   oprintf(DUPO("movpos/change-needed") "%s:%s(%d*%d) %d<-%d => %d\n",
560           methodinfos[feati->kind].pname, feati->pname,
561           feati->posns, feati->weight,
562           target, startpoint, r);
563   return r;
564 }  
565
566 static int evaluate_target(Segment *move, MovPosComb target,
567                            MovPosComb startpoint, MovFeatKind *kind_r) {
568   /* returns number of features which have to change to reach target,
569    * or -1 for mixed kinds.  kind_r may be 0. */
570   const SegmentInfo *movei= move->i;
571   int feat, tchanges;
572   const MovFeatInfo *feati;
573   MovFeatKind kind;
574
575   oprintf(DUPO("movpos/eval") "%s/%s <-%s\n",
576           move->i->pname, posnpname(move,target), posnpname(move,startpoint));
577
578   if (startpoint<0) {
579     startpoint= movpos_poscomb_actual(move);
580     oprintf(DUPO("movpos/eval") "  actual <-%s\n",
581             posnpname(move,startpoint));
582   }
583
584   for (feat=0, feati=movei->movfeats, tchanges=0, kind= mfk_none;
585        feat<movei->n_movfeats;
586        feat++, feati++) {
587     if (!change_needed(feati,target,startpoint)) continue;
588     tchanges++;
589     if (kind && feati->kind != kind) return -1;
590     kind= feati->kind;
591   }
592
593   if (kind_r) *kind_r= kind;
594   oprintf(DUPO("movpos/eval") "changes=%d kind=%s\n",
595           tchanges, methodinfos[kind].pname);
596   return tchanges;
597 }
598
599 ErrorCode movpos_findcomb_bysegs(Segment *back, Segment *move, Segment *fwd,
600                                  MovPosComb startpoint, MovPosComb *chosen_r) {
601   const SegmentInfo *movei= move->i;
602   MovPosComb tcomb, bestcomb=-1;
603   int tchanges, bestchanges=INT_MAX;
604   const SegPosCombInfo *pci;
605
606   for (tcomb=0, pci=movei->poscombs;
607        tcomb<movei->n_poscombs;
608        tcomb++, pci++) {
609     Segment *tback= &segments[pci->link[1].next];
610     Segment *tfwd=  &segments[pci->link[0].next];
611     if (back && !(back==tback || back==tfwd)) continue;
612     if (fwd  && !(fwd ==tback || fwd ==tfwd)) continue;
613
614     if (movei->n_movfeats>1) {
615       /* we have to search for the one which is least effort, then */
616       tchanges= evaluate_target(move,tcomb,startpoint,0);
617       if (tchanges >= bestchanges) /* prefer low-numbered movposcombs */
618         continue;
619       if (tchanges==-1) {
620         tchanges= INT_MAX-1;
621         /* fall through and update */
622       }
623     } else {
624       tchanges= 1;
625     }
626     bestcomb= tcomb;
627     bestchanges= tchanges;
628   }
629   if (chosen_r) *chosen_r= bestcomb;
630   return
631     bestchanges==INT_MAX ? EC_MovFeatRouteNotFound :
632     bestchanges==INT_MAX-1 ? EC_MovFeatKindsCombination :
633     0;
634 }
635
636 ErrorCode movpos_change(Segment *move, MovPosComb target,
637                         int maxdelay_ms, MovPosChange *chg) {
638   const SegmentInfo *movei= move->i;
639   const MovFeatInfo *feati;
640   int feat;
641   MovPosComb actual;
642   ErrorCode ec;
643   MovFeatKind kind= mfk_none;
644
645   if (!move->moving) {
646     actual= move->movposcomb;
647     assert(!move->motion);
648   } else {
649     kind= move->motion->ki - methodinfos;
650     actual= move->motion->actual;
651   }
652
653   oprintf(DUPO("movpos/change") "%s/%s maxdelay=%dms actual=%s\n",
654           move->i->pname, posnpname(move,target),
655           maxdelay_ms, posnpname(move, actual));
656   if (chg) oprintf(DUPO("movpos/change") " chg=%s:%s/%s\n",
657                    chg->ki->pname, chg->move->i->pname,
658                    posnpname(chg->move, chg->intent));
659
660   { /* provide horizon for visibility of motions[] */
661     int n_motions=0;
662     Motion motions[movei->n_movfeats];
663
664     for (feat=0, feati=movei->movfeats;
665          feat<movei->n_movfeats;
666          feat++, feati++) {
667       if (!change_needed(feati,actual,target))
668         continue;
669       MovPosComb posn= target / feati->weight % feati->posns;
670       if (kind) {
671         if (feati->kind != kind) { ec= EC_MovFeatKindsCombination; goto x; }
672       } else {
673         kind= feati->kind;
674       }
675       motions[n_motions].i= feati;
676       motions[n_motions].posn= posn;
677       n_motions++;
678     }
679
680     const KindInfo *ki= &methodinfos[kind];
681
682     if (chg) {
683       if (chg->ki != ki ||
684           chg->move != move ||
685           chg->intent != target)
686         return EC_MovFeatReservationInapplicable;
687     } else {
688       chg= mp_allocate(ki,move,n_motions,target);
689     }
690     chg->actual= actual;
691
692     oprintf(DUPO("movpos/change") "confirm %s:%d...\n", ki->pname, n_motions);
693     ec= ki->confirm(chg, move, n_motions, motions, maxdelay_ms);
694     oprintf(DUPO("movpos/change") "confirm => %s\n",errorcodelist[ec]);
695     if (ec) goto x;
696   }
697   return 0;
698
699  x:
700   movpos_unreserve(chg);
701   return ec;
702 }
703
704 ErrorCode
705 movpos_reserve(Segment *move, int maxdelay_ms, MovPosChange **res_r,
706                MovPosComb target, MovPosComb startpoint /*as for findcomb*/) {
707   MovFeatKind kind= mfk_none;
708   ErrorCode ec;
709   int nchanges;
710
711   oprintf(DUPO("movpos/reserve") "%s/%s maxdelay=%dms startpoint=%s\n",
712           move->i->pname, posnpname(move,target),
713           maxdelay_ms, posnpname(move,startpoint));
714
715   nchanges= evaluate_target(move,target,startpoint,&kind);
716   if (nchanges==-1) return EC_MovFeatKindsCombination;
717
718   const KindInfo *ki= &methodinfos[kind];
719   oprintf(DUPO("movpos/reserve") "allocate %s:%d...\n", ki->pname, nchanges);
720   Change *chg= mp_allocate(ki, move, nchanges, target);
721   ec= ki->reserve(chg, move, maxdelay_ms);
722   oprintf(DUPO("movpos/reserve") "reserve => %s\n",errorcodelist[ec]);
723   if (ec) goto x;
724
725   *res_r= chg;
726   return 0;
727
728  x:
729   movpos_unreserve(chg);
730   return ec;
731 }
732
733 void movpos_unreserve(MovPosChange *res) {
734   if (!res) return;
735   oprintf(DUPO("movpos/unreserve") "%s:%s/%s\n",
736           res->ki->pname, res->move->i->pname,
737           posnpname(res->move, res->intent));
738   res->ki->destroy(res);
739 }
740
741 MovPosComb movpos_poscomb_actual(Segment *seg) {
742  return seg->moving ? seg->motion->actual : seg->movposcomb;
743 }
744
745 MovPosComb movpos_change_intent(MovPosChange *chg) {
746   return chg->intent;
747 }