flutter/examples/fn/lib/component.dart
Rafael Weinstein 202f99d71d Initial commit of Effen reactive framework experiment for Sky
This is just a proof of concept. If we like this direction, it will move out of the examples directory (likely re-written) and be committed in smaller pieces with unit tests and formal reviews.

TBR=abarth
BUG=

Review URL: https://codereview.chromium.org/971183002
2015-03-02 20:55:02 -08:00

159 lines
3.9 KiB
Dart

part of fn;
List<Component> _dirtyComponents = new List<Component>();
bool _renderScheduled = false;
void _renderDirtyComponents() {
Stopwatch sw = new Stopwatch()..start();
_dirtyComponents.sort((a, b) => a._order - b._order);
for (var comp in _dirtyComponents) {
comp._renderIfDirty();
}
_dirtyComponents.clear();
_renderScheduled = false;
sw.stop();
print("Render took ${sw.elapsedMicroseconds} microseconds");
}
void _scheduleComponentForRender(Component c) {
_dirtyComponents.add(c);
if (!_renderScheduled) {
_renderScheduled = true;
new Future.microtask(_renderDirtyComponents);
}
}
abstract class Component extends Node {
bool _dirty = true; // components begin dirty because they haven't rendered.
Node _rendered = null;
bool _removed = false;
int _order;
static int _currentOrder = 0;
bool _stateful;
static Component _currentlyRendering;
Component({ Object key, bool stateful })
: _stateful = stateful != null ? stateful : false,
_order = _currentOrder + 1,
super(key:key);
void willUnmount() {}
void _remove() {
assert(_rendered != null);
assert(_root != null);
willUnmount();
_rendered._remove();
_rendered = null;
_root = null;
_removed = true;
}
// TODO(rafaelw): It seems wrong to expose DOM at all. This is presently
// needed to get sizing info.
sky.Node getRoot() => _root;
bool _sync(Node old, sky.Node host, sky.Node insertBefore) {
Component oldComponent = old as Component;
if (oldComponent == null || oldComponent == this) {
_renderInternal(host, insertBefore);
return false;
}
assert(oldComponent != null);
assert(_dirty);
assert(_rendered == null);
if (oldComponent._stateful) {
_stateful = false; // prevent iloop from _renderInternal below.
reflect.copyPublicFields(this, oldComponent);
oldComponent._dirty = true;
_dirty = false;
oldComponent._renderInternal(host, insertBefore);
return true; // Must retain old component
}
_rendered = oldComponent._rendered;
_renderInternal(host, insertBefore);
return false;
}
void _renderInternal(sky.Node host, sky.Node insertBefore) {
if (!_dirty) {
assert(_rendered != null);
return;
}
var oldRendered = _rendered;
int lastOrder = _currentOrder;
_currentOrder = _order;
_currentlyRendering = this;
_rendered = render();
_currentlyRendering = null;
_currentOrder = lastOrder;
_dirty = false;
// TODO(rafaelw): This prevents components from returning different node
// types as their root node at different times. Consider relaxing.
assert(oldRendered == null ||
_rendered.runtimeType == oldRendered.runtimeType);
if (_rendered._sync(oldRendered, host, insertBefore)) {
_rendered = oldRendered; // retain stateful component
}
_root = _rendered._root;
assert(_rendered._root is sky.Node);
}
void _renderIfDirty() {
assert(_rendered != null);
assert(!_removed);
var rendered = _rendered;
while (rendered is Component) {
rendered = rendered._rendered;
}
sky.Node root = rendered._root;
_renderInternal(root.parentNode, root.nextSibling);
}
void setState(Function fn()) {
assert(_rendered != null); // cannot setState before mounting.
_stateful = true;
fn();
if (_currentlyRendering != this) {
_dirty = true;
_scheduleComponentForRender(this);
}
}
Node render();
}
abstract class App extends Component {
sky.Node _host = null;
App()
: super(stateful: true) {
_host = sky.document.createElement('div');
sky.document.appendChild(_host);
new Future.microtask(() {
Stopwatch sw = new Stopwatch()..start();
_sync(null, _host, null);
assert(_root is sky.Node);
sw.stop();
print("Initial render: ${sw.elapsedMicroseconds} microseconds");
});
}
}