Skip to content

Core Semantics

Nicolás Seijas edited this page Aug 3, 2026 · 3 revisions

Core Semantics (the frozen V1 contract)

These rules give the behavior of Rambla in V1. The rules are frozen. The generators and all the adapters use them. Thus a change to one rule is a breaking change. Tests examine each rule.

The primary source is SEMANTICS.md. This page is a summary in Simplified Technical English.

State

1. An error in a subscriber does not stop the engine

If a PropertyChanged subscriber makes an error, Rambla does not catch the error. The error goes to the caller.

Rambla always keeps the state operational. Before it raises the notifications, Rambla clears the flush flag and empties the set of dirty properties. Thus a subsequent mutation makes a new flush. The notifications after the error do not occur in that flush. The values are already in the fields.

The same rule applies to the scheduler. If Post makes an error, Rambla removes the flush flag. Thus a subsequent mutation posts a new flush.

2. The notification order is not specified

In one flush, the order of the PropertyChanged events is not specified. Rambla notifies each dirty property one time. Do not write code that needs Bid before Ask.

3. A mutation is a true change of a value

SetField compares the two values with EqualityComparer<T>.Default. If the values are equal, Rambla does these operations:

  • It does not write the field.
  • It does not mark the property.
  • It does not raise a notification.
  • It does not count a mutation.

Thus StateMetrics.Mutations counts the true changes, not the write attempts.

4. Batches

  • The disposal of the external scope of BeginUpdate() makes exactly one flush. If no value changed in the batch, it makes no flush.
  • With nested batches, only the external scope makes the flush.
  • You can dispose the scopes in any order.
  • A second disposal of one scope has no result.
  • No flush occurs while a batch is open.

5. The scheduler can be immediate or deferred

IStateScheduler.Post can run the flush immediately or later. Rambla is correct in both conditions. Rambla posts the flush outside of its lock. Thus an immediate scheduler does not raise a notification while the lock is closed.

6. Lifetime

RamblaState holds no resources. It is not IDisposable. It does not dispose its scheduler. The caller holds the scheduler and disposes it.

7. A read from a different thread can be old or torn

A property getter reads the field with no synchronization. Thus a reader on a different thread can get an old value. For a type that is longer than one machine word, such as decimal, the reader can get a mixture of the old bytes and the new bytes.

A read in a PropertyChanged handler is always safe. To read a long value safely from a different thread, publish an immutable object.

Collections

The two collections obey the rules above and these additional rules.

  1. A read gives the visible contents. The visible contents always agree with the notifications that Rambla raised. A mutation is not visible before the next flush.
  2. One flush raises the minimum number of notifications. The flush compares the previous contents with the pending target. If more than ResetThreshold items changed, the flush raises one Reset notification. The default value is 32.
  3. V1 has no Move notification. A change of the order gives Replace notifications.
  4. The mutators accept any thread. The reads and the notifications belong to the thread of the scheduler.
  5. Only one flush operates at a time. A flush that finds a flush in operation stops. The flush in operation applies all the changes.
  6. An error in a handler does not leave a partial condition. The flush stops and the error goes to the caller. Then Rambla makes one recovery flush. That flush makes the contents agree with the notifications.

The dictionary adds the insertion order, one Replace for each key, mutators that use the pending target, and an atomic ReplaceSnapshot. Refer to Collections.

Async commands

  1. One run at a time. By default, CanExecute gives false while a run is in operation. With CancelPrevious, a new run cancels the run in operation.
  2. A cancelled run cannot change the state. Only the last run gives a value to IsRunning and to the error property.
  3. The command holds the error. The error does not go to the caller. The command deletes the error when the next run starts.
  4. Cancellation is not an error. The error property stays null.
  5. The run continues on the thread that started it. Thus a command that a person starts from the UI thread gives its state on the UI thread, for all schedulers. CanExecuteChanged also goes through the scheduler, and Rambla coalesces it.
  6. Rambla makes the command at the first read of the command property.
  7. The command holds no resources between the runs. A run that is in operation continues after the view closes. Cancel it if this is not correct for your application.

The throttling scheduler

  1. The rate is a maximum, not a target.
  2. The scheduler releases the first post after an idle interval immediately.
  3. The scheduler does not discard a flush.
  4. An error in a subscriber does not stop the flushes behind it.
  5. The scheduler holds a timer. The caller disposes the scheduler.

Refer to Schedulers.

Clone this wiki locally