Skip to content

Releases: dXnSix/dXn.Reactive

Release list

v1.1.0

Choose a tag to compare

@dXnSix dXnSix released this 23 Oct 20:07

Features

  • (repository) rename the crate to dxn_reactive
    Migration: depend on dxn_reactive = { git = "https://github.com/dXnSix/dXn.Reactive", tag = "v1.1.0" } instead of rayx_reactive, and replace rayx_reactive:: paths with dxn_reactive::.

v1.0.0

Choose a tag to compare

@dXnSix dXnSix released this 18 Oct 23:03

The first tagged release. Before it, consumers built the untagged 0.1 sources; the breaking changes
below are relative to those sources, each with a Migration: note.

Delivery

  • Fixed: a subscriber panic no longer stalls its delivery queue. The panic still propagates to
    the thread draining the queue, but the items queued behind the panicking one, and later ones,
    are delivered to every live subscriber (including new ones) by the next publication. This
    applies to every signal, event bus and collection source.
  • Changed: subscribers of one queue are invoked in subscription order (previously unspecified),
    and a subscriber disposed on the delivering thread, including by another subscriber while the
    same item is in flight, is never invoked again.
  • Changed: Signal::set/replace/update and EventBus::emit deliver in commit order when
    several threads write concurrently.
  • Added: DeliveryQueue::push, DeliveryQueue::drain and DeliveryQueue::pending_len.
    enqueue keeps its contract (push followed by drain).

Errors and interop

  • Breaking: ReactiveError has a new variant External(Arc<dyn Error + Send + Sync>) carrying
    an error from another layer. Migration: add a wildcard or an External(_) arm to exhaustive
    matches on ReactiveError.
  • Added: impl From<CollectionError> for ReactiveError (Poisoned stays Poisoned, every
    other variant becomes External holding the original), so ? on collection subscriptions works
    inside when_activated callbacks. CollectionError::from(ReactiveError::External(..)) recovers a
    wrapped collection error; other external errors map to Poisoned.
  • Added: ValueStream::map_err and ValueStream::err_into, and ReactiveError::external.
  • Breaking (generic error parameters): ReactiveValueStreamExt<T, E = ReactiveError> and
    OptionValueStreamExt<T, StreamError = CollectionError> are implemented for streams of any error
    type, and Effect::watch accepts &ValueStream<T, E> and returns Result<Effect, E>.
    Migration: code that built a stream with ValueStream::new(..) and relied on a scalar
    operator to infer the error type now needs an annotation (ValueStream::<T, ReactiveError>::new)
    or ValueStream::from_reactive.
  • Breaking (generic error parameters): every collection operator that takes a stream (sort
    comparers and resort triggers, page/virtual requests including sort_and_*, batch_if/buffer_if
    pause streams, regroupers, group selectors, forced-transform triggers, tree predicates, switch,
    populate_from*, the to_*_change_set* and edit_diff* bridges, buffered change-set streams)
    and every per-item selector returning one (filter_on_observable, auto_refresh*,
    transform_on_observable, group_on_observable, merge_many*, when_*value_changed*,
    true_for_*, query_when_changed_on*) accepts ValueStream<_, E> with
    CollectionError: From<E>. Pass signal.values() directly; the collection_stream adapter shown
    in earlier docs is no longer needed. Two-stream operators
    (sort_*_dynamic_with_resort, sort_and_*_dynamic*, group_dynamic_with_regrouper) take an
    independent error type for each stream. Migration: existing call sites keep compiling, except
    a stream built inline with primitives::ValueStream::new(..) (the crate-root one) whose error
    type was inferred from the operator: annotate it (ValueStream::<_, CollectionError>::new) or
    build it with the collections::ValueStream alias, which fixes CollectionError.
  • Added: Computed::from_stream(&stream, initial) holds a stream's latest value as scalar state
    (for example a collection count() driving Command::with_can_execute).
  • Added: ValueStream combinators on ReactiveValueStreamExt: merge, combine_latest,
    with_latest_from, switch_map, scan, take, first, take_until, pairwise,
    distinct_until_changed_by, share and share_replay.

