#ifndef NSYNC_WAITER_H_
#define NSYNC_WAITER_H_
#include "third_party/nsync/time.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_

/* nsync_wait_n() allows the client to wait on multiple objects
   (condition variables, nsync_notes, nsync_counters, etc.) until at
   least one of them becomes ready, or a deadline expires.

   It can be thought of as rather like Unix's select() or poll(), except
   the the objects being waited for are synchronization data structures,
   rather than file descriptors.

   The client can construct new objects that can be waited for by
   implementing three routines.

   Examples:

   To wait on two nsync_notes n0, n1, and a nsync_counter c0, with a
   deadline of abs_deadline:

       // Form an array of struct nsync_waitable_s, identifying the
       // objects and the corresponding descriptors. (Static
       // initialization syntax is used for brevity.)
       static struct nsync_waitable_s w[] = {
         { &n0, &nsync_note_waitable_funcs },
         { &n1, &nsync_note_waitable_funcs },
         { &c0, &nsync_counter_waitable_funcs }
       };
       static struct nsync_waitable_s *pw[] = { &w[0], &w[1], &w[2] };
       int n = sizeof (w) / sizeof (w[0]);

       // Wait. The mu, lock, and unlock arguments are NULL because no
       // condition variables are invovled.
       int i = nsync_wait_n (NULL, NULL, NULL, abs_deadline, n, pw);
       if (i == n) {
         // timeout
       } else {
         // w[i].v became ready.
       }

    To wait on multiple condition variables, the mu/lock/unlock
    parameters are used. Imagine cv0 and cv1 are signalled when
    predicates pred0() (under lock mu0) and pred1() (under lock mu1)
    become true respectively. Assume that mu0 is acquired before mu1.

       static void lock2 (void *v) {  // lock two mutexes in order
         nsync_mu **mu = (nsync_mu **) v;
         nsync_mu_lock (mu[0]);
         nsync_mu_lock (mu[1]);
       }
       static void unlock2 (void *v) { // unlock two mutexes.
         nsync_mu **mu = (nsync_mu **) v;
         nsync_mu_unlock (mu[1]);
         nsync_mu_unlock (mu[0]);
       }

       // Describe the condition variables and the locks.
       static struct nsync_waitable_s w[] = {
         { &cv0, &nsync_cv_waitable_funcs },
         { &cv1, &nsync_cv_waitable_funcs }
       };
       static struct nsync_waitable_s *pw[] = { &w[0], &w[1] };
       nsync_mu *lock_list[] = { &mu0, &mu1 };
       int n = sizeof (w) / sizeof (w[0]);

       lock2 (list_list);
       while (!pred0 () && !pred1 ()) {
         // Wait for one of the condition variables to be signalled,
         // with no timeout.
         nsync_wait_n (lock_list, &lock2, &unlock2,
                       nsync_time_no_deadline, n, pw);
       }
       if (pred0 ()) { ... }
       if (pred1 ()) { ... }
       unlock2 (list_list);

   */

/* forward declaration of struct that contains type dependent wait
   operations */
struct nsync_waitable_funcs_s;

/* Clients wait on objects by forming an array of struct
   nsync_waitable_s. Each each element points to one object and its
   type-dependent functions. */
struct nsync_waitable_s {
  /* pointer to object */
  void *v;
  /* pointer to type-dependent functions. Use
     &nsync_note_waitable_funcs for an nsync_note,
     &nsync_counternote_waitable_funcs for an nsync_counter,
     &nsync_cv_waitable_funcs for an nsync_cv. */
  const struct nsync_waitable_funcs_s *funcs;
};

/* Wait until at least one of *waitable[0,..,count-1] is has been
   notified, or abs_deadline is reached. Return the index of the
   notified element of waitable[], or count if no such element exists.
   If mu!=NULL, (*unlock)(mu) is called after the thread is queued on
   the various waiters, and (*lock)(mu) is called before return;
   mu/lock/unlock are used to acquire and release the relevant locks
   whan waiting on condition variables. */
int nsync_wait_n(void *mu, void (*lock)(void *), void (*unlock)(void *),
                 nsync_time abs_deadline, int count,
                 struct nsync_waitable_s *waitable[]);

/* A "struct nsync_waitable_s" implementation must implement these
   functions. Clients should ignore the internals. */
struct nsync_waiter_s;
struct nsync_waitable_funcs_s {

  /* Return the time when *v will be ready (max time if unknown), or 0
     if it is already ready. The parameter nw may be passed as NULL, in
     which case the result should indicate whether the thread would
     block if it were to wait on *v. All calls with the same *v must
     report the same result until the object becomes ready, from which
     point calls must report 0. */
  nsync_time (*ready_time)(void *v, struct nsync_waiter_s *nw);

  /* If *v is ready, return zero; otherwise enqueue *nw on *v and return
     non-zero. */
  int (*enqueue)(void *v, struct nsync_waiter_s *nw);

  /* If nw has been previously dequeued, return zero; otherwise dequeue
   *nw from *v and return non-zero.  */
  int (*dequeue)(void *v, struct nsync_waiter_s *nw);
};

/* The "struct nsync_waitable_s" for nsync_note, nsync_counter, and nsync_cv. */
extern const struct nsync_waitable_funcs_s nsync_note_waitable_funcs;
extern const struct nsync_waitable_funcs_s nsync_counter_waitable_funcs;
extern const struct nsync_waitable_funcs_s nsync_cv_waitable_funcs;

COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* NSYNC_WAITER_H_ */
