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