All files / src/worker foreground.ts

96.42% Statements 27/28
100% Branches 10/10
60% Functions 3/5
96.42% Lines 27/28

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59                                1x     1x     1x   1x 1x 1x   1x 4x 4x   4x     1x 4x 4x 4x 4x 3x 3x 3x 4x 4x             1x 4x 4x 23x 23x 4x 1x  
/**
 * Which of the reader's work somebody is waiting for, and which can wait (§12).
 *
 * One worker reads for everything: the views, and two walks nobody is looking
 * at — the census that counts a `.hic`'s contacts (§6.5) and the circularity
 * check's reads (§8.15). Their reads interleave with a view's, and over a
 * network each is a round trip that holds the thread, so a chromosome clicked
 * while they ran waited for theirs as well as its own: on a 1 GB `.hic` from
 * S3, 10 s for a picture that took 4 alone, under a veil that named the census.
 *
 * So a view's request counts itself in while it runs, and the background waits
 * until none has run for {@link SETTLE_MS} — long enough to cover the round trip
 * to the main thread between a view's `range` and its `matrix`, which would
 * otherwise be a gap for a walk to step into.
 */
 
import { yieldToEventLoop } from '../async'
 
/** Quiet a background step waits for after the last view request ends. */
export const SETTLE_MS = 250
 
/** How often a waiting step looks again. */
const POLL_MS = 50
 
export class Foreground {
  private reading = 0
  private quietSince = Number.NEGATIVE_INFINITY
 
  constructor(
    private readonly now: () => number = () => performance.now(),
    private readonly sleep: (ms: number) => Promise<void> = (ms) =>
      new Promise((resolve) => setTimeout(resolve, ms)),
  ) {}
 
  /** A request somebody is waiting for has started; call what it returns when it ends. */
  begin(): () => void {
    this.reading += 1
    let ended = false
    return () => {
      if (ended) return
      ended = true
      this.reading -= 1
      this.quietSince = this.now()
    }
  }
 
  /**
   * Resolve once no view has been reading for {@link SETTLE_MS}, or as soon as
   * `stop` says the wait is over — a walk somebody has come to depend on, or a
   * request called off. Always a real yield first, as the walks had before.
   */
  async quiet(stop?: () => boolean): Promise<void> {
    await yieldToEventLoop()
    while (stop?.() !== true && (this.reading > 0 || this.now() - this.quietSince < SETTLE_MS)) {
      await this.sleep(POLL_MS)
    }
  }
}