///|
/// The facade's view of the host event loop's notifications.
///
/// `ProtonEventLoop::poll` runs on the main thread and signals whenever native
/// work arrives, so waiting here is a plain scheduler wait -- there is no
/// descriptor left to drain. Waiting stays race-free through the revision:
/// capture it before checking the condition that may make waiting unnecessary,
/// and a notification that lands in between is still seen.
priv struct RuntimeWakeup {
  signal : @core.RuntimeWakeSignal
}

///|
/// Attaches to the running host loop. `App::run` has already refused to start
/// without one, so there is nothing left to fail here.
fn RuntimeWakeup::RuntimeWakeup() -> RuntimeWakeup {
  RuntimeWakeup::{ signal: host_event_loop_signal(), }
}

///|
fn RuntimeWakeup::revision(self : RuntimeWakeup) -> Int64 {
  self.signal.revision()
}

///|
/// Waits for native work or a scheduler notification newer than `revision`.
async fn RuntimeWakeup::wait_after(
  self : RuntimeWakeup,
  revision : Int64,
) -> Unit raise AppRunError {
  self.signal.wait_for_change(revision) catch {
    error => raise normalize_async_run_error(error)
  }
}

///|
/// Drives one wake-based state machine until its completion predicate holds.
///
/// Capturing the revision before checking state closes the notification race:
/// work arriving between the check and suspension changes the revision, so
/// `wait_after` returns instead of sleeping indefinitely.
async fn drive_wakeup_until(
  wakeup : RuntimeWakeup,
  complete : () -> Bool raise AppRunError,
  advance : () -> Bool raise AppRunError,
) -> Unit raise AppRunError {
  while true {
    let revision = wakeup.revision()
    if complete() {
      return
    }
    let did_work = advance()
    if complete() {
      return
    }
    if did_work {
      @async.pause() catch {
        error => raise normalize_async_run_error(error)
      }
    } else {
      wakeup.wait_after(revision)
    }
  }
}