drip_core 0.5.0-alpha
drip_core: ^0.5.0-alpha copied to clipboard
High-performance, zero-dependency reactive engine for the DRIP framework. Synchronous-only tracking with microtask coalescing.
drip_core #
Direct Render Isolated Propagation — A high-performance, pure Dart reactive engine.
DRIP Core is the zero-dependency state engine powering the DRIP framework. It provides atomic reactive primitives with synchronous-only tracking and microtask-based coalescing, giving the Flutter render layer the granularity needed to bypass the widget tree entirely.
⚠️ Early Alpha: APIs are stable within the alpha series but subject to change before v1.0.0.
⚡ Performance #
- High Throughput: Capable of
>10,000,000writes/sec on modern hardware. - O(1) Dependency Registration: Tracking cost is constant per read.
- O(n) Propagation: Invalidation cost is proportional only to direct subscribers.
- Zero Zones: No
dart:asyncZone overhead for tracking. - Smart Batching: Multiple synchronous writes coalesce into exactly one propagation pass.
🛠 Core Primitives #
| Class | Role |
|---|---|
DripState<T> |
The source of truth. Holds a typed value and a version clock. |
DripComputed<T> |
Lazily evaluated, cached derivation. Recomputes only when dependencies change. |
DripAsync<T> |
Reactive async state container. Manages transitions between loading, data, and error states with concurrent call cancellation. |
DripAsyncValue<T> |
Sealed class (DripLoading, DripData, DripError) providing exhaustive state mapping and previousData preservation. |
DripEffect |
Automatic side-effect. Runs once on creation, re-runs on dependency change. |
DripScope |
Resource owner. Disposes registered nodes in LIFO order. |
DripReadable<T> |
Shared read/subscribe interface implemented by DripState, DripComputed, and DripAsync. |
📖 Usage #
Basic reactive state #
import 'package:drip_core/drip_core.dart';
final count = dripState(0);
DripEffect(() {
print('Count: ${count.value}');
});
// Multiple synchronous writes → one propagation pass
count.write(1);
count.write(2);
count.write(3);
// Output (after microtask flush):
// Count: 0 ← initial run
// Count: 3 ← single batched notification
Derived state #
final a = dripState(10);
final b = dripState(20);
final sum = DripComputed(() => a.value + b.value);
print(sum.value); // 30
a.write(15);
print(sum.value); // 35 — recomputed lazily on read
Resource management #
final scope = DripScope(debugName: 'UserModule');
final name = scope.state('Alice');
scope.effect(() => print('Hello, ${name.value}'));
scope.dispose(); // disposes name and the effect in LIFO order
Accepting multiple sources via DripReadable #
void display(DripReadable<String> source) {
print(source.value);
}
final raw = dripState('hello');
final upper = DripComputed(() => raw.value.toUpperCase());
final asyncStr = DripAsync<String>()..setData('async data');
display(raw); // ✅
display(upper); // ✅
// Note: asyncStr.value is a DripAsyncValue<String>, so you'd normally map it first
🏗 Key Architectural Invariants #
- Synchronous-Only Tracking: Dependencies are only tracked during synchronous execution. Async gaps (after
await) never register spurious dependencies. - Deterministic Propagation: Notification order follows the reactive graph.
- One Pass Per Frame: Updates are batched into microtasks — complex graphs trigger exactly one propagation pass per sync block.
- LIFO Disposal: Scopes dispose resources in reverse creation order, ensuring safe cleanup of dependent nodes.
📦 Installation #
dependencies:
drip_core: ^0.5.0-alpha
📄 License #
MIT — see LICENSE.