-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Summary
Add a source generator that produces a complete implementation of the Observer pattern for event publication and subscription, with safe-by-default lifetimes and deterministic behavior.
The generator lives in PatternKit.Generators and emits code that is:
- reflection-free
- allocation-aware
- explicit about threading policy
- self-contained (no runtime PatternKit dependency)
Motivation / Problem
Observer is easy to misuse:
- leaking subscriptions
- nondeterministic invocation order
- unclear exception behavior (one subscriber breaks others?)
- ad-hoc concurrency policies
We want a generated implementation that makes the “rules of engagement” explicit and testable.
Supported Targets (must-have)
The generator must support:
partial classpartial structpartial record classpartial record struct
Two consumption modes must be supported:
- Event type (a type represents one observable event stream).
- Event hub (a type groups multiple generated events).
Proposed User Experience
A) Single event, payload-based
[Observer]
public partial class TemperatureChanged { }Generated (representative shape):
public partial class TemperatureChanged
{
public IDisposable Subscribe(Action<Temperature> handler);
public IDisposable Subscribe(Func<Temperature, ValueTask> handler);
public void Publish(Temperature value);
public ValueTask PublishAsync(Temperature value, CancellationToken ct = default);
}B) Event hub (multi-event grouping)
[ObserverHub]
public static partial class SystemEvents
{
[ObservedEvent]
public static partial TemperatureChanged TemperatureChanged { get; }
[ObservedEvent]
public static partial ShutdownRequested ShutdownRequested { get; }
}Generated semantics:
- Each
[ObservedEvent]property returns a singleton instance of that event stream. - Hub generation is optional, but if present must be deterministic and self-contained.
Attributes / Surface Area
Namespace: PatternKit.Generators.Observer
Core
[Observer]on an event stream type[ObserverHub]on a hub type[ObservedEvent]on hub properties
Configuration
ObserverAttribute suggested properties:
ObserverThreadingPolicy Threading(default:Locking)ObserverExceptionPolicy Exceptions(default:Continue)ObserverOrderPolicy Order(default:RegistrationOrder)bool GenerateAsync(default: inferred)bool ForceAsync(default: false)
Enums:
ObserverThreadingPolicy:SingleThreadedFast,Locking,ConcurrentObserverExceptionPolicy:Stop,Continue,AggregateObserverOrderPolicy:RegistrationOrder,Undefined
Semantics (must-have)
Subscriptions
Subscribe(Action<T>)returns anIDisposabletoken.Dispose()unsubscribes deterministically.- Duplicate subscriptions are allowed in v1 (invoked multiple times).
Publishing
- Default order:
RegistrationOrder. - Publishing uses snapshot semantics (publish iterates a stable snapshot so modifications during publish do not affect the current cycle).
Exception policies
-
Continue(default): invoke all handlers; exceptions do not stop others.- v1: either swallow exceptions or route them to an optional user hook (see below). Must be explicit.
-
Stop: first exception aborts. -
Aggregate: run all and throw anAggregateException(or return a result) at the end.
Recommended v1 behavior:
- For sync
Publish:Continueswallows by default but provides an optional hook:OnSubscriberError(Exception ex)if present. - For async
PublishAsync: same semantics.
Async
Subscribe(Func<T, ValueTask>)must be supported.PublishAsyncinvokes async handlers in deterministic order.- Cancellation token behavior: best-effort. If canceled before next invocation, stop and return canceled.
Threading policies
SingleThreadedFast: no locks; documented as not thread-safe.Locking: lock around subscribe/unsubscribe; publish takes snapshot under lock.Concurrent: thread-safe with concurrent primitives; ordering may degrade toUndefinedunless extra work is done. Must be documented.
Optional advanced features (explicitly v2 unless trivial)
- Weak subscriptions
- Backpressure / queueing
- Filters / predicate subscriptions
- “Once” subscriptions
Diagnostics (must-have)
Stable IDs, actionable:
PKOBS001Type marked[Observer]must bepartial.PKOBS002Hub type marked[ObserverHub]must bepartialandstatic.PKOBS003Hub property marked[ObservedEvent]has invalid shape (must bestatic partialand return the event stream type).PKOBS004Async publish requested but async handler shape unsupported.PKOBS005Invalid configuration combination (e.g.,Concurrent+RegistrationOrderrequires additional ordering guarantees).
Generated Code Layout
TypeName.Observer.g.csTypeName.ObserverHub.g.cs(if hub feature enabled)
Determinism:
- internal storage deterministic where promised (especially for
RegistrationOrder).
Testing Expectations
-
Subscribe/unsubscribe works and is idempotent.
-
Publish invokes in registration order.
-
Dispose during publish does not break iteration (snapshot semantics).
-
Exception policy behavior matches docs:
- Continue
- Stop
- Aggregate
-
Threading policy tests:
- Locking prevents basic races in a concurrent subscribe/publish stress test.
-
Async publish respects cancellation (best-effort).
Acceptance Criteria
-
[Observer]works for class/struct/record class/record struct. - Supports sync handlers and async handlers (
ValueTask). - Deterministic ordering for
RegistrationOrdermodes. - Snapshot publishing semantics.
- Clear exception policy with tests.
- Explicit, documented threading policy.
- Hub mode supported (optional but if implemented, fully tested).
- Diagnostics cover invalid usage and config.