Commit b9c06c0a authored by Adam Barth's avatar Adam Barth

Make stateful components work in fn3

parent 6a88ec6d
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:sky/animation.dart';
import 'package:sky/rendering.dart'; import 'package:sky/rendering.dart';
abstract class Key { abstract class Key {
...@@ -107,6 +108,9 @@ abstract class Element<T extends Widget> { ...@@ -107,6 +108,9 @@ abstract class Element<T extends Widget> {
} }
Element _parent; Element _parent;
dynamic _slot;
int _depth;
T _widget; T _widget;
_ElementLifecycle _lifecycleState = _ElementLifecycle.initial; _ElementLifecycle _lifecycleState = _ElementLifecycle.initial;
...@@ -123,15 +127,30 @@ abstract class Element<T extends Widget> { ...@@ -123,15 +127,30 @@ abstract class Element<T extends Widget> {
void mount(dynamic slot) { void mount(dynamic slot) {
assert(_lifecycleState == _ElementLifecycle.initial); assert(_lifecycleState == _ElementLifecycle.initial);
assert(_parent == null || _parent._lifecycleState == _ElementLifecycle.mounted);
assert(slot != null);
assert(_widget != null); assert(_widget != null);
assert(_depth == null);
_lifecycleState = _ElementLifecycle.mounted; _lifecycleState = _ElementLifecycle.mounted;
assert(_parent == null || _parent._lifecycleState == _ElementLifecycle.mounted); _slot = slot;
_depth = _parent == null ? 0 : _parent._depth + 1;
} }
void update(T updated, dynamic slot) { void updateSlot(dynamic slot) {
assert(slot != null);
assert(_lifecycleState == _ElementLifecycle.mounted); assert(_lifecycleState == _ElementLifecycle.mounted);
assert(_parent != null);
assert(_parent._lifecycleState == _ElementLifecycle.mounted);
assert(_widget != null); assert(_widget != null);
assert(_depth == null);
_slot = slot;
}
void update(T updated) {
assert(updated != null); assert(updated != null);
assert(_lifecycleState == _ElementLifecycle.mounted);
assert(_widget != null);
assert(_depth != null);
assert(_canUpdate(_widget, updated)); assert(_canUpdate(_widget, updated));
_widget = updated; _widget = updated;
} }
...@@ -139,12 +158,30 @@ abstract class Element<T extends Widget> { ...@@ -139,12 +158,30 @@ abstract class Element<T extends Widget> {
void unmount() { void unmount() {
assert(_lifecycleState == _ElementLifecycle.mounted); assert(_lifecycleState == _ElementLifecycle.mounted);
assert(_widget != null); assert(_widget != null);
assert(_depth != null);
_slot = null;
_depth = null;
_lifecycleState = _ElementLifecycle.defunct; _lifecycleState = _ElementLifecycle.defunct;
} }
void _updateSlotForChild(Element child, dynamic slot) {
if (child == null)
return;
assert(child._parent == this);
void move(Element element) {
child.updateSlot(slot);
if (child is! RenderObjectElement)
child.visitChildren(move);
}
move(child);
}
void _detachChild(Element child) { void _detachChild(Element child) {
if (child == null) if (child == null)
return; return;
assert(child._parent == this);
child._parent = null; child._parent = null;
bool haveDetachedRenderObject = false; bool haveDetachedRenderObject = false;
...@@ -168,7 +205,7 @@ abstract class Element<T extends Widget> { ...@@ -168,7 +205,7 @@ abstract class Element<T extends Widget> {
if (child != null) { if (child != null) {
if (_canUpdate(child._widget, updated)) { if (_canUpdate(child._widget, updated)) {
child.update(updated, slot); child.update(updated);
return child; return child;
} }
_detachChild(child); _detachChild(child);
...@@ -181,15 +218,66 @@ abstract class Element<T extends Widget> { ...@@ -181,15 +218,66 @@ abstract class Element<T extends Widget> {
return newChild; return newChild;
} }
static void flushBuild() {
_buildScheduler.buildDirtyElements();
}
} }
class _BuildScheduler {
final Set<BuildableElement> _dirtyElements = new Set<BuildableElement>();
bool _inBuildDirtyElements = false;
void schedule(BuildableElement element) {
if (_dirtyElements.isEmpty)
scheduler.ensureVisualUpdate();
_dirtyElements.add(element);
}
void _absorbDirtyElement(List<BuildableElement> list) {
list.addAll(_dirtyElements);
_dirtyElements.clear();
list.sort((BuildableElement a, BuildableElement b) => a._depth - b._depth);
}
void buildDirtyElements() {
if (_dirtyElements.isEmpty)
return;
_inBuildDirtyElements = true;
try {
while (!_dirtyElements.isEmpty) {
List<BuildableElement> sortedDirtyElements = new List<BuildableElement>();
_absorbDirtyElement(sortedDirtyElements);
int index = 0;
while (index < sortedDirtyElements.length) {
sortedDirtyElements[index]._rebuildIfNeeded();
if (!_dirtyElements.isEmpty) {
assert(_dirtyElements.every((Element element) => !sortedDirtyElements.contains(element)));
_absorbDirtyElement(sortedDirtyElements);
index = 0;
} else {
index += 1;
}
}
}
} finally {
_inBuildDirtyElements = false;
}
assert(_dirtyElements.isEmpty);
}
}
final _BuildScheduler _buildScheduler = new _BuildScheduler();
abstract class BuildableElement<T extends Widget> extends Element<T> { abstract class BuildableElement<T extends Widget> extends Element<T> {
BuildableElement(T widget) : super(widget); BuildableElement(T widget) : super(widget);
WidgetBuilder _builder; WidgetBuilder _builder;
Element _child; Element _child;
bool _dirty = true;
void _rebuild(dynamic slot) { void _rebuild() {
_dirty = false;
Widget built; Widget built;
try { try {
built = _builder(); built = _builder();
...@@ -197,7 +285,20 @@ abstract class BuildableElement<T extends Widget> extends Element<T> { ...@@ -197,7 +285,20 @@ abstract class BuildableElement<T extends Widget> extends Element<T> {
} catch (e, stack) { } catch (e, stack) {
_debugReportException('building $this', e, stack); _debugReportException('building $this', e, stack);
} }
_child = _updateChild(_child, built, slot); _child = _updateChild(_child, built, _slot);
}
void _rebuildIfNeeded() {
if (_dirty && _lifecycleState == _ElementLifecycle.mounted)
_rebuild();
}
void scheduleBuild() {
if (_dirty || _lifecycleState != _ElementLifecycle.mounted)
return;
_dirty = true;
_buildScheduler.schedule(this);
// TODO(abarth): Implement rebuilding.
} }
void visitChildren(ElementVisitor visitor) { void visitChildren(ElementVisitor visitor) {
...@@ -208,7 +309,7 @@ abstract class BuildableElement<T extends Widget> extends Element<T> { ...@@ -208,7 +309,7 @@ abstract class BuildableElement<T extends Widget> extends Element<T> {
void mount(dynamic slot) { void mount(dynamic slot) {
super.mount(slot); super.mount(slot);
assert(_child == null); assert(_child == null);
_rebuild(slot); _rebuild();
assert(_child != null); assert(_child != null);
} }
} }
...@@ -218,11 +319,11 @@ class ComponentElement extends BuildableElement<Component> { ...@@ -218,11 +319,11 @@ class ComponentElement extends BuildableElement<Component> {
_builder = component.build; _builder = component.build;
} }
void update(Component updated, dynamic slot) { void update(Component updated) {
super.update(updated, slot); super.update(updated);
assert(_widget == updated); assert(_widget == updated);
_builder = _widget.build; _builder = _widget.build;
_rebuild(slot); _rebuild();
} }
} }
...@@ -234,13 +335,14 @@ class ComponentStateElement extends BuildableElement<ComponentConfiguration> { ...@@ -234,13 +335,14 @@ class ComponentStateElement extends BuildableElement<ComponentConfiguration> {
_state.config = configuration; _state.config = configuration;
} }
ComponentState get state => _state;
ComponentState _state; ComponentState _state;
void update(ComponentConfiguration updated, dynamic slot) { void update(ComponentConfiguration updated) {
super.update(updated, slot); super.update(updated);
assert(_widget == updated); assert(_widget == updated);
_state.config = _widget; _state.config = _widget;
_rebuild(slot); _rebuild();
} }
void unmount() { void unmount() {
...@@ -248,10 +350,6 @@ class ComponentStateElement extends BuildableElement<ComponentConfiguration> { ...@@ -248,10 +350,6 @@ class ComponentStateElement extends BuildableElement<ComponentConfiguration> {
_state.didUnmount(); _state.didUnmount();
_state = null; _state = null;
} }
void scheduleBuild() {
// TODO(abarth): Implement rebuilding.
}
} }
RenderObjectElement _findAncestorRenderObjectElement(Element ancestor) { RenderObjectElement _findAncestorRenderObjectElement(Element ancestor) {
...@@ -275,8 +373,8 @@ class RenderObjectElement<T extends RenderObjectWidget> extends Element<T> { ...@@ -275,8 +373,8 @@ class RenderObjectElement<T extends RenderObjectWidget> extends Element<T> {
_ancestorRenderObjectElement.insertChildRenderObject(renderObject, slot); _ancestorRenderObjectElement.insertChildRenderObject(renderObject, slot);
} }
void update(T updated, dynamic slot) { void update(T updated) {
super.update(updated, slot); super.update(updated);
assert(_widget == updated); assert(_widget == updated);
_widget.updateRenderObject(renderObject); _widget.updateRenderObject(renderObject);
} }
...@@ -297,6 +395,8 @@ class RenderObjectElement<T extends RenderObjectWidget> extends Element<T> { ...@@ -297,6 +395,8 @@ class RenderObjectElement<T extends RenderObjectWidget> extends Element<T> {
} }
} }
final Object _uniqueChild = new Object();
class OneChildRenderObjectElement<T extends OneChildRenderObjectWidget> extends RenderObjectElement<T> { class OneChildRenderObjectElement<T extends OneChildRenderObjectWidget> extends RenderObjectElement<T> {
OneChildRenderObjectElement(T widget) : super(widget); OneChildRenderObjectElement(T widget) : super(widget);
...@@ -309,19 +409,19 @@ class OneChildRenderObjectElement<T extends OneChildRenderObjectWidget> extends ...@@ -309,19 +409,19 @@ class OneChildRenderObjectElement<T extends OneChildRenderObjectWidget> extends
void mount(dynamic slot) { void mount(dynamic slot) {
super.mount(slot); super.mount(slot);
_child = _updateChild(_child, _widget.child, null); _child = _updateChild(_child, _widget.child, _uniqueChild);
} }
void update(T updated, dynamic slot) { void update(T updated) {
super.update(updated, slot); super.update(updated);
assert(_widget == updated); assert(_widget == updated);
_child = _updateChild(_child, _widget.child, null); _child = _updateChild(_child, _widget.child, _uniqueChild);
} }
void insertChildRenderObject(RenderObject child, dynamic slot) { void insertChildRenderObject(RenderObject child, dynamic slot) {
final renderObject = this.renderObject; // TODO(ianh): Remove this once the analyzer is cleverer final renderObject = this.renderObject; // TODO(ianh): Remove this once the analyzer is cleverer
assert(renderObject is RenderObjectWithChildMixin); assert(renderObject is RenderObjectWithChildMixin);
assert(slot == null); assert(slot == _uniqueChild);
renderObject.child = child; renderObject.child = child;
assert(renderObject == this.renderObject); // TODO(ianh): Remove this once the analyzer is cleverer assert(renderObject == this.renderObject); // TODO(ianh): Remove this once the analyzer is cleverer
} }
......
...@@ -869,7 +869,7 @@ abstract class Component extends Widget { ...@@ -869,7 +869,7 @@ abstract class Component extends Widget {
if (_dirty || !_mounted) if (_dirty || !_mounted)
return; return;
_dirty = true; _dirty = true;
_scheduleComponentForRender(this); _scheduleComponentForBuild(this);
} }
static void flushBuild() { static void flushBuild() {
...@@ -1051,7 +1051,7 @@ void _endSyncPhase() { ...@@ -1051,7 +1051,7 @@ void _endSyncPhase() {
} }
// TODO(ianh): Move this to Component // TODO(ianh): Move this to Component
void _scheduleComponentForRender(Component component) { void _scheduleComponentForBuild(Component component) {
_dirtyComponents.add(component); _dirtyComponents.add(component);
if (!_buildScheduled) { if (!_buildScheduled) {
_buildScheduled = true; _buildScheduled = true;
...@@ -1559,7 +1559,7 @@ abstract class AbstractWidgetRoot extends StatefulComponent { ...@@ -1559,7 +1559,7 @@ abstract class AbstractWidgetRoot extends StatefulComponent {
AbstractWidgetRoot() { AbstractWidgetRoot() {
_mounted = true; _mounted = true;
_scheduleComponentForRender(this); _scheduleComponentForBuild(this);
} }
void syncConstructorArguments(AbstractWidgetRoot source) { void syncConstructorArguments(AbstractWidgetRoot source) {
......
import 'package:sky/rendering.dart';
import 'package:sky/src/fn3.dart';
import 'package:test/test.dart';
import 'widget_tester.dart';
class TestComponentConfig extends ComponentConfiguration {
TestComponentConfig({ this.left, this.right });
final Widget left;
final Widget right;
TestComponentState createState() => new TestComponentState();
}
class TestComponentState extends ComponentState {
TestComponentConfig get config => super.config;
bool _showLeft = true;
void flip() {
setState(() {
_showLeft = !_showLeft;
});
}
Widget build() {
return _showLeft ? config.left : config.right;
}
}
final BoxDecoration kBoxDecorationA = new BoxDecoration();
final BoxDecoration kBoxDecorationB = new BoxDecoration();
void main() {
test('Stateful component smoke test', () {
WidgetTester tester = new WidgetTester();
void checkTree(BoxDecoration expectedDecoration) {
OneChildRenderObjectElement element =
tester.findElement((element) => element is OneChildRenderObjectElement);
expect(element, isNotNull);
expect(element.renderObject is RenderDecoratedBox, isTrue);
RenderDecoratedBox renderObject = element.renderObject;
expect(renderObject.decoration, equals(expectedDecoration));
}
tester.pumpFrame(
new TestComponentConfig(
left: new DecoratedBox(decoration: kBoxDecorationA),
right: new DecoratedBox(decoration: kBoxDecorationB)
)
);
checkTree(kBoxDecorationA);
tester.pumpFrame(
new TestComponentConfig(
left: new DecoratedBox(decoration: kBoxDecorationB),
right: new DecoratedBox(decoration: kBoxDecorationA)
)
);
checkTree(kBoxDecorationB);
ComponentStateElement stateElement =
tester.findElement((element) => element is ComponentStateElement);
(stateElement.state as TestComponentState).flip();
Element.flushBuild();
checkTree(kBoxDecorationA);
tester.pumpFrame(
new TestComponentConfig(
left: new DecoratedBox(decoration: kBoxDecorationA),
right: new DecoratedBox(decoration: kBoxDecorationB)
)
);
checkTree(kBoxDecorationB);
});
}
...@@ -6,6 +6,8 @@ class TestComponent extends Component { ...@@ -6,6 +6,8 @@ class TestComponent extends Component {
Widget build() => child; Widget build() => child;
} }
final Object _rootSlot = new Object();
class WidgetTester { class WidgetTester {
ComponentElement _rootElement; ComponentElement _rootElement;
...@@ -35,9 +37,9 @@ class WidgetTester { ...@@ -35,9 +37,9 @@ class WidgetTester {
void pumpFrame(Widget widget) { void pumpFrame(Widget widget) {
if (_rootElement == null) { if (_rootElement == null) {
_rootElement = new ComponentElement(new TestComponent(child: widget)); _rootElement = new ComponentElement(new TestComponent(child: widget));
_rootElement.mount(null); _rootElement.mount(_rootSlot);
} else { } else {
_rootElement.update(new TestComponent(child: widget), null); _rootElement.update(new TestComponent(child: widget));
} }
} }
......
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