///|
/// Cooperative cancellation signal passed to language providers.
///
/// Tokens are stable values: repeated reads from a source return the same
/// token before and after cancellation. Cancellation ownership belongs to
/// `CancellationTokenSource`; `CancellationToken::new` and `cancel` remain as
/// compatibility entry points for existing direct-token callers.
struct CancellationToken {
state : CancellationTokenState
}
///|
priv enum CancellationTokenState {
NeverCancelled
AlreadyCancelled((() -> Unit) -> @base_common.Disposable)
CompatibilityMutable(MutableCancellationToken)
SourceMutable(MutableCancellationToken)
}
///|
/// Listener state is allocated lazily, just as VS Code's `MutableToken` only
/// creates its emitter when a listener is first registered.
priv struct MutableCancellationToken {
mut cancelled : Bool
mut emitter : @base_common.Emitter[Unit]?
schedule : (() -> Unit) -> @base_common.Disposable
}
///|
fn schedule_cancellation_inline(
callback : () -> Unit,
) -> @base_common.Disposable {
callback()
@base_common.Disposable::from(() => ())
}
///|
let cancellation_none : CancellationToken = { state: NeverCancelled, }
///|
let cancellation_cancelled : CancellationToken = {
state: AlreadyCancelled(schedule_cancellation_inline),
}
///|
fn MutableCancellationToken::MutableCancellationToken(
schedule : (() -> Unit) -> @base_common.Disposable,
) -> MutableCancellationToken {
{ cancelled: false, emitter: None, schedule, }
}
///|
/// Shared token that never requests cancellation.
pub fn CancellationToken::none() -> CancellationToken {
cancellation_none
}
///|
/// Whether cancellation has been requested.
pub fn CancellationToken::is_cancellation_requested(
self : CancellationToken,
) -> Bool {
match self.state {
NeverCancelled => false
AlreadyCancelled(_) => true
CompatibilityMutable(token) | SourceMutable(token) => token.cancelled
}
}
///|
/// Registers a once-only cancellation listener in registration order.
///
/// A listener registered after cancellation is scheduled through the source's
/// scheduler and the returned disposable can suppress that pending delivery.
/// MoonBit closures capture the source `thisArgs` context directly. When a
/// disposable bucket is supplied, the returned subscription is appended to it.
fn CancellationToken::on_cancellation_requested_impl(
self : CancellationToken,
listener : () -> Unit,
disposables? : Array[@base_common.Disposable],
) -> @base_common.Disposable {
let subscription = match self.state {
NeverCancelled => @base_common.Disposable::from(() => ())
AlreadyCancelled(schedule) => schedule(listener)
CompatibilityMutable(token) | SourceMutable(token) =>
token.on_cancellation_requested(listener)
}
if disposables is Some(bucket) {
bucket.push(subscription)
}
subscription
}
///|
fn MutableCancellationToken::cancel(self : MutableCancellationToken) -> Unit {
if self.cancelled {
return
}
// The flag changes before notification so every listener observes the
// one-way state transition.
self.cancelled = true
if self.emitter is Some(emitter) {
emitter.fire(())
emitter.dispose()
self.emitter = None
}
}
///|
fn MutableCancellationToken::on_cancellation_requested(
self : MutableCancellationToken,
listener : () -> Unit,
) -> @base_common.Disposable {
if self.cancelled {
return (self.schedule)(listener)
}
let emitter = match self.emitter {
Some(emitter) => emitter
None => {
let emitter : @base_common.Emitter[Unit] = Emitter()
self.emitter = Some(emitter)
emitter
}
}
emitter.event(_ => listener())
}
///|
/// Registers a once-only cancellation listener in registration order.
pub fn CancellationToken::on_cancellation_requested(
self : CancellationToken,
listener : () -> Unit,
disposables? : Array[@base_common.Disposable],
) -> @base_common.Disposable {
self.on_cancellation_requested_impl(listener, disposables?)
}
///|
/// Retires listener state without changing the cancellation flag.
fn MutableCancellationToken::dispose(self : MutableCancellationToken) -> Unit {
if self.emitter is Some(emitter) {
emitter.dispose()
self.emitter = None
}
}
///|
/// Owner for one lazily created cancellation token and its optional parent
/// subscription. `dispose(cancel=false)` releases listeners without requesting
/// cancellation; `dispose(cancel=true)` cancels first.
struct CancellationTokenSource {
mut token_value : CancellationToken?
mut parent_listener : @base_common.Disposable?
schedule : (() -> Unit) -> @base_common.Disposable
}
///|
/// Creates a cancellation source. A parent cancellation propagates once to the
/// child. `schedule` adapts VS Code's zero-delay shortcut event to the host;
/// the host-neutral default delivers inline, while JS hosts or tests may inject
/// a disposable next-turn scheduler.
fn CancellationTokenSource::new_impl(
parent? : CancellationToken,
schedule~ : (() -> Unit) -> @base_common.Disposable = schedule_cancellation_inline,
) -> CancellationTokenSource {
let source : CancellationTokenSource = {
token_value: None,
parent_listener: None,
schedule,
}
if parent is Some(parent) {
source.parent_listener = Some(
parent.on_cancellation_requested_impl(() => source.cancel()),
)
}
source
}
///|
/// Creates a cancellation source with optional parent propagation and host
/// scheduling.
pub fn CancellationTokenSource::CancellationTokenSource(
parent? : CancellationToken,
schedule~ : (() -> Unit) -> @base_common.Disposable = schedule_cancellation_inline,
) -> CancellationTokenSource {
CancellationTokenSource::new_impl(parent?, schedule~)
}
///|
/// Returns the source's stable token, allocating mutable state only on the
/// first read.
fn CancellationTokenSource::token_impl(
self : CancellationTokenSource,
) -> CancellationToken {
match self.token_value {
Some(token) => token
None => {
let token : CancellationToken = {
state: SourceMutable(MutableCancellationToken(self.schedule)),
}
self.token_value = Some(token)
token
}
}
}
///|
/// Returns the source's stable cancellation token.
pub fn CancellationTokenSource::token(
self : CancellationTokenSource,
) -> CancellationToken {
self.token_impl()
}
///|
/// Releases the parent subscription and mutable listener state. Disposal is
/// idempotent. When no token has been read, non-cancelling disposal installs
/// the shared never-cancelled token so a later `cancel` remains a no-op.
fn CancellationTokenSource::dispose_impl(
self : CancellationTokenSource,
cancel~ : Bool = false,
) -> Unit {
if cancel {
self.cancel()
}
if self.parent_listener is Some(listener) {
listener.dispose()
}
match self.token_value {
None => self.token_value = Some(cancellation_none)
Some({ state: SourceMutable(token), }) => token.dispose()
Some(
{ state: NeverCancelled | AlreadyCancelled(_) | CompatibilityMutable(_), }
) => ()
}
}
///|
/// Releases source listeners, optionally requesting cancellation first.
pub fn CancellationTokenSource::dispose(
self : CancellationTokenSource,
cancel~ : Bool = false,
) -> Unit {
self.dispose_impl(cancel~)
}