Skip to content

Instantly share code, notes, and snippets.

@priezz
Last active January 11, 2023 09:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save priezz/ca852da480abb148bd69dcb7f4e6dc51 to your computer and use it in GitHub Desktop.
Save priezz/ca852da480abb148bd69dcb7f4e6dc51 to your computer and use it in GitHub Desktop.
observers
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.');
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment