observers
Created with <3 with dartpad.dev.
Created with <3 with dartpad.dev.
import 'dart:async'; | |
typedef Listener = void Function(); | |
int _actionsProcessing = 0; | |
Set<Observable>? _currentReactives; | |
Set<Observable>? _scheduledReactives; | |
class Observable<T> { | |
Observable(T initialValue) : _value = initialValue; | |
final Set<Listener> _listeners = {}; | |
// TODO: Side effects - to be called before listeners in a similar to actions | |
// mode (with delayed notifications) | |
// TODO: Compound side effects - similar to observers | |
T _value; | |
T get value { | |
_currentReactives?.add(this); | |
return _value; | |
} | |
set value(T value) { | |
if (value == _value) return; | |
_value = value; | |
_scheduledReactives?.add(this) ?? _notify(); | |
} | |
void _notify() { | |
for (final listener in _listeners) { | |
listener(); | |
} | |
} | |
} | |
class Observer { | |
Observer(this._fn) { | |
_initialize(); | |
} | |
final Listener _fn; | |
static bool _processing = false; | |
final Set<Observable> _reactives = {}; | |
Future<void> _initialize() async { | |
while (_processing) { | |
await Future.delayed(Duration.zero); | |
} | |
_processing = true; | |
_currentReactives = _reactives; | |
_fn(); | |
_currentReactives = null; | |
for (final r in _reactives) { | |
r._listeners.add(_listener); | |
} | |
_processing = false; | |
} | |
void dispose() { | |
for (final r in _reactives) { | |
r._listeners.remove(_listener); | |
} | |
} | |
void _listener() => _fn(); | |
} | |
void action(Function() fn) { | |
// TODO: Deal with parallel and nested actions | |
_actionsProcessing++; | |
_scheduledReactives ??= {}; | |
fn(); | |
_actionsProcessing--; | |
if (_actionsProcessing > 0) return; | |
Set<Observable> reactives = _scheduledReactives!; | |
_scheduledReactives = null; | |
final Set<Listener> listeners = {for (final r in reactives) ...r._listeners}; | |
for (final listener in listeners) { | |
listener(); | |
} | |
} | |
/// ---------------------------------------------------------------------------- | |
final DateTime _start = DateTime.now(); | |
String time([DateTime? from]) => | |
// '[${DateTime.now().difference(from ?? _start).inMicroseconds.toString()}μs]'; | |
'[${DateTime.now().difference(from ?? _start).inMilliseconds.toString()}ms]'; | |
const int _iterations = 10000000; | |
/// Generated classes would look similar | |
class SomeData { | |
final Observable<int> _i = Observable(0); | |
int get i => _i.value; | |
set i(int value) => _i.value = value; | |
final Observable<int> _k = Observable(0); | |
int get k => _k.value; | |
set k(int value) => _k.value = value; | |
@override | |
toString() => '$i + $k = ${i + k}'; | |
} | |
extension SomeDataExtension on SomeData { | |
update() => action(() { | |
i = 2; | |
k = 3; | |
}); | |
} | |
Future<void> main() async { | |
final Observable<String> first = Observable('--'); | |
final Observable<String> second = Observable('--'); | |
final r1 = Observer( | |
() => print('${time()} 1: ${first.value} ${second.value}'), | |
); | |
final r2 = Observer( | |
() => print('${time()} 2: ${second.value}'), | |
); | |
first.value = 'Hello'; | |
second.value = 'here'; | |
r1.dispose(); | |
first.value = 'Goodbye'; | |
second.value = 'there'; | |
r2.dispose(); | |
final data = SomeData(); | |
final r3 = Observer(() => print('${time()} $data')); | |
data.update(); | |
r3.dispose(); | |
/// Performance tests | |
DateTime start = DateTime.now(); | |
final List<int> list1 = List.generate(_iterations, (i) => i); | |
print('\nList 1 - ${time(start)}. Create'); | |
start = DateTime.now(); | |
for (int i = 0; i < _iterations; i++) { | |
list1[i] *= 2; | |
} | |
print('List 1 - ${time(start)}. Update'); | |
start = DateTime.now(); | |
final List<Observable<int>> list2 = | |
List.generate(_iterations, (i) => Observable(i)); | |
print('\nList 2 - ${time(start)}. Create'); | |
start = DateTime.now(); | |
for (int i = 0; i < _iterations; i++) { | |
list2[i].value += 2; | |
list1[i] = list2[i].value; | |
} | |
print('List 2 - ${time(start)}. Update'); | |
final List<Observer?> observers = List.generate(_iterations, (i) => null); | |
start = DateTime.now(); | |
for (int i = 0; i < _iterations; i++) { | |
observers[i] = Observer(() => list1[i] = list2[i].value); | |
} | |
print('List 2 - ${time(start)}. Launch observers'); | |
start = DateTime.now(); | |
for (int i = 0; i < _iterations; i++) { | |
++list2[i].value; | |
} | |
print('List 2 - ${time(start)}. Update'); | |
start = DateTime.now(); | |
for (int i = 0; i < _iterations; i++) { | |
observers[i]?.dispose(); | |
} | |
print('List 2 - ${time(start)}. Dispose observers'); | |
final observer = Observer(() { | |
int result = 0; | |
for (int i = 0; i < _iterations; i++) { | |
result += list2[i].value; | |
} | |
print('List 2 sum = $result'); | |
}); | |
start = DateTime.now(); | |
action(() { | |
for (int i = 0; i < _iterations; i++) { | |
++list2[i].value; | |
} | |
}); | |
print('List 2 - ${time(start)}. Batch update'); | |
observer.dispose(); | |
/// Validate [list1] data | |
bool checked = true; | |
for (int i = 0; i < _iterations; i++) { | |
if (list1[i] == i + 3) continue; | |
checked = false; | |
break; | |
} | |
print(checked ? 'Checked' : 'Not checked'); | |
print('\n${time()} Done.'); | |
} |