Commit dfd821e5 authored by Hixie's avatar Hixie

Ignore generation of child if child is unchanged

Also:

 - don't mark a node as from the new generation if it is dirty, since we
   know it still has to be built.

 - establish the rule that you can't call setState() during initState()
   or build().

 - make syncChild() return early for unchanged children.

 - update the tests, including adding a new one.
parent 2cb58ebf
......@@ -389,18 +389,12 @@ abstract class Widget {
// Returns the child which should be retained as the child of this node.
Widget syncChild(Widget newNode, Widget oldNode, dynamic slot) {
assert(() {
'You have probably used a single instance of a Widget in two different places in the widget tree. Widgets can only be used in one place at a time.';
return newNode == null || newNode.isFromOldGeneration;
});
if (oldNode != null && !oldNode.isFromOldGeneration)
oldNode = null;
if (newNode == oldNode) {
assert(newNode == null || newNode.isFromOldGeneration);
// TODO(ianh): Simplify next few asserts once the analyzer is cleverer
assert(newNode is! RenderObjectWrapper ||
(newNode is RenderObjectWrapper && newNode._ancestor != null)); // TODO(ianh): Simplify this once the analyzer is cleverer
(newNode is RenderObjectWrapper && newNode._ancestor != null)); // if the child didn't change, it had better be configured
assert(newNode is! StatefulComponent ||
(newNode is StatefulComponent && newNode._isStateInitialized)); // if the child didn't change, it had better be configured
if (newNode != null) {
newNode.setParent(this);
newNode._markAsFromCurrentGeneration();
......@@ -408,8 +402,18 @@ abstract class Widget {
return newNode; // Nothing to do. Subtrees must be identical.
}
assert(() {
'You have probably used a single instance of a Widget in two different places in the widget tree. Widgets can only be used in one place at a time.';
return newNode == null || newNode.isFromOldGeneration;
});
if (oldNode != null && !oldNode.isFromOldGeneration)
oldNode = null;
if (newNode == null) {
// the child in this slot has gone away (we know oldNode != null)
// the child in this slot has gone away
// remove it if they old one is still here
if (oldNode != null) {
assert(oldNode != null);
assert(oldNode.isFromOldGeneration);
assert(oldNode.mounted);
......@@ -418,6 +422,7 @@ abstract class Widget {
assert(!oldNode.mounted);
// we don't update the generation of oldNode, because there's
// still a chance it could be reused as-is later in the tree.
}
return null;
}
......@@ -868,6 +873,12 @@ abstract class Component extends Widget {
super._sync(old, slot);
}
void _markAsFromCurrentGeneration() {
if (_dirty)
return;
super._markAsFromCurrentGeneration();
}
void _buildIfDirty() {
if (!_dirty || !_mounted)
return;
......@@ -947,6 +958,8 @@ abstract class StatefulComponent extends Component {
// Calls function fn immediately and then schedules another build
// for this Component.
void setState(void fn()) {
assert(!_debugIsBuilding);
assert(_isStateInitialized);
fn();
_scheduleBuild();
}
......@@ -985,12 +998,14 @@ void exitLayoutCallbackBuilder(LayoutCallbackBuilderHandle handle) {
List<int> _debugFrameTimes = <int>[];
// TODO(ianh): Move this to Component
void _absorbDirtyComponents(List<Component> list) {
list.addAll(_dirtyComponents);
_dirtyComponents.clear();
list.sort((Component a, Component b) => a._order - b._order);
}
// TODO(ianh): Move this to Component
void _buildDirtyComponents() {
assert(!_dirtyComponents.isEmpty);
......@@ -1038,11 +1053,13 @@ void _buildDirtyComponents() {
}
// TODO(ianh): Move this to Widget
void _endSyncPhase() {
Widget._currentGeneration += 1;
Widget._notifyMountStatusChanged();
}
// TODO(ianh): Move this to Component
void _scheduleComponentForRender(Component component) {
_dirtyComponents.add(component);
if (!_buildScheduled) {
......
......@@ -4,50 +4,50 @@ import 'package:test/test.dart';
import 'widget_tester.dart';
class SecondComponent extends StatefulComponent {
SecondComponent(this.navigator);
Navigator navigator;
class FirstComponent extends Component {
FirstComponent(this.navigator);
void syncConstructorArguments(SecondComponent source) {
navigator = source.navigator;
}
final Navigator navigator;
Widget build() {
return new GestureDetector(
onTap: navigator.pop,
onTap: () {
navigator.pushNamed('/second');
},
child: new Container(
decoration: new BoxDecoration(
backgroundColor: new Color(0xFFFF00FF)
backgroundColor: new Color(0xFFFFFF00)
),
child: new Text('Y')
child: new Text('X')
)
);
}
}
class FirstComponent extends Component {
FirstComponent(this.navigator);
class SecondComponent extends StatefulComponent {
SecondComponent(this.navigator);
final Navigator navigator;
Navigator navigator;
void syncConstructorArguments(SecondComponent source) {
navigator = source.navigator;
}
Widget build() {
return new GestureDetector(
onTap: () {
navigator.pushNamed('/second');
},
onTap: navigator.pop,
child: new Container(
decoration: new BoxDecoration(
backgroundColor: new Color(0xFFFFFF00)
backgroundColor: new Color(0xFFFF00FF)
),
child: new Text('X')
child: new Text('Y')
)
);
}
}
void main() {
test('Can navigator to and from a stateful component', () {
test('Can navigator navigate to and from a stateful component', () {
WidgetTester tester = new WidgetTester();
final NavigationState routes = new NavigationState([
......@@ -65,8 +65,15 @@ void main() {
return new Navigator(routes);
});
expect(tester.findText('X'), isNotNull);
expect(tester.findText('Y'), isNull);
tester.tap(tester.findText('X'));
scheduler.beginFrame(10.0);
expect(tester.findText('X'), isNotNull);
expect(tester.findText('Y'), isNotNull);
scheduler.beginFrame(20.0);
scheduler.beginFrame(30.0);
scheduler.beginFrame(1000.0);
......@@ -77,5 +84,8 @@ void main() {
scheduler.beginFrame(1030.0);
scheduler.beginFrame(2000.0);
expect(tester.findText('X'), isNotNull);
expect(tester.findText('Y'), isNull);
});
}
import 'package:sky/gestures/arena.dart';
import 'package:sky/gestures/pointer_router.dart';
import 'package:sky/gestures/tap.dart';
import 'package:sky/widgets.dart';
import 'package:test/test.dart';
import '../engine/mock_events.dart';
import 'widget_tester.dart';
class Inside extends StatefulComponent {
void syncConstructorArguments(Inside source) {
}
Widget build() {
return new Listener(
onPointerDown: _handlePointerDown,
child: new Text('INSIDE')
);
}
EventDisposition _handlePointerDown(_) {
setState(() { });
return EventDisposition.processed;
}
}
class Middle extends StatefulComponent {
Inside child;
Middle({ this.child });
void syncConstructorArguments(Middle source) {
child = source.child;
}
Widget build() {
return new Listener(
onPointerDown: _handlePointerDown,
child: child
);
}
EventDisposition _handlePointerDown(_) {
setState(() { });
return EventDisposition.processed;
}
}
class Outside extends App {
Widget build() {
return new Middle(child: new Inside());
}
}
void main() {
test('setState() smoke test', () {
WidgetTester tester = new WidgetTester();
tester.pumpFrame(() {
return new Outside();
});
TestPointer pointer = new TestPointer(1);
Point location = tester.getCenter(tester.findText('INSIDE'));
tester.dispatchEvent(pointer.down(location), location);
tester.pumpFrameWithoutChange();
tester.dispatchEvent(pointer.up(), location);
tester.pumpFrameWithoutChange();
});
}
\ No newline at end of file
......@@ -40,27 +40,36 @@ void main() {
WidgetTester tester = new WidgetTester();
InnerComponent inner;
InnerComponent inner1;
InnerComponent inner2;
OuterContainer outer;
tester.pumpFrame(() {
return new OuterContainer(child: new InnerComponent());
inner1 = new InnerComponent();
outer = new OuterContainer(child: inner1);
return outer;
});
expect(inner1._didInitState, isTrue);
expect(inner1.parent, isNotNull);
tester.pumpFrame(() {
inner = new InnerComponent();
outer = new OuterContainer(child: inner);
return outer;
inner2 = new InnerComponent();
return new OuterContainer(child: inner2);
});
expect(inner._didInitState, isFalse);
expect(inner.parent, isNull);
expect(inner1._didInitState, isTrue);
expect(inner1.parent, isNotNull);
expect(inner2._didInitState, isFalse);
expect(inner2.parent, isNull);
outer.setState(() {});
scheduler.beginFrame(0.0);
tester.pumpFrameWithoutChange(0.0);
expect(inner._didInitState, isFalse);
expect(inner.parent, isNull);
expect(inner1._didInitState, isTrue);
expect(inner1.parent, isNotNull);
expect(inner2._didInitState, isFalse);
expect(inner2.parent, isNull);
});
}
......@@ -9,7 +9,6 @@ import '../engine/mock_events.dart';
typedef Widget WidgetBuilder();
class TestApp extends App {
TestApp();
WidgetBuilder _builder;
void set builder (WidgetBuilder value) {
......@@ -29,6 +28,7 @@ class WidgetTester {
WidgetTester() {
_app = new TestApp();
runApp(_app);
scheduler.beginFrame(0.0); // to initialise the app
}
TestApp _app;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment