Skip to content

Commit

Permalink
Merge pull request flutter#1254 from abarth/fn3-state
Browse files Browse the repository at this point in the history
Make stateful components work in fn3
  • Loading branch information
abarth committed Sep 21, 2015
2 parents 785769d + 496e0b4 commit 61a92c9
Show file tree
Hide file tree
Showing 4 changed files with 215 additions and 28 deletions.
146 changes: 123 additions & 23 deletions sky/packages/sky/lib/src/fn3/framework.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:sky/animation.dart';
import 'package:sky/rendering.dart';

abstract class Key {
Expand Down Expand Up @@ -107,6 +108,9 @@ abstract class Element<T extends Widget> {
}

Element _parent;
dynamic _slot;
int _depth;

T _widget;

_ElementLifecycle _lifecycleState = _ElementLifecycle.initial;
Expand All @@ -123,28 +127,61 @@ abstract class Element<T extends Widget> {

void mount(dynamic slot) {
assert(_lifecycleState == _ElementLifecycle.initial);
assert(_parent == null || _parent._lifecycleState == _ElementLifecycle.mounted);
assert(slot != null);
assert(_widget != null);
assert(_depth == null);
_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(_parent != null);
assert(_parent._lifecycleState == _ElementLifecycle.mounted);
assert(_widget != null);
assert(_depth == null);
_slot = slot;
}

void update(T updated) {
assert(updated != null);
assert(_lifecycleState == _ElementLifecycle.mounted);
assert(_widget != null);
assert(_depth != null);
assert(_canUpdate(_widget, updated));
_widget = updated;
}

void unmount() {
assert(_lifecycleState == _ElementLifecycle.mounted);
assert(_widget != null);
assert(_depth != null);
_slot = null;
_depth = null;
_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) {
if (child == null)
return;
assert(child._parent == this);
child._parent = null;

bool haveDetachedRenderObject = false;
Expand All @@ -168,7 +205,7 @@ abstract class Element<T extends Widget> {

if (child != null) {
if (_canUpdate(child._widget, updated)) {
child.update(updated, slot);
child.update(updated);
return child;
}
_detachChild(child);
Expand All @@ -181,23 +218,87 @@ abstract class Element<T extends Widget> {
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> {
BuildableElement(T widget) : super(widget);

WidgetBuilder _builder;
Element _child;
bool _dirty = true;

void _rebuild(dynamic slot) {
void _rebuild() {
_dirty = false;
Widget built;
try {
built = _builder();
assert(built != null);
} catch (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) {
Expand All @@ -208,7 +309,7 @@ abstract class BuildableElement<T extends Widget> extends Element<T> {
void mount(dynamic slot) {
super.mount(slot);
assert(_child == null);
_rebuild(slot);
_rebuild();
assert(_child != null);
}
}
Expand All @@ -218,11 +319,11 @@ class ComponentElement extends BuildableElement<Component> {
_builder = component.build;
}

void update(Component updated, dynamic slot) {
super.update(updated, slot);
void update(Component updated) {
super.update(updated);
assert(_widget == updated);
_builder = _widget.build;
_rebuild(slot);
_rebuild();
}
}

Expand All @@ -234,24 +335,21 @@ class ComponentStateElement extends BuildableElement<ComponentConfiguration> {
_state.config = configuration;
}

ComponentState get state => _state;
ComponentState _state;

void update(ComponentConfiguration updated, dynamic slot) {
super.update(updated, slot);
void update(ComponentConfiguration updated) {
super.update(updated);
assert(_widget == updated);
_state.config = _widget;
_rebuild(slot);
_rebuild();
}

void unmount() {
super.unmount();
_state.didUnmount();
_state = null;
}

void scheduleBuild() {
// TODO(abarth): Implement rebuilding.
}
}

RenderObjectElement _findAncestorRenderObjectElement(Element ancestor) {
Expand All @@ -275,8 +373,8 @@ class RenderObjectElement<T extends RenderObjectWidget> extends Element<T> {
_ancestorRenderObjectElement.insertChildRenderObject(renderObject, slot);
}

void update(T updated, dynamic slot) {
super.update(updated, slot);
void update(T updated) {
super.update(updated);
assert(_widget == updated);
_widget.updateRenderObject(renderObject);
}
Expand All @@ -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> {
OneChildRenderObjectElement(T widget) : super(widget);

Expand All @@ -309,19 +409,19 @@ class OneChildRenderObjectElement<T extends OneChildRenderObjectWidget> extends

void mount(dynamic slot) {
super.mount(slot);
_child = _updateChild(_child, _widget.child, null);
_child = _updateChild(_child, _widget.child, _uniqueChild);
}

void update(T updated, dynamic slot) {
super.update(updated, slot);
void update(T updated) {
super.update(updated);
assert(_widget == updated);
_child = _updateChild(_child, _widget.child, null);
_child = _updateChild(_child, _widget.child, _uniqueChild);
}

void insertChildRenderObject(RenderObject child, dynamic slot) {
final renderObject = this.renderObject; // TODO(ianh): Remove this once the analyzer is cleverer
assert(renderObject is RenderObjectWithChildMixin);
assert(slot == null);
assert(slot == _uniqueChild);
renderObject.child = child;
assert(renderObject == this.renderObject); // TODO(ianh): Remove this once the analyzer is cleverer
}
Expand Down
6 changes: 3 additions & 3 deletions sky/packages/sky/lib/src/widgets/framework.dart
Original file line number Diff line number Diff line change
Expand Up @@ -869,7 +869,7 @@ abstract class Component extends Widget {
if (_dirty || !_mounted)
return;
_dirty = true;
_scheduleComponentForRender(this);
_scheduleComponentForBuild(this);
}

static void flushBuild() {
Expand Down Expand Up @@ -1051,7 +1051,7 @@ void _endSyncPhase() {
}

// TODO(ianh): Move this to Component
void _scheduleComponentForRender(Component component) {
void _scheduleComponentForBuild(Component component) {
_dirtyComponents.add(component);
if (!_buildScheduled) {
_buildScheduled = true;
Expand Down Expand Up @@ -1559,7 +1559,7 @@ abstract class AbstractWidgetRoot extends StatefulComponent {

AbstractWidgetRoot() {
_mounted = true;
_scheduleComponentForRender(this);
_scheduleComponentForBuild(this);
}

void syncConstructorArguments(AbstractWidgetRoot source) {
Expand Down
85 changes: 85 additions & 0 deletions sky/unit/test/fn3/stateful_component_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
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);

});

}
Loading

0 comments on commit 61a92c9

Please sign in to comment.