Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tracking changes between start and end #19

Merged
merged 3 commits into from
Jan 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions lib/mobx.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,14 @@ library mobx;
import 'package:mobx/src/api/observable.dart';

export 'package:mobx/src/api/action.dart';
export 'package:mobx/src/core/action.dart' show Action;
export 'package:mobx/src/api/observable.dart';
export 'package:mobx/src/core/observable.dart' show ObservableValue;
export 'package:mobx/src/api/reaction.dart';
export 'package:mobx/src/api/reaction_helper.dart' show ReactionDisposer;
export 'package:mobx/src/core/reaction.dart' show Reaction, DerivationTracker;
export 'package:mobx/src/api/context.dart';
export 'package:mobx/src/core/context.dart' show ReactiveContext;
export 'package:mobx/src/core/computed.dart' show ComputedValue;
export 'package:mobx/src/core/atom.dart'
show Atom, WillChangeNotification, ChangeNotification;
19 changes: 13 additions & 6 deletions lib/src/core/context.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,18 +55,25 @@ class ReactiveContext {
}
}

T trackDerivation<T>(Derivation d, T Function() fn) {
Derivation startTracking(Derivation derivation) {
final prevDerivation = _state.trackingDerivation;
_state.trackingDerivation = d;
_state.trackingDerivation = derivation;

resetDerivationState(d);
d.newObservables = Set();
resetDerivationState(derivation);
derivation.newObservables = Set();

final result = fn();
return prevDerivation;
}

void endTracking(Derivation currentDerivation, Derivation prevDerivation) {
_state.trackingDerivation = prevDerivation;
bindDependencies(d);
bindDependencies(currentDerivation);
}

T trackDerivation<T>(Derivation d, T Function() fn) {
final prevDerivation = startTracking(d);
final result = fn();
endTracking(d, prevDerivation);
return result;
}

Expand Down
54 changes: 54 additions & 0 deletions lib/src/core/reaction.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,42 @@
import 'package:meta/meta.dart';
import 'package:mobx/src/core/atom.dart';
import 'package:mobx/src/core/context.dart';
import 'package:mobx/src/core/derivation.dart';

/// Tracks changes that happen between [start] and [end].
///
/// This should only be used in situations where it is not possible to
/// track changes inside a callback function.
@experimental
class DerivationTracker {
DerivationTracker(ReactiveContext context, Function() onInvalidate,
{String name})
: _reaction = Reaction(context, onInvalidate, name: name);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not clear why we need a reaction here for tracking? Could you please elaborate? Is it mostly to wrap the onInvalidate in some derivation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically yes, I'm not sure how to do it better. DerivationTracker is pretty much Reaction, but with track replaced with start and end, so it made sense to delegate to Reaction.


final Reaction _reaction;
Derivation _previousDerivation;

void start() {
if (_reaction._isRunning) {
return;
}
_previousDerivation = _reaction._startTracking();
}

void end() {
if (!_reaction._isRunning) {
return;
}
_reaction._endTracking(_previousDerivation);
_previousDerivation = null;
}

void dispose() {
end();
_reaction.dispose();
}
}

class Reaction implements Derivation {
Reaction(this._context, Function() onInvalidate, {this.name}) {
_onInvalidate = onInvalidate;
Expand Down Expand Up @@ -32,6 +67,25 @@ class Reaction implements Derivation {
schedule();
}

@experimental
Derivation _startTracking() {
_context.startBatch();
_isRunning = true;
return _context.startTracking(this);
}

@experimental
void _endTracking(Derivation previous) {
_context.endTracking(this, previous);
_isRunning = false;

if (_isDisposed) {
_context.clearObservables(this);
}

_context.endBatch();
}

void track(void Function() fn) {
_context.startBatch();

Expand Down
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ homepage: https://github.com/mobxjs/mobx.dart

environment:
sdk: '>=2.1.0-dev <3.0.0'
meta: ^1.1.7

dev_dependencies:
test: ^1.5.1
Expand Down
2 changes: 2 additions & 0 deletions test/all.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'action_test.dart' as action_test;
import 'autorun_test.dart' as autorun_test;
import 'computed_test.dart' as computed_test;
import 'derivation_tracker_test.dart' as derivation_tracker_test;
import 'intercept_test.dart' as intercept_test;
import 'listenable_test.dart' as listenable_test;
import 'observable_test.dart' as observable_test;
Expand All @@ -18,4 +19,5 @@ void main() {
observe_test.main();
intercept_test.main();
listenable_test.main();
derivation_tracker_test.main();
}
170 changes: 170 additions & 0 deletions test/derivation_tracker_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import 'package:mobx/mobx.dart';
import 'package:mobx/src/api/context.dart';
import 'package:mobx/src/core/reaction.dart';
import 'package:test/test.dart';

void main() {
test(
'DerivationTracker reacts to changes to reactive values between begin and end',
() {
var i = 0;

final tracker = DerivationTracker(currentContext, () {
i++;
});

final var1 = observable(0);
final var2 = observable(0);

tracker.start();
final var3 = observable(0);
var1.value;
tracker.end();

// No changes, no calls to onInvalidate
expect(i, equals(0));

// Change outside tracking, no onInvalidate call
var2.value += 1;
expect(i, equals(0));

// Change outside tracking to an observable created inside tracking
// no onInvalidate call
var3.value += 1;
expect(i, equals(0));

// Changing a value that was read when tracking was active
// calls onInvalidate
var1.value += 1;
expect(i, equals(1));

// No calls to onInvalidate after first change
var1.value += 1;
expect(i, equals(1));
});

test('DerivationTracker can be used multiple times', () {
var i = 0;
final tracker = DerivationTracker(currentContext, () {
i++;
});
final var1 = observable(0);

tracker.start();
var1.value;
tracker.end();

expect(i, equals(0));

var1.value += 1;
expect(i, equals(1));

tracker.start();
var1.value;
tracker.end();

var1.value += 1;
expect(i, equals(2));

final var2 = observable(0);
tracker.start();
var2.value;
tracker.end();

var2.value += 1;
expect(i, equals(3));
});

test("disposed DerivationTracker doesn't call onInvalidate", () {
var i = 0;
final tracker = DerivationTracker(currentContext, () {
i++;
});
final var1 = observable(0);

tracker.start();
var1.value;
tracker
..end()
..dispose();

var1.value += 1;
expect(i, equals(0));
});

test('calling start multiple times before calling end does nothing', () {
var i = 0;
final tracker = DerivationTracker(currentContext, () {
i++;
});
final var1 = observable(0);

tracker..start()..start()..start();

var1.value;

tracker
..start()
..end();

expect(i, equals(0));

var1.value += 1;
expect(i, equals(1));
});

test('calling end multiple times after start does nothing', () {
var i = 0;
final tracker = DerivationTracker(currentContext, () {
i++;
});
final var1 = observable(0);

tracker.start();
var1.value;
tracker..end()..end()..end();

expect(i, equals(0));

var1.value += 1;
expect(i, equals(1));
});

test('calling dispose multiple times does nothing', () {
var i = 0;
final tracker = DerivationTracker(currentContext, () {
i++;
});
final var1 = observable(0);

tracker.start();
var1.value;
tracker..dispose()..dispose()..dispose();

expect(i, equals(0));

var1.value += 1;
expect(i, equals(0));
});

test('autorun works inside tracking', () {
var i = 0;
var autoVar = 0;
final tracker = DerivationTracker(currentContext, () {
i++;
});
final var1 = observable(0);

tracker.start();
var1.value;
autorun((_) {
autoVar += var1.value;
});
tracker.end();

var1.value = 1;

expect(i, equals(1));
expect(autoVar, equals(1));
});
}