Collection sources

  • Breaking: CollectionError has a new variant Contended. Migration: add a wildcard or a
    Contended arm to exhaustive matches on CollectionError.
  • Fixed (behavior): cross-thread contention no longer fails with NestedUpdate. On native
    targets edits, reads, connect, lookup, watch*, suspend_notifications and count_changed
    wait for the lock, so concurrent writers and readers all succeed; on wasm32 they return
    Contended without blocking and without leaving partial subscriptions. NestedUpdate now means
    same-thread re-entry only, and is also returned (instead of deadlocking) for reads such as
    items()/len()/count()/keys()/version() and suspend_notifications made from inside the
    source's own edit closure, key selector or a T::clone running under its lock. The same applies
    to live groups (RayListGroup, RayCacheGroup). Migration: retry loops on NestedUpdate for
    contention are no longer needed.
  • Fixed: changes(), preview(), connect() and count_changed() deliver batches in commit
    (version) order with several writer threads; an edit made inside a preview() callback is
    delivered after the batch that triggered it.
  • Fixed: count_changed() registers before reading its baseline: counts changed during the
    initial callback or concurrently are reported, and the last reported count equals len()/count().
  • Fixed: connect(), watch(key), watch_value(key) and watch_optional(key) subscribed while
    notifications are suspended no longer receive changes their snapshot already covers when the
    suspension ends. The resumed publication is still one public ChangeSet.
  • Fixed: dropping the last notification-suspension guard always resumes publication, also when
    it is dropped inside the source's own edit or while another thread holds the source under the
    non-blocking policy (the resume is applied when the lock is released).
  • Fixed: a live group connection made between a grouping operator staging a change and
    publishing it no longer receives that change twice, and disposing a keyed watch whose subscriber
    owns another watch on the same cache no longer deadlocks.
  • Documented: native targets now wait on contention, so accessing source B from inside source
    A's edit closure while another thread does the reverse can deadlock; keep one global order.
  • Added: updater reads (ListUpdater::items/len/is_empty/get,
    CacheUpdater::lookup/contains_key/count/keys) reflecting the edit's own changes, making
    read-modify-write inside one edit atomic.
  • Added: SourceList::get/contains/read, SourceCache::contains_key/read (with the new
    CacheView), the same on ObservableList/ObservableCache, and SourceCache::from_items.
  • Changed (behavior): watch_value and watch_optional emit at most one value per batch, the
    key's final state in it (a load that keeps the key emits only the new value, with no None
    flicker), and ChangeStream::watch(key) forwards a Clear with only the watched key's entry.

Materialization and composition

  • Fixed: populate_into, populate_from*, the to_*_change_set* adapters,
    ObservableList/ObservableCache::from_stream (and as_observable_*) and the source size limits
    no longer discard a batch whose destination edit fails: the batch is kept and retried, in order,
    when the destination's lock is next released (and in any case before the next batch). A failure
    that cannot clear (Poisoned) is reported and its batch dropped. A retry runs on the thread that
    releases the lock, so the destination's subscribers can be called from that thread.
  • Added: populate_into_with_errors, populate_from_with_errors,
    populate_from_batches_with_errors, the to_list_change_set*_with_errors /
    to_cache_change_set*_with_errors adapters, limit_size_to_with_errors on SourceList and
    SourceCache, and ObservableList::errors() / ObservableCache::errors(), which report each
    failed attempt.
  • Fixed: the source size limits (SourceList/SourceCache::limit_size_to) recompute an eviction
    whose edit failed instead of queueing it, so an item is never evicted twice or in place of another,
    and emit only the items actually removed.
  • Fixed: ref_count rolls back a subscriber whose upstream connection failed; a subscriber that
    joined from another thread meanwhile waits for the outcome and connects again itself (on wasm32 it
    gets Contended instead of waiting). A late subscriber's snapshot always comes before live
    batches, and batches from a released upstream are ignored.
  • Fixed: switch delivers its output in order and never concurrently, never forwards a
    superseded inner's batches after its Clear, and leaves no inner subscription after disposal.
    Added: switch_with_errors reports an inner subscription that failed; the switch retries when
    the outer stream emits again.

Operator emission

  • Fixed: operators with several inputs (dynamic filters, sorts, page/virtualize and group
    operators, *_with_resort, *_with_regrouper, sort_and_*_dynamic*, all joins, static and
    dynamic combiners, forced transforms, transform_to_tree_with_predicate and
    merge_many_*_change_sets) no longer deliver batches out of order or concurrently when their
    inputs are driven from different threads; each subscription has one ordered output, possibly
    delivered by the thread that is already draining it. The live groups created by the group
    operators and the right groups of *_join_many are notified the same way: their own streams
    receive batches in computation order, after the operator released its lock, so a group's
    subscriber may edit the operator's inputs (it used to deadlock *_join_many).
  • Fixed: reverse, virtualize, group_by, ensure_unique_keys, cache transform_many and
    collect_update_stats no longer call their subscrib...
Read more