chiark / gitweb /
4bbc7f7f78c579f16c2d2919a1714d28a752fe18
[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 Method Method;
15 typedef struct Change Change;
16
17 typedef struct MovPosChange {
18   Segment *move;
19   /* everything beyond here is private for indep */
20   MovPosComb actual, target;
21   int refcount;
22   int n_changes;
23   Change *changes[];
24 } Indep;
25
26 static void method_update_feature(Method*, MovPosChange*, const Motion *mo);
27   /* Called from methods' execution logic when an individual feature
28    * has been moved.  This is used by the method-independent code to
29    * compute the correct delta set of movements from the current
30    * actual position, when thinking about new plans.  It is also sent
31    * to clients and ultimately used by the UI.
32    */
33
34 static void method_change_done(Method *m, Change *chg);
35   /* Called from methods' execution logic when a whole change
36    * has been done.  The method-independent code will take care of
37    * updating move->movposcomb. etc.
38    *
39    * REENTRANCY: May be called from within a call to the method's
40    * execute().  Of course cannot legally be called from within
41    * prepare, consider, check or dispose.
42    */
43
44
45 /* Kind-independent code is responsible for determining
46  * the method, doing a bit of cleanup, and adjusting the flow
47  * slightly.  Per-kind code does the actual work and is mostly in
48  * charge - it is also responsible for updating seg->moving and ->motion.
49  */
50 /*
51  * The following states exist for each Method
52  *   S  Starting     corresponds to global states other than Sta_Run
53  *   T  Tentative    changes have been made but may yet be undone
54  *   Y  Yes          proposed changes have been checked and are OK
55  *   E  Executing    the method is executing
56  *
57  * The following states exist for each Change
58  * at points when control flow  passes between kind and indep:
59  *   U  Unallocated  no memory allocated (Change does not exist)
60  *   P  Prepared     memory allocation done, basics filled in
61  *   I  Installed    reservation or confirmation successful
62  *   D  Done         motion is complete and callback is being entered
63  *   G  Garbage      motion is complete and callback is being exited
64  *
65  * Changes may be for:
66  *   R  Reservation
67  *   C  Confirmation
68  *
69  * No Changes remain Prepared while the Method is Executing.
70  */
71
72 struct Change {                    /* valid in:  filled in by and when:      */
73   Method *meth;                    /*  PIDG       indep after prepare()      */
74   MovPosChange *indep;             /*  PID        indep after prepare()      */
75   unsigned installed:1; /* private for indep */
76   /* kind-specific data follows */ /*  varies     kind-specific code, varies */
77 };
78
79 struct Method {
80   const char *pname;
81
82   ErrorCode (*prepare)(Method *m,                                 /* TYE->T */
83                        const Segment *move,
84                        int n_motions, const Motion *motions,
85                        int ms, int confirmation,
86                        Change **chg_r,          /* 0->P; err: 0->0; may be 0 */
87                        int *cost_r /* may be 0 */);
88   void (*dispose)(Method *m,                                       /* TY->TY */
89                   Change*);                                          /* P->U */
90
91   ErrorCode (*install)(Method *m,                   /* TYE->T; err: TYE->TYY */
92                        Change *inst);                     /* P->I; err: P->P */
93       /* error: ET->T, no change to Changes */
94   void (*remove)(Method *m,                                      /* TYE->TYY */
95                  Change *remove);                                    /* I->P */
96       /* error: ET->T, no change to Changes */
97
98   ErrorCode (*check)(Method *m);                    /* TYE->Y; err: TYE->TYE */
99   void (*execute)(Method *m);                                       /* EY->E */
100   void (*all_abandon)(Method *m);                                  /* TYE->S */
101
102   unsigned needcheck, needexec; /* used by indep to track T/Y/E */
103 };
104
105 /*========== general utility functions ==========*/
106
107 const char *movpos_pname(const Segment *move, MovPosComb poscomb) {
108   return !SOMEP(poscomb) ? "?" : move->i->poscombs[poscomb].pname;
109 }
110
111 static void ouposn_moving(const MovPosChange *indep) {
112   Segment *move= indep->move;
113   ouprintf("movpos %s position %s moving\n",
114            move->i->pname, movpos_pname(move, indep->actual));
115 }
116
117 MovPosComb movposcomb_update_feature(MovPosComb startpoint,
118                                      const MovFeatInfo *mfi,
119                                      int featpos) {
120   MovPosComb above_weight= mfi->weight * mfi->posns;
121   MovPosComb above= startpoint / above_weight;
122   MovPosComb below= startpoint % mfi->weight;
123   return above*above_weight + featpos*mfi->weight + below;
124 }
125
126 MovPosComb movpos_poscomb_actual(const Segment *seg) {
127   return seg->moving ? seg->motion->actual : seg->movposcomb;
128 }
129
130 static void ignore_all_abandon(Method *m) { }
131
132 /*========== points and other fixed timeslot movfeats ==========*/
133
134 /*
135  * We maintain two queues, one for reserved one for actually confirmed
136  *  requests where we know what we're doing.
137  *
138  * We divide time into discrete slots, numbered with clock arithmetic.
139  *
140  *     cslot         cslot+1      cslot+2
141  *
142  *     currently     next in      after
143  *     changing      line         that
144  *
145  * We increment cslot when we consider the movfeat to move;
146  * for points and relays this is when we issue the command to the PIC.
147  * In a request, the deadline represents the latest allowable value
148  * of cslot just before that increment.
149  */
150
151 typedef unsigned FsqSlot;
152 typedef int FsqSlotSigned;
153
154 #define FSQDN (~(FsqSlot)0)
155
156 typedef struct {   /* state      Prep Resv  Inst Resv  Prep Conf  Inst Conf   */
157   Change h;        /* in queue?  absent     reserved   absent     confirmed   */
158   FsqSlot deadline; /*           relative   relative   absolute   absolute    */
159   int n_motions;    /*            1          1         num undone num undone  */
160   Motion motions[]; /*  [0].i:    0          0         non-0      non-0       */
161                     /*  .posn:    undef      undef     defined    defined     */
162 } FsqReq;
163
164 #define FSQ_MAX_QUEUE  15
165
166 typedef struct {
167   int n;
168   FsqReq *l[FSQ_MAX_QUEUE];
169 } FsqQueue;
170
171 typedef struct FsqMethod FsqMethod;
172
173 typedef struct {
174   /* set by fsq's client before fsq_init */
175   int slot_ms, lag_ms;
176   void (*move)(FsqMethod *m, const MovFeatInfo *mfi, int movfeatposn);
177   /* shared */
178   int ready; /* >0 means ready; set to 0 by fsq just before move */
179   /* fsq's client must arrange that these start out at all-bits-zero */
180   FsqSlot cslot;
181   FsqQueue confirmed, reserved;
182 } FsqState;
183
184 struct FsqMethod {
185   Method m;
186   FsqState f;
187   /* client may put things here */
188 };
189
190 static void fsq_check_action(FsqMethod *m);
191 static ErrorCode fsq_check_plan(FsqMethod *m);
192
193 static FsqSlotSigned fsq_maxdelay_reldeadline(FsqMethod *m,
194                                               int maxdelay_ms, int n_motions) {
195   if (maxdelay_ms==-1) return FSQ_MAX_QUEUE*16;
196   return (maxdelay_ms - m->f.lag_ms) / m->f.slot_ms - n_motions;
197 }
198
199 static void fsq_queue_remove_index(FsqQueue *q, int index) {
200   q->n--;
201   memmove(&q->l[index], &q->l[index+1], sizeof(q->l[0]) * (q->n - index));
202 }
203
204 static int fsq_req_compar_approx(const void *av, const void *bv) {
205   FsqReq *const *a= av;
206   FsqReq *const *b= av;
207   FsqSlotSigned dldiff= (*b)->deadline - (*a)->deadline;
208   return dldiff;
209 }
210
211 static void fsq_queue_remove_item(FsqQueue *q, FsqReq *r) {
212   FsqReq **entry;
213   int i;
214   entry= bsearch(r, q->l, q->n, sizeof(q->l[0]), fsq_req_compar_approx);
215   assert(entry);
216
217 #define SEARCH_CHECK do{                                \
218     if (q->l[i] == r) goto found;                       \
219     if (q->l[i]->deadline != r->deadline) break;        \
220 }while(0)
221   for(i= entry - q->l;     i >= 0;   i--)  SEARCH_CHECK;
222   for(i= entry - q->l + 1; i < q->n; i++)  SEARCH_CHECK;
223   abort();
224
225  found:
226   fsq_queue_remove_index(q, i);
227 }
228
229 static FsqQueue *fsq_item_queue(FsqMethod *m, FsqReq *r)
230   { return r->motions[0].i ? &m->f.confirmed : &m->f.reserved; }
231
232 #define WHICH(wh)                               \
233   (whichr= wh##r,                               \
234    whichwhen= wh##when,                         \
235    wh++)
236
237 static ErrorCode fsq_check_plan(FsqMethod *m) {
238   /* Checks whether we can meet the currently queued commitments */
239   /* if this fails, indep machinery calls fsq_prepare to dequeue */
240   int future, conf, resv, whichwhen, DP;
241   FsqReq *whichr;
242
243   conf=resv=0;
244   future=0;
245
246   DPRINTF1(movpos,fsq, "%s   plan", m->m.pname);
247
248   /* If CDU isn't charged we can't do one right away */
249   if (m->f.ready<0) {
250     DPRINTF2(" +");
251     future++;
252   }
253
254   for (;;) {
255     FsqReq *confr= conf < m->f.confirmed.n ? m->f.confirmed.l[conf] : 0;
256     FsqReq *resvr= resv < m->f.reserved .n ? m->f.reserved .l[resv] : 0;
257     if (!confr && !resvr) break;
258     DPRINTF2(" %d:",future);
259     int confwhen= confr ? confr->deadline - m->f.cslot : INT_MAX;
260     int resvwhen= resvr ? resvr->deadline              : INT_MAX;
261     if (future && resvwhen < confwhen) {
262       WHICH(resv);  DPRINTF2("~");
263     } else if (confr) {
264       WHICH(conf);
265     } else {
266       future++;     DPRINTF2("-");
267       continue;
268     }
269     DPRINTF2("%s/%s[%d@t+%d]", whichr->h.indep->move->i->pname,
270             movpos_pname(whichr->h.indep->move, whichr->h.indep->target),
271             whichr->n_motions, whichwhen);
272     if (future > whichwhen) {
273       DPRINTF2("!...bad\n");
274       return EC_MovFeatTooLate;
275     }
276     future += whichr->n_motions;
277   }
278   DPRINTF2(" ok\n");
279   return 0;
280 }
281
282 #undef WHICH
283
284 static ErrorCode fsq_queue_insert_item(FsqMethod *m, FsqQueue *q, FsqReq *r) {
285   int insat;
286
287   if (q->n == FSQ_MAX_QUEUE)
288     return EC_BufferFull;
289
290   for (insat= q->n;
291        insat>0 && (FsqSlotSigned)(r->deadline - q->l[insat-1]->deadline) < 0;
292        insat--)
293     q->l[insat]= q->l[insat-1];
294   q->l[insat]= r;
295   q->n++;
296
297   return 0;
298 }
299
300 static void fsq_item_debug(FsqMethod *m, FsqReq *r, const char *opwhat) {
301   int DP;
302   Segment *move= r->h.indep->move;
303   DPRINTF1(movpos,fsq, "%s %s %s", m->m.pname, opwhat, move->i->pname);
304   if (r->motions[0].i) {
305     int i;
306     Motion *mo;
307     for (i=0, mo=r->motions; i<r->n_motions; i++, mo++)
308       DPRINTF2("/%s%d", mo->i->pname, (int)mo->posn);
309     DPRINTF2("\n");
310   } else {
311     DPRINTF2("(%d)\n", r->n_motions);
312   }
313 }
314  
315 static ErrorCode fsq_enqueue(FsqMethod *m, FsqReq *r) { /* P->I; err: P->P */
316   if (!r) return 0;
317   fsq_item_debug(m,r,"enqueue");
318   return fsq_queue_insert_item(m, fsq_item_queue(m,r), r);
319 }
320
321 static void fsq_dequeue(FsqMethod *m, FsqReq *r) { /* I->P */
322   if (!r) return;
323   fsq_item_debug(m,r,"dequeue");
324   fsq_queue_remove_item(fsq_item_queue(m,r), r);
325 }
326
327 /*---------- method entrypoints ----------*/
328
329 static ErrorCode fsq_prepare(Method *mm, const Segment *move,
330                              int n_motions, const Motion *motions,
331                              int ms, int confirmation,
332                              Change **chg_r, int *cost_r) {
333   FsqMethod *m= (void*)mm;
334
335   assert(n_motions > 0);
336
337   FsqSlotSigned reldeadline= fsq_maxdelay_reldeadline(m, ms, n_motions);
338   if (reldeadline <= 0) return EC_MovFeatTooLate;
339
340   if (chg_r) {
341     int alloc_motions= confirmation ? n_motions : 1;
342       /* we need at least one motion in the table so we can tell
343        *  the difference between the states by looking at motions[0].i */
344     FsqReq *r= mmalloc(sizeof(*r) + alloc_motions * sizeof(r->motions[0]));
345     r->n_motions= n_motions;
346     if (confirmation) {
347       r->deadline= reldeadline + m->f.cslot;
348       memcpy(r->motions, motions, sizeof(*r->motions)*n_motions);
349     } else {
350       r->deadline= reldeadline;
351       r->motions[0].i= 0;
352     }
353     *chg_r= &r->h;
354   }
355   if (cost_r) {
356     *cost_r= n_motions * m->f.slot_ms;
357   }
358
359   return 0;
360 }
361
362 static void fsq_dispose(Method *mm, Change *chg) {
363   FsqReq *r= (FsqReq*)chg;
364   free(r);
365 }
366
367 static void fsq_remove(Method *mm, Change *remvchg) {
368   FsqMethod *m= (void*)mm;
369   FsqReq *remv= (FsqReq*)remvchg;
370
371   fsq_dequeue(m, remv);
372 }
373
374 static ErrorCode fsq_install(Method *mm, Change *instchg) {
375   FsqMethod *m= (void*)mm;
376   FsqReq *inst= (FsqReq*)instchg;
377
378   return fsq_enqueue(m, inst);
379 }
380
381 static ErrorCode fsq_check(Method *mm) { 
382   FsqMethod *m= (void*)mm;
383   ErrorCode ec= fsq_check_plan(m);
384   return ec;
385 }
386
387 static void fsq_execute(Method *mm) {
388   FsqMethod *m= (void*)mm;
389   fsq_check_action(m);
390 }
391
392 /*---------- something to do ? ----------*/
393
394 static void fsq_check_action(FsqMethod *m) {
395   /* client should call this after it sets ready */
396   ErrorCode ec;
397
398   if (!m->f.confirmed.n) {
399     if (sta_state == Sta_Finalising) resolve_motioncheck();
400     return;
401   }
402
403   FsqReq *r= m->f.confirmed.l[0];
404
405   if (r->n_motions && m->f.ready>0) {
406     /* look for something to move */
407     Motion *mo= &r->motions[--r->n_motions];
408     assert(mo->posn < mo->i->posns);
409     m->f.ready= 0;
410     m->f.move(m, mo->i, mo->posn);
411     m->f.cslot++;
412
413     method_update_feature(&m->m, r->h.indep, mo);
414   }
415
416   if (!r->n_motions) {
417     fsq_queue_remove_index(&m->f.confirmed, 0);
418     method_change_done(&m->m, &r->h);
419     m->m.dispose(&m->m, &r->h);
420
421     ec= fsq_check_plan(m);  assert(!ec);
422     fsq_check_action(m);
423   }
424 }
425
426 /*========== points ==========*/
427
428 /*
429  * CDU and point queue states:
430  *
431  *
432  *    ____________                              conf'd
433  *   /   points_  \                 ready         .n
434  *   |   all_      |
435  *   |   abandon   |
436  *   |             V
437  *   |from       INACTIVE               -1      0
438  *   |any       <=Sta_Settling
439  *  ^^^^^^^^     (start)
440  *                 |
441  *     ___________ |turning
442  *    /           \| _on
443  *   |             V
444  *   |           CHARGING               0       any
445  *   |          >=Sta_Resolving
446  *   |             |
447  *   |             |on_pic
448  *   |             |_charged
449  *   |             V
450  *   ^           READY                  1       any
451  *   |             |
452  *   |             |fsq_check_action
453  *   |             | calls point_move which fires a point
454  *    \___________/
455  *
456  */
457
458 #define CDU_RECHARGE   350 /*ms*/
459 #define POINT_MOVEMENT  50 /*ms*/
460
461 static ErrorCode point_prepare(Method *mm, const Segment *move,
462                                int n_motions, const Motion *motions,
463                                int ms, int confirmation,
464                                Change **chg_r, int *cost_r) {
465   FsqMethod *m= (void*)mm;
466   assert(m->f.ready>=0);
467   return fsq_prepare(mm,move, n_motions,motions,
468                      ms,confirmation, chg_r,cost_r);
469  }
470
471 static void point_move(FsqMethod *m, const MovFeatInfo *mfi, int posn) {
472   /* actually firing points, yay! */
473   PicInsn piob;
474   enco_pic_point(&piob, mfi->boob[posn]);
475   serial_transmit(&piob);
476 }
477
478 static void points_all_abandon(Method *mm) {
479   FsqMethod *m= (void*)mm;
480   m->f.ready= -1;
481 }
482
483 static FsqMethod points_method;
484
485 void points_turning_on(void) {
486   FsqMethod *m= &points_method;
487   m->f.ready= 0;
488 }
489 void on_pic_charged(const PicInsnInfo *pii, const PicInsn *pi, int objnum) {
490   FsqMethod *m= &points_method;
491   if (m->f.ready<0) return;
492   m->f.ready= 1;
493   fsq_check_action(m);
494 }
495
496 static FsqMethod points_method= {
497   { "point",
498     point_prepare, fsq_dispose,
499     fsq_install, fsq_remove,
500     fsq_check, fsq_execute, points_all_abandon },
501   { .lag_ms= POINT_MOVEMENT, .slot_ms= CDU_RECHARGE, .move= point_move }
502 };
503
504 /*========== relays ==========*/
505
506 /*
507  * Waggler states:
508  *
509  *    ____________                              conf'd
510  *   /  wagglers_ \                 ready         .n
511  *   |   all_      |
512  *   |   abandon   |
513  *   |             V
514  *   |from       UNKNOWN                -1      0
515  *   |any       <=Sta_Settling
516  *  ^^^^^^^^     (start)
517  *                 |
518  *     ___________ |turning
519  *    /           \| _on
520  *   |             V
521  *   |           CHARGING               0       any
522  *   |          >=Sta_Resolving
523  *   |             |
524  *   |             |on_pic
525  *   |             |_charged
526  *   |             V
527  *   ^           READY                  1       any
528  *   |             |
529  *   |             |fsq_check_action
530  *   |             | calls waggle_do which switches a relay
531  *    \___________/
532  *
533  */
534
535 static FsqMethod waggle;
536
537 static void waggle_do(FsqMethod *m, const MovFeatInfo *mfi, int posn) {
538   /* actually setting relays */
539   PicInsn piob;
540   enco_pic_waggle(&piob, mfi->boob[0], posn);
541   serial_transmit(&piob);
542 }
543
544 static SegmentNum waggle_settle_seg;
545 static int waggle_settle_feat;
546
547 static void waggle_settle_check(void) {
548   for (;;) {
549     if (waggle_settle_seg >= info_nsegments) return;
550
551     Segment *seg= &segments[waggle_settle_seg];
552     if (waggle_settle_feat >= seg->i->n_movfeats) {
553       waggle_settle_seg++; waggle_settle_feat=0; continue;
554     }
555
556     const MovFeatInfo *feati= &seg->i->movfeats[waggle_settle_feat];
557     if (feati->kind != mfk_relay) {
558       waggle_settle_feat++; continue;
559     }
560
561     waggle.f.ready= 0;
562     waggle_do(&waggle, feati, (seg->movposcomb / feati->weight) & 1);
563     waggle_settle_feat++;
564   }
565 }
566
567 void waggle_startup_manual(void) {
568   waggle.f.ready= 1;
569 }
570
571 void waggle_settle(void) {
572   waggle_settle_seg= 0;
573   waggle_settle_feat= 0;
574   waggle.f.ready= 1;
575   waggle_settle_check();
576 }
577
578 void on_pic_waggled(const PicInsnInfo *pii, const PicInsn *pi, int objnum) {
579   if (sta_state == Sta_Settling) {
580     waggle.f.ready= 1;
581     waggle_settle_check();
582   } else if (sta_state >= Sta_Resolving || sta_state == Sta_Manual) {
583     waggle.f.ready= 1;
584     fsq_check_action(&waggle);
585     return;
586   }
587 }
588
589 static FsqMethod waggle= {
590   { "relay",
591     fsq_prepare, fsq_dispose,
592     fsq_install, fsq_remove,
593     fsq_check, fsq_execute, ignore_all_abandon },
594   { .lag_ms= 5, .slot_ms= 50, .move= waggle_do }
595 };
596
597 /*========== dummy `nomove' kind ==========*/
598
599 typedef struct NomoveChange {
600   Change h;
601   DLIST_NODE(struct NomoveChange) inqueue;
602   int n_motions; /* 0 for reservations */
603   Motion motions[];
604 } NomoveChange;
605
606 typedef struct {
607   Method m;
608   NomoveChange *queuehead; /* contains confirmations only */
609 } NomoveMethod;
610
611 static ErrorCode nomove_prepare(Method *meth_in, const Segment *move,
612                                 int n_motions, const Motion *motions,
613                                 int ms, int confirming,
614                                 Change **chg_r, int *cost_r) {
615   if (chg_r) {
616     NomoveChange *chg;
617     assert(n_motions>0);
618     if (!confirming) n_motions= 0;
619     chg= mmalloc(sizeof(*chg) + sizeof(Motion)*n_motions);
620     chg->n_motions= n_motions;
621     memcpy(chg->motions, motions, sizeof(Motion)*n_motions);
622   }
623   if (cost_r) {
624     *cost_r= 0;
625   }
626   return 0;
627 }
628 static void nomove_dispose(Method *mm, Change *remvchg) {
629   NomoveChange *remv= (void*)remvchg;
630   free(remv);
631 }
632
633 static ErrorCode nomove_install(Method *mm, Change *instchg) {
634   NomoveMethod *meth= (void*)mm;
635   NomoveChange *inst= (void*)instchg;
636   if (inst->n_motions)
637     DLIST1_PREPEND(meth->queuehead, inst, inqueue);
638   return 0;
639 }
640 static void nomove_remove(Method *mm, Change *remvchg) {
641   NomoveMethod *meth= (void*)mm;
642   NomoveChange *remv= (void*)remvchg;
643   if (remv->n_motions)
644     DLIST1_REMOVE(meth->queuehead, remv, inqueue);
645 }
646
647 static ErrorCode nomove_check(Method *mm) { return 0; }
648
649 static void nomove_execute(Method *mm) {
650   NomoveMethod *meth= (void*)mm;
651   NomoveChange *done;
652
653   while ((done= meth->queuehead)) {
654     assert(done->n_motions);
655     int i;
656     for (i=0; i<done->n_motions; i++)
657       method_update_feature(&meth->m, done->h.indep, &done->motions[i]);
658     method_change_done(&meth->m, &done->h);
659     DLIST1_REMOVE(meth->queuehead, done, inqueue);
660     nomove_dispose(&meth->m, &done->h);
661   }
662 }
663
664 static Method nomove_method= {
665   "nomove",
666   nomove_prepare, nomove_dispose,
667   nomove_install, nomove_remove,
668   nomove_check, nomove_execute, ignore_all_abandon
669 };
670
671 /*========== method-independent machinery ==========*/
672
673 #define INDEP_DBG_FMT "<%p:%s/%s[%d]>"
674 #define INDEP_DBG_ARGS(in) (in),                                        \
675     ((in)->move->i->pname), (movpos_pname((in)->move, (in)->target)),   \
676     ((in)->n_changes)
677
678 #define INDEP_DPFX_FMT "movpos " INDEP_DBG_FMT " "
679 #define INDEP_DPFX_ARGS(in) INDEP_DBG_ARGS((in))
680
681 #define METH_DPFX_FMT "%s " INDEP_DBG_FMT " "
682 #define METH_DPFX_ARGS(indep, meth) ((meth).pname), INDEP_DBG_ARGS((indep))
683
684 static void indep_indep_done(Indep *indep);
685
686 static Method *methods[]= {
687   [mfk_none] = (Method*)&nomove_method,
688   [mfk_point] = (Method*)&points_method,
689   [mfk_relay] = (Method*)&waggle,
690   0
691 };
692
693 /*---------- entrypoints from methods ----------*/
694
695 static void method_update_feature(Method *m, MovPosChange *indep,
696                                   const Motion *mo) {
697   ouprintf("movpos %s feat %s %d %s\n", indep->move->i->pname,
698            mo->i->pname, mo->posn, m->pname);
699   if (SOMEP(indep->actual))
700     indep->actual=
701       movposcomb_update_feature(indep->actual, mo->i, mo->posn);
702   ouposn_moving(indep);
703 }
704
705 static void method_change_done(Method *m, Change *chg) {
706   Indep *indep= chg->indep;
707   Change **search;
708
709   DPRINTF(movpos,meth, METH_DPFX_FMT "method_change_done...\n",
710           METH_DPFX_ARGS(indep,*m));
711
712   for (search=indep->changes; *search; search++)
713     if ((*search) == chg) goto found;
714   assert(!"change in indep");
715
716  found:
717   indep->n_changes--;
718   if (indep->n_changes) {
719     *search= indep->changes[indep->n_changes];
720     return;
721   }
722
723   if (!indep->refcount)
724     indep_indep_done(indep);
725 }
726
727 static void indep_indep_done(Indep *indep) {
728   /* all done */
729   Segment *move= indep->move;
730   move->moving= 0;
731   move->motion= 0;
732   move->movposcomb= indep->target;
733   ouprintf("movpos %s position %s stable\n",
734            move->i->pname, movpos_pname(move, move->movposcomb));
735   free(indep);
736 }
737
738 /*---------- internal core machinery ----------*/
739
740 static Method *feature_method(const MovFeatInfo *feati) {
741   assert(feati->kind >= 0);
742   assert(feati->kind < ARRAY_SIZE(methods));
743   Method *meth= methods[feati->kind];
744   assert(meth);
745   return meth;
746 }
747
748 static int change_needed(const MovFeatInfo *feati,
749                          MovPosComb startpoint, MovPosComb target) {
750   int r;
751   r= !SOMEP(startpoint) ||
752     (target / feati->weight) % feati->posns -
753     (startpoint / feati->weight) % feati->posns;
754   if (DEBUGP(movpos,eval))
755     DPRINTFA(" { %s:%s(%d*%d) %d..%d => %d }",
756              feature_method(feati)->pname, feati->pname,
757              feati->posns, feati->weight,
758              startpoint, target, r);
759   return r;
760 }
761
762 static void indep_dispose(MovPosChange *indep) {
763   if (!indep) return;
764
765   DPRINTF(movpos,intern, INDEP_DPFX_FMT "dispose...\n",
766           INDEP_DPFX_ARGS(indep));
767
768   int changei;
769   for (changei=0; changei<indep->n_changes; changei++) {
770       Change *chg= indep->changes[changei];
771       if (!chg) continue;
772
773       Method *meth= chg->meth;
774       DPRINTF(movpos,meth, METH_DPFX_FMT "dispose...\n",
775               METH_DPFX_ARGS(indep,*meth));
776       meth->dispose(meth, chg);
777   }
778   free(indep);
779 }
780
781 #define EVAL_MAX_METHODS 2
782 #define EVAL_MAX_MOTIONS 2
783
784 static ErrorCode indep_prepare(Segment *move, MovPosComb target,
785                                MovPosComb startpoint,
786                                int ms, int confirming,
787                                MovPosChange **indep_r /* 0 ok */,
788                                int *cost_r /* 0 ok */) {
789   static int n_meths;
790   static Method *meths[EVAL_MAX_METHODS];
791   static int n_motions[EVAL_MAX_METHODS];
792   static Motion motions[EVAL_MAX_METHODS][EVAL_MAX_MOTIONS];
793
794   const SegmentInfo *movei= move->i;
795   int feat, DP;
796
797   MovPosChange *indep=0;
798
799   DPRINTF1(movpos,eval, "movpos prepare %s/%s <-%s", move->i->pname,
800            movpos_pname(move,target), movpos_pname(move,startpoint));
801
802   if (!SOMEP(startpoint)) {
803     startpoint= movpos_poscomb_actual(move);
804     DPRINTF2(" actual <-%s", movpos_pname(move,startpoint));
805   }
806
807   n_meths= 0;
808
809   for (feat=0; feat<movei->n_movfeats; feat++) {
810     const MovFeatInfo *feati= &movei->movfeats[feat];
811     if (!change_needed(feati,startpoint,target)) continue;
812     MovPosComb posn= target / feati->weight % feati->posns;
813     Method *meth= feature_method(feati);
814
815     int methi;
816     for (methi=0; methi<n_meths; methi++)
817       if (meths[methi] == meth) goto found_method;
818     /* need a new method */
819     methi= n_meths++;
820     if (methi >= EVAL_MAX_METHODS) {
821       DPRINTF2(" MovFeatTooManyMethods methi=%d\n",methi);
822       return EC_MovFeatTooManyMethods;
823     }
824     meths[methi]= meth;
825     n_motions[methi]= 0;
826     DPRINTF2(" meths[%d]=%s", methi,meth->pname);
827
828   found_method:;
829     int motioni= n_motions[methi]++;
830     if (motioni >= EVAL_MAX_MOTIONS) {
831       DPRINTF2(" MovFeatTooManyMotions motioni=%d\n",motioni);
832       return EC_MovFeatTooManyMotions;
833     }
834     DPRINTF2(" motion[%d][%d]=%s%d", methi, motioni, feati->pname,posn);
835     motions[methi][motioni].i= feati;
836     motions[methi][motioni].posn= posn;
837   }
838
839   if (indep_r) {
840     DPRINTF2(" alloc");
841     indep= mmalloc(sizeof(*indep) + sizeof(Change*) * n_meths);
842     indep->move= move;
843     indep->actual= startpoint;
844     indep->target= target;
845     indep->n_changes= n_meths;
846     indep->refcount= 0;
847     memset(indep->changes, 0, sizeof(Change*) * n_meths);
848   }
849   DPRINTF2("\n");
850
851   int totalcost= 0;
852   int changei;
853   ErrorCode ec;
854
855   for (changei=0; changei<n_meths; changei++) {
856     Method *meth= meths[changei];
857     int thiscost= 0;
858     meth->needcheck= 1;
859     meth->needexec= 1;
860
861     if (indep_r)
862       DPRINTF(movpos,meth, METH_DPFX_FMT "prepare n_motions=%d...\n",
863               METH_DPFX_ARGS(indep,*meth), n_motions[changei]);
864     else
865       DPRINTF(movpos,meth, "%s prepare (costing) n_motions=%d...\n",
866               meth->pname, n_motions[changei]);
867
868     ec= meth->prepare(meth,move,
869                       n_motions[changei],motions[changei],
870                       ms, confirming,
871                       indep ? &indep->changes[changei] : 0,
872                       &thiscost);
873     if (ec) goto x;
874     if (indep) {
875       Change *chg= indep->changes[changei];
876       chg->meth= meth;
877       chg->indep= indep;
878       chg->installed= 0;
879     }
880     totalcost += thiscost;
881   }
882
883   if (indep_r) *indep_r= indep;
884   if (cost_r) *cost_r= totalcost;
885
886   if (indep_r)
887     DPRINTF(movpos,eval, INDEP_DPFX_FMT "prepare cost=%d ok\n",
888             INDEP_DPFX_ARGS(indep), totalcost);
889   else
890     DPRINTF(movpos,eval, "movpos prepare %s/%s cost=%d ok\n",
891             move->i->pname, movpos_pname(move,target), totalcost);
892   return 0;
893
894  x:
895   indep_dispose(indep);
896   DPRINTF(movpos,entry, "movpos prepare %s/%s err=%s\n", move->i->pname,
897           movpos_pname(move,target), ec2str(ec));
898   return ec;
899 }
900
901 static void indep_remove(MovPosChange *remv) {
902   if (!remv) return;
903   
904   DPRINTF(movpos,intern, INDEP_DPFX_FMT "remove...\n",
905           INDEP_DPFX_ARGS(remv));
906
907   int i;
908   for (i=0; i<remv->n_changes; i++) {
909     Change *chg= remv->changes[i];
910     if (!chg->installed) continue;
911     Method *meth= chg->meth;
912     meth->needexec= 1;
913
914     DPRINTF(movpos,meth, METH_DPFX_FMT "remove...\n",
915             METH_DPFX_ARGS(remv,*meth));
916     meth->remove(meth, chg);
917     chg->installed= 0;
918   }
919 }
920
921 static ErrorCode
922 indep_install(MovPosChange *inst, int checknow) {
923   /* if this fails, inst may be left partially installed */
924   if (!inst) return 0;
925   
926   DPRINTF(movpos,intern, INDEP_DPFX_FMT "install checknow=%d...\n",
927           INDEP_DPFX_ARGS(inst), checknow);
928
929   int i;
930   ErrorCode ec;
931   for (i=0; i<inst->n_changes; i++) {
932     Change *chg= inst->changes[i];
933     assert(!chg->installed);
934     Method *meth= chg->meth;
935
936     meth->needexec= 1;
937     ec= meth->install(meth, chg);
938     DPRINTF(movpos,meth, METH_DPFX_FMT "install=%s\n",
939             METH_DPFX_ARGS(inst,*meth), ec2str(ec));
940     if (ec) goto x;
941     chg->installed= 1;
942     meth->needcheck= 1;
943
944     if (checknow) {
945       ec= meth->check(meth);
946       DPRINTF(movpos,meth, METH_DPFX_FMT "check=%s\n",
947               METH_DPFX_ARGS(inst,*meth), ec2str(ec));
948       if (ec) goto x;
949       meth->needcheck= 0;
950     }
951   }
952   return 0;
953
954  x:
955   return ec;
956 }
957
958 static void indep_check_execute(void) {
959   DPRINTF(movpos,intern, "movpos indep_check_execute\n");
960
961   Method **methwalk, *meth;
962   for (methwalk= methods; (meth= *methwalk); methwalk++) {
963     if (meth->needcheck) {
964       ErrorCode ec= meth->check(meth);
965       DPRINTF(movpos,meth, "%s check=%s\n", meth->pname, ec2str(ec));
966       assert(!ec);
967       meth->needcheck= 0;
968     }
969     if (meth->needexec) {
970       meth->needexec= 0;
971       DPRINTF(movpos,meth, "%s execute...\n", meth->pname);
972       meth->execute(meth);
973     }
974   }
975 }
976
977 /*---------- entrypoints from rest of program ----------*/
978
979 ErrorCode
980 movpos_reserve(Segment *move, int maxdelay_ms, MovPosChange **res_r,
981                MovPosComb target, MovPosComb startpoint /*as for findcomb*/) {
982   ErrorCode ec;
983   MovPosChange *indep= 0;
984
985   DPRINTF(movpos,entry, "movpos reserve %s/%s maxdelay=%dms startpoint=%s\n",
986           move->i->pname, movpos_pname(move,target),
987           maxdelay_ms, movpos_pname(move,startpoint));
988
989   ec= indep_prepare(move,target, startpoint,
990                     maxdelay_ms,0,
991                     &indep, 0);
992   if (ec) return ec;
993
994   ec= indep_install(indep, 1);
995   if (ec) goto x;
996
997   indep_check_execute();
998
999   DPRINTF(movpos,entry, "movpos reserve %s/%s ok\n",
1000           move->i->pname, movpos_pname(move,target));
1001   *res_r= indep;
1002   return 0;
1003
1004  x:
1005   indep_remove(indep);
1006   indep_dispose(indep);
1007   indep_check_execute();
1008
1009   DPRINTF(movpos,entry, "movpos reserve %s/%s err=%s\n",
1010           move->i->pname, movpos_pname(move,target), ec2str(ec));
1011   return ec;
1012 }
1013
1014 ErrorCode movpos_findcomb_bysegs(Segment *back, Segment *move, Segment *fwd,
1015                                  MovPosComb startpoint, MovPosComb *chosen_r) {
1016   const SegmentInfo *movei= move->i;
1017   MovPosComb tcomb, bestcomb=-1;
1018   int tcost, bestcost=INT_MAX;
1019   const SegPosCombInfo *pci;
1020
1021   DPRINTF(movpos,eval, "movpos_findcomb_bysegs %s-%s-%s <-%s\n",
1022           back ? back->i->pname : "*", move->i->pname,
1023           fwd  ? fwd ->i->pname : "*", movpos_pname(move, startpoint));
1024
1025   for (tcomb=0, pci=movei->poscombs;
1026        tcomb<movei->n_poscombs;
1027        tcomb++, pci++) {
1028     /* these next assignments may generate segments[-1] but we don't
1029      * care because that won't compare equal to back or fwd */
1030     Segment *tback= &segments[pci->link[1].next];
1031     Segment *tfwd=  &segments[pci->link[0].next];
1032
1033     DPRINTF(movpos,intern, "movpos_findcomb_bysegs  ... %s : %s-%s-%s\n",
1034             movpos_pname(move, tcomb),
1035             SOMEP(pci->link[1].next) ? tback->i->pname : "#",
1036             move->i->pname,
1037             SOMEP(pci->link[0].next) ? tfwd->i->pname : "#");       
1038
1039     if (back && !(back==tback || back==tfwd)) continue;
1040     if (fwd  && !(fwd ==tback || fwd ==tfwd)) continue;
1041
1042     /* we have to search for the one which is least effort */
1043     ErrorCode ec= indep_prepare(move,tcomb,startpoint, -1,0, 0,&tcost);
1044     if (ec) return ec;
1045
1046     if (tcost >= bestcost) /* prefer low-numbered movposcombs */
1047       continue;
1048
1049     bestcomb= tcomb;
1050     bestcost= tcost;
1051   }
1052   DPRINTF(movpos,entry, "movpos_findcomb_bysegs %s..%s..%s <-%s => %s/%s\n",
1053           back ? back->i->pname : "-", move->i->pname,
1054           fwd  ? fwd ->i->pname : "-", movpos_pname(move, startpoint),
1055           move->i->pname, movpos_pname(move,bestcomb));
1056
1057   if (chosen_r) *chosen_r= bestcomb;
1058   return
1059     bestcost==INT_MAX ? EC_MovFeatRouteNotFound :
1060     0;
1061 }
1062
1063 ErrorCode movpos_change(Segment *move, MovPosComb target,
1064                         int maxdelay_ms, MovPosChange *resv) {
1065   int DP;
1066   MovPosComb actual;
1067   ErrorCode ec;
1068
1069   if (!move->moving) {
1070     actual= move->movposcomb;
1071     assert(!move->motion);
1072   } else {
1073     actual= move->motion->actual;
1074   }
1075
1076   DPRINTF1(movpos,entry, "movpos change %s/%s maxdelay=%dms actual=%s",
1077            move->i->pname, movpos_pname(move, target),
1078            maxdelay_ms, movpos_pname(move, actual));
1079   if (resv) DPRINTF2(" resv=%s/%s",
1080                      resv->move->i->pname,
1081                      movpos_pname(resv->move, resv->target));
1082   if (move->motion) DPRINTF2(" oldmotion=/%s",
1083                              movpos_pname(move, move->motion->target));
1084   DPRINTF2("\n");
1085
1086   MovPosChange *inst= 0;
1087
1088   ec= indep_prepare(move,target, actual,
1089                     maxdelay_ms,1,
1090                     &inst, 0);
1091   if (ec) goto x;
1092
1093   indep_remove(resv);
1094   indep_remove(move->motion);;
1095
1096   ec= indep_install(inst, 1);
1097   if (ec) goto x;
1098
1099   indep_dispose(resv);
1100   indep_dispose(move->motion);
1101
1102   move->motion= inst;
1103   move->moving= 1;
1104
1105   inst->refcount++; /* prevents method_change_done from destroying it */
1106
1107   ouposn_moving(inst);
1108   indep_check_execute();
1109
1110   inst->refcount--;
1111   if (!inst->n_changes)
1112     /* oh! */
1113     indep_indep_done(inst);
1114
1115   DPRINTF(movpos,entry, "movpos change %s/%s ok\n",
1116           move->i->pname, movpos_pname(move, target));
1117   return 0;
1118
1119  x:
1120   indep_remove(inst);
1121   indep_dispose(inst);
1122   ec= indep_install(move->motion, 0);  assert(!ec);
1123   ec= indep_install(resv, 0);          assert(!ec);
1124   indep_check_execute();
1125
1126   DPRINTF(movpos,entry, "movpos change %s/%s err=%s\n",
1127           move->i->pname, movpos_pname(move, target), ec2str(ec));
1128   return ec;
1129 }
1130
1131 void movpos_unreserve(MovPosChange *resv) {
1132   if (!resv) return;
1133   DPRINTF(movpos,entry, "movpos unreserve %s/%s...\n",
1134           resv->move->i->pname, movpos_pname(resv->move, resv->target));
1135   indep_remove(resv);
1136   indep_dispose(resv);
1137   indep_check_execute();
1138 }
1139
1140 MovPosComb movpos_change_intent(MovPosChange *indep) {
1141   return indep->target;
1142 }
1143
1144 void motions_all_abandon(void) {
1145   Method **meth;
1146   SEG_IV;
1147   DPRINTF(movpos,entry, "movpos motions_all_abandon...\n");
1148   FOR_SEG {
1149     if (!seg->moving) continue;
1150
1151     MovPosChange *abandon= seg->motion;
1152     indep_remove(abandon);
1153     seg->movposcomb= abandon->actual;
1154     seg->moving= 0;
1155     seg->motion= 0;
1156     indep_dispose(abandon);
1157   }
1158   for (meth=methods; *meth; meth++)
1159     (*meth)->all_abandon(*meth);
1160 }