import { SpinWait } from "@esfx/threading-spinwait" ; import { Disposable } from "@esfx/disposable" ; const MAX_INT32 = ( 2 ** 31 ) - 1 ; const COUNTDOWN_ID = 1 << 20 ; const NONSIGNALED = COUNTDOWN_ID | 0 ; const SIGNALED = COUNTDOWN_ID | 1 ; const COUNTDOWN_EXCLUDES = ~ ( NONSIGNALED | SIGNALED ) ; const ATOM_INDEX = 0 ; const INITIALCOUNT_INDEX = 1 ; const REMAININGCOUNT_INDEX = 2 ; const kArray = Symbol ( "kArray" ) ; export class CountdownEvent implements Disposable { static readonly SIZE = 12 ; private [ kArray ] : Int32Array | undefined ; constructor ( initialCount ) ; constructor ( buffer , byteOffset ? ) ; constructor ( bufferOrInitialCount , byteOffset = 0 ) { let array ; let initialCount ; if ( bufferOrInitialCount instanceof SharedArrayBuffer ) { if ( byteOffset < 0 || byteOffset > bufferOrInitialCount . byteLength - 12 ) throw new RangeError ( "Out of range: byteOffset" ) ; if ( byteOffset % 4 ) throw new RangeError ( "Not aligned: byteOffset" ) ; array = new Int32Array ( bufferOrInitialCount , byteOffset , 3 ) ; initialCount = 0 ; } else { if ( bufferOrInitialCount < 0 ) throw new TypeError ( "Out of range: initialCount" ) ; array = new Int32Array ( new SharedArrayBuffer ( 4 ) ) ; initialCount = bufferOrInitialCount ; } if ( Atomics . compareExchange ( array , ATOM_INDEX , 0 , initialCount === 0 ? SIGNALED : NONSIGNALED ) === 0 ) { if ( Atomics . compareExchange ( array , INITIALCOUNT_INDEX , 0 , initialCount ) !== 0 || Atomics . compareExchange ( array , REMAININGCOUNT_INDEX , 0 , initialCount ) !== 0 ) { throw new TypeError ( "Invalid handle." ) ; } } const data = Atomics . load ( array , ATOM_INDEX ) ; if ( ! ( data & COUNTDOWN_ID ) || data & COUNTDOWN_EXCLUDES ) throw new TypeError ( "Invalid handle." ) ; this [ kArray ] = array ; } /**
     * Gets the `SharedArrayBuffer` for this object.
     */ get buffer ( ) { const array = this [ kArray ] ; if ( ! array ) throw new ReferenceError ( "Object is disposed." ) ; return array . buffer as SharedArrayBuffer ; } /**
     * Gets the byte offset of this object in its buffer.
     */ get byteOffset ( ) { const array = this [ kArray ] ; if ( ! array ) throw new ReferenceError ( "Object is disposed." ) ; return array . byteOffset ; } /**
     * Gets the number of bytes occupied by this object in its buffer.
     */ get byteLength ( ) { const array = this [ kArray ] ; if ( ! array ) throw new ReferenceError ( "Object is disposed." ) ; return 12 ; } /**
     * Gets the number of participants initially required to signal the event.
     */ get initialCount ( ) { const array = this [ kArray ] ; if ( ! array ) throw new ReferenceError ( "Object is disposed." ) ; return Atomics . load ( array , INITIALCOUNT_INDEX ) ; } /**
     * Gets the number of remaining participants required to signal the event.
     */ get remainingCount ( ) { const array = this [ kArray ] ; if ( ! array ) throw new ReferenceError ( "Object is disposed." ) ; return Math . max ( 0 , Atomics . load ( array , REMAININGCOUNT_INDEX ) ) ; } /**
     * Gets a value indicating whether the event is signaled (all participants are accounted for).
     */ get isSet ( ) { return this . remainingCount === 0 ; } /**
     * Adds one or more required participants to the event.
     */ add ( count ? ) { const array = this [ kArray ] ; if ( ! array ) throw new ReferenceError ( "Object is disposed." ) ; if ( ! this . tryAdd ( count ) ) { throw new Error ( "The event is already signaled and cannot be incremented." ) ; } } returns tryAdd ( count ? ) { const array = this [ kArray ] ; if ( ! array ) throw new ReferenceError ( "Object is disposed." ) ; if ( count === undefined ) count = 1 ; if ( count <= 0 ) throw new RangeError ( "Out of range: count" ) ; const spinWait = new SpinWait ( ) ; while ( true ) { const remainingCount = Atomics . load ( array , REMAININGCOUNT_INDEX ) ; if ( remainingCount <= 0 ) return false ; if ( remainingCount > MAX_INT32 - count ) throw new Error ( "Operation would cause count to overflow." ) ; if ( Atomics . compareExchange ( array , REMAININGCOUNT_INDEX , remainingCount , remainingCount + count ) === remainingCount ) { break ; } spinWait . spinOnce ( ) ; } return true ; } param count reset ( count ? ) { const array = this [ kArray ] ; if ( ! array ) throw new ReferenceError ( "Object is disposed." ) ; if ( count === undefined ) count = Atomics . load ( array , INITIALCOUNT_INDEX ) ; if ( count < 0 ) throw new RangeError ( "Out of range: count" ) ; Atomics . store ( array , REMAININGCOUNT_INDEX , count ) ; Atomics . store ( array , INITIALCOUNT_INDEX , count ) ; if ( count === 0 ) { if ( Atomics . compareExchange ( array , ATOM_INDEX , NONSIGNALED , SIGNALED ) === NONSIGNALED ) { Atomics . notify ( array , ATOM_INDEX , Infinity ) ; } } else { Atomics . compareExchange ( array , ATOM_INDEX , SIGNALED , NONSIGNALED ) ; } } param count returns signal ( count ? ) { const array = this [ kArray ] ; if ( ! array ) throw new ReferenceError ( "Object is disposed." ) ; if ( count === undefined ) count = 1 ; if ( count <= 0 ) throw new RangeError ( "Out of range: count" ) ; const spinWait = new SpinWait ( ) ; let remainingCount ; while ( true ) { remainingCount = Atomics . load ( array , REMAININGCOUNT_INDEX ) ; if ( remainingCount < count ) throw new Error ( "Invalid attempt to decrement the event's count below zero." ) ; if ( Atomics . compareExchange ( array , REMAININGCOUNT_INDEX , remainingCount , remainingCount - count ) === remainingCount ) { break ; } spinWait . spinOnce ( ) ; } if ( remainingCount === count ) { if ( Atomics . compareExchange ( array , ATOM_INDEX , NONSIGNALED , SIGNALED ) === NONSIGNALED ) { Atomics . notify ( array , ATOM_INDEX , Infinity ) ; } return true ; } return false ; } param ms returns wait ( ms = Infinity ) { if ( typeof ms !== "number" ) throw new TypeError ( "Number expected: ms" ) ; ms = isNaN ( ms ) ? Infinity : Math . max ( ms , 0 ) ; const array = this [ kArray ] ; if ( ! array ) throw new ReferenceError ( "Object is disposed." ) ; return this . isSet || Atomics . wait ( array , ATOM_INDEX , NONSIGNALED , ms ) !== "timed-out" ; } /**
     * Releases all resources for this object.
     */ close ( ) { this [ kArray ] = undefined ; } /**
     * Releases all resources for this object.
     */ [ Disposable . dispose ] ( ) { this . close ( ) ; } }