-
Notifications
You must be signed in to change notification settings - Fork 13
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
profile to see performance bottleneck in rendering #191
Comments
Caching Regarding the second idea to cache built objects so that they are not duplicated, here's some thoughts. We don't want to cache them in the normal sense of producing a new object and then seeing if it is in the cache, and using the cached copy if it is. This requires building a new object and checking deeply for equality to see if it is in the cache, and only then do the savings come in. This could be as costly as simply keeping the new object and waiting for it to get compared deeply for equality with another object. But I have an idea for a heuristic that could work in most cases, particularly for built objects used in Redux. I asked a question here in case someone else has tackled this problem: https://stackoverflow.com/questions/61514081/builder-pattern-that-avoids-creating-duplicate-equivalent-immutable-objects We could implement this in a fork of built_value. If it is an optional thing that is off by default, the author might even want to incorporate our change. The following description is easier to follow by opening up a generated file, such as state/strand.g.dart, and looking at the implementation of Currently, builders created from a built object (such as when calling reducers to update the Redux store) maintain a reference called There is a getter The method The idea is that setting The problem is that this is overly aggressive in setting the dirty bit. Obviously write access should make the builder dirty. But read access in principle is safe if nothing changes. Do you might think we should check whether write access to a field occurs, and if not, then re-use the existing built object, even if read access to a field occurred. But the problem with that logic is that builders can contain nested builders. It is possible to modify a nested builder, but this could not be detected by the parent builder since only read access to the nested builder is used to access it. A concrete example: builder = built.toBuilder();
nested = builder.nestedBuilder;
nested.field = "abc";
built = builder.build();
More generally, built objects can even contain non-immutable values such as Lists, although this is not good practice. However, I think the following heuristic should work in our case and many others, which would expand the situations in which it is provably safe to return In the scadnano library, all built objects contain only two types of objects: Dart-provided immutable values such as Strings and ints, and built objects (including built collections). We could re-write the Then, in the When For each immutable field (types For each nested builder Recursively, this should work, since the nested builders will all be doing the same check. So hopefully, no new objects get allocated unless a value actually changes during the lifetime of the builder object. Note that we check |
This happens when class StrandBuilder implements Builder<Strand, StrandBuilder> {
_$Strand _$v;
ListBuilder<Substrand> _substrands;
ListBuilder<Substrand> get substrands =>
_$this._substrands ??= new ListBuilder<Substrand>();
set substrands(ListBuilder<Substrand> substrands) =>
_$this._substrands = substrands;
BuiltList<Substrand> _substrands_built // new field, set in `build()`
// -- snip --
} |
It would be a local variable, not a field, since it's only needed during In the concrete case of add a field class StrandBuilder implements Builder<Strand, StrandBuilder> {
bool _accessed = false; change StrandBuilder get _$this {
if (!_accessed) {
_substrands = _$v.substrands?.toBuilder();
_dna_sequence = _$v.dna_sequence;
_idt = _$v.idt?.toBuilder();
_is_scaffold = _$v.is_scaffold;
_modification_5p = _$v.modification_5p?.toBuilder();
_modification_3p = _$v.modification_3p?.toBuilder();
_modifications_int = _$v.modifications_int?.toBuilder();
_color = _$v.color;
_unused_fields = _$v.unused_fields?.toBuilder();
_accessed = true;
}
return this;
} and change @override
_$Strand build() {
Strand._finalizeBuilder(this);
_$Strand _$result;
try {
if (!_accessed) {
_$result = _$v;
} else {
BuiltList<Strand> new_substrands = substrands.build();
IDTFields new_idt = _idt?.build();
Modification5Prime new_modification_5p = _modification_5p?.build();
Modification3Prime new_modification_3p = _modification_3p?.build();
BuiltList<ModificationInternal> new_modification3_int = modifications_int.build();
BuiltMap<String, Object> unused_fields = unused_fields.build();
bool nothing_changed = identical(new_substrands, _$v.substrands)
&& identical(new_idt, _$v._idt)
&& identical(new_modification_5p, _$v._modification_5p)
&& identical(new_modification_3p, _$v._modification_3p)
&& identical(new_modifications_int, _$v._modifications_int)
&& identical(new_unused_fields, _$v.unused_fields)
&& dna_sequence == _$v.dna_sequence
&& is_scaffold == _$v.is_scaffold
&& color == _$v.color;
if (nothing_changed) {
// Only case where the behavior would be different in the new implementation
_$result = _$v;
} else {
_$result = new _$Strand._(
substrands: new_substrands,
dna_sequence: dna_sequence,
idt: new_idt,
is_scaffold: is_scaffold,
modification_5p: new_modification_5p,
modification_3p: new_modification_3p,
modifications_int: new_modifications_int,
color: color,
unused_fields: unused_fields);
}
}
} catch (_) {
String _$failedField;
try {
_$failedField = 'substrands';
substrands.build();
_$failedField = 'idt';
_idt?.build();
_$failedField = 'modification_5p';
_modification_5p?.build();
_$failedField = 'modification_3p';
_modification_3p?.build();
_$failedField = 'modifications_int';
modifications_int.build();
_$failedField = 'unused_fields';
unused_fields.build();
} catch (e) {
throw new BuiltValueNestedFieldError(
'Strand', _$failedField, e.toString());
}
rethrow;
}
replace(_$result);
return _$result;
} Just to be clear, the rule in setting I've never used I inspected the Some other things would probably have to change, which I haven't thought much about, for instance, this is probably what @override
void replace(Strand other) {
if (other == null) {
throw new ArgumentError.notNull('other');
}
_$v = other as _$Strand;
_accessed = false;
} |
One thing I haven't studied yet is the built collections. For instance, here's BuiltList<E> build() {
if (_listOwner == null) {
_setOwner(_BuiltList<E>.withSafeList(_list));
}
return _listOwner;
} Hopefully, if nothing changed, then |
We might also have to be careful to ensure that this optimization is actually helping us in our case. It will all be for nothing if some reducer always produces a new built object. For instance, if static void _finalizeBuilder(StrandBuilder builder) {
BoundSubstrand first_ss = builder.substrands.first;
String id = id_from_data(first_ss.helix, first_ss.offset_5p, first_ss.forward);
// ensure Loopouts have appropriate prev and next indices for adjacent BoundSubstrands
// (not necessary for Crossovers since they are lazily evaluated,
// but Loopout objects are created prior to creating the Strand)
for (int i = 0; i < builder.substrands.length; i++) {
var substrand = builder.substrands[i];
if (substrand is Loopout) {
if (substrand.prev_substrand_idx != i - 1 || substrand.next_substrand_idx != i + 1) {
var loopout = substrand.rebuild((b) => b
..prev_substrand_idx = i - 1
..next_substrand_idx = i + 1);
builder.substrands[i] = loopout;
}
}
// set strand_id on all substrands
if (substrand is BoundSubstrand) {
builder.substrands[i] = substrand.rebuild((b) => b..strand_id = id);
} else if (substrand is Loopout) {
builder.substrands[i] = substrand.rebuild((b) => b..strand_id = id);
}
}
} Inspecting that, I think that in the case that no data about the strand has changed, then it won't make any modifications that will cause the new implementation of But I wouldn't trust that until we had actually run it through a debugger to make sure. This sort of thing would be good to check with unit tests that check whether |
Don't reducers have to produce a new built object whenever the object needs to be modify? Are you saying that some objects are needlessly rebuilt even when they do not need to be? Based on your explanation regarding I do not understand the cause of the performance issue actually, are you saying that when one strand is changed, all of the other strands in design will be rebuilt so that |
Yes, but I'm worried that sometimes they produce a new object even when the object's value isn't modified. (Meaning that it is
I'm saying I don't know, and we need to find out. It's based on guessing at what could be causing all the action I see when I run the Google Chrome profiler. For instance, load a large design with hundreds of strands (such as one of the 16-helix rectangle eaxmples), and try nicking a strand. Turn on the profiler and then nick the strand. Here's what I see: That's almost a full second of processing in that part on the right. Anyway, first thing to test is which objects are But, what makes me suspect that many new objects are being allocated is lines like this (https://github.com/UC-Davis-molecular-computing/scadnano/blob/master/lib/src/reducers/dna_design_reducer.dart#L42) DNADesign dna_design_composed_local_reducer(DNADesign dna_design, action) => dna_design?.rebuild((d) => d
..grid = TypedReducer<Grid, actions.GridChange>(grid_local_reducer)(dna_design.grid, action)
..helices.replace(helices_local_reducer(dna_design.helices, action))
..strands.replace(strands_local_reducer(dna_design.strands, action))); Supposing that none of the Anyway, I could be wrong. The long comment I wrote above was just an educated guess at how to approach things. The real thing we need to do here is what the top post says: dig into the profile data and figure out why putting a single nick in a single strand takes a full second of processing power. But, I would also be curious to know, if you modify a single strand, clearly it has to change that strand, and it has to change the |
I implemented this idea. Unfortunately, I did it just a few hours before built_value began a major upgrade that is difficult to merge, so right now I can't point to a fork of built_value implementing the idea. But the short answer is that it made no difference in performance according to the Chrome profiler. The performance issues seem to be elsewhere. I did note that on a very large design, a lot of time was spent building the set of mismatches, and a nontrivial amount of time was spent writing the DNADesign JSON to localStorage, so I added notes to the README to disable those for performance reasons, and added an issue #348 to allow disabling of writing the design to localStorage. A few notes about implementation:
Below I've pasted a concrete example of a small Builder, and a larger example of the builder for |
smaller test classes: abstract class Parent with BuiltJsonSerializable implements Built<Parent, ParentBuilder> {
int get field_int;
Child get child;
BuiltList<Child> get children;
/************************ begin BuiltValue boilerplate ************************/
factory Parent({int field_int, Child child, BuiltList<Child> children}) = _$Parent._;
Parent._();
static Serializer<Parent> get serializer => _$parentSerializer;
@memoized
int get hashCode;
}
abstract class Child with BuiltJsonSerializable implements Built<Child, ChildBuilder> {
String get field_str;
/************************ begin BuiltValue boilerplate ************************/
factory Child({String field_str}) = _$Child._;
Child._();
static Serializer<Child> get serializer => _$childSerializer;
@memoized
int get hashCode;
} Here's their generated code: class _$Parent extends Parent {
@override
final int field_int;
@override
final Child child;
@override
final BuiltList<Child> children;
factory _$Parent([void Function(ParentBuilder) updates]) =>
(new ParentBuilder()..update(updates)).build();
_$Parent._({this.field_int, this.child, this.children}) : super._() {
if (field_int == null) {
throw new BuiltValueNullFieldError('Parent', 'field_int');
}
if (child == null) {
throw new BuiltValueNullFieldError('Parent', 'child');
}
if (children == null) {
throw new BuiltValueNullFieldError('Parent', 'children');
}
}
@override
Parent rebuild(void Function(ParentBuilder) updates) =>
(toBuilder()..update(updates)).build();
@override
ParentBuilder toBuilder() => new ParentBuilder()..replace(this);
@override
bool operator ==(Object other) {
if (identical(other, this)) return true;
return other is Parent &&
field_int == other.field_int &&
child == other.child &&
children == other.children;
}
int __hashCode;
@override
int get hashCode {
return __hashCode ??= $jf($jc(
$jc($jc(0, field_int.hashCode), child.hashCode), children.hashCode));
}
@override
String toString() {
return (newBuiltValueToStringHelper('Parent')
..add('field_int', field_int)
..add('child', child)
..add('children', children))
.toString();
}
}
class ParentBuilder implements Builder<Parent, ParentBuilder> {
_$Parent _$v;
int _field_int;
int get field_int => _$this._field_int;
set field_int(int field_int) => _$this._field_int = field_int;
ChildBuilder _child;
ChildBuilder get child => _$this._child ??= new ChildBuilder();
set child(ChildBuilder child) => _$this._child = child;
ListBuilder<Child> _children;
ListBuilder<Child> get children =>
_$this._children ??= new ListBuilder<Child>();
set children(ListBuilder<Child> children) => _$this._children = children;
ParentBuilder();
bool _accessed = false;
ParentBuilder get _$this {
if (!_accessed) {
_accessed = true;
if (_$v != null) {
_field_int = _$v.field_int;
_child = _$v.child?.toBuilder();
_children = _$v.children?.toBuilder();
}
}
return this;
}
@override
void replace(Parent other) {
if (other == null) {
throw new ArgumentError.notNull('other');
}
_$v = other as _$Parent;
}
@override
void update(void Function(ParentBuilder) updates) {
if (updates != null) updates(this);
}
@override
_$Parent build() {
_$Parent _$result;
try {
_$result = _$v;
if (_$result == null) {
_$result = new _$Parent._(
field_int: field_int,
child: child.build(),
children: children.build());
}
if (_$v != null && _accessed) {
var new_field_int = field_int;
var new_child = child.build();
var new_children = children.build();
bool changed = !identical(new_field_int, _$v.field_int) ||
!identical(new_child, _$v.child) ||
!identical(new_children, _$v.children);
if (changed) {
_$result = new _$Parent._(
field_int: new_field_int,
child: new_child,
children: new_children);
}
}
} catch (_) {
String _$failedField;
try {
_$failedField = 'child';
child.build();
_$failedField = 'children';
children.build();
} catch (e) {
throw new BuiltValueNestedFieldError(
'Parent', _$failedField, e.toString());
}
rethrow;
}
replace(_$result);
return _$result;
}
}
class _$Child extends Child {
@override
final String field_str;
factory _$Child([void Function(ChildBuilder) updates]) =>
(new ChildBuilder()..update(updates)).build();
_$Child._({this.field_str}) : super._() {
if (field_str == null) {
throw new BuiltValueNullFieldError('Child', 'field_str');
}
}
@override
Child rebuild(void Function(ChildBuilder) updates) =>
(toBuilder()..update(updates)).build();
@override
ChildBuilder toBuilder() => new ChildBuilder()..replace(this);
@override
bool operator ==(Object other) {
if (identical(other, this)) return true;
return other is Child && field_str == other.field_str;
}
int __hashCode;
@override
int get hashCode {
return __hashCode ??= $jf($jc(0, field_str.hashCode));
}
@override
String toString() {
return (newBuiltValueToStringHelper('Child')..add('field_str', field_str))
.toString();
}
}
class ChildBuilder implements Builder<Child, ChildBuilder> {
_$Child _$v;
String _field_str;
String get field_str => _$this._field_str;
set field_str(String field_str) => _$this._field_str = field_str;
ChildBuilder();
bool _accessed = false;
ChildBuilder get _$this {
if (!_accessed) {
_accessed = true;
if (_$v != null) {
_field_str = _$v.field_str;
}
}
return this;
}
@override
void replace(Child other) {
if (other == null) {
throw new ArgumentError.notNull('other');
}
_$v = other as _$Child;
}
@override
void update(void Function(ChildBuilder) updates) {
if (updates != null) updates(this);
}
@override
_$Child build() {
_$Child _$result;
_$result = _$v;
if (_$result == null) {
_$result = new _$Child._(field_str: field_str);
}
if (_$v != null && _accessed) {
var new_field_str = field_str;
bool changed = !identical(new_field_str, _$v.field_str);
if (changed) {
_$result = new _$Child._(field_str: new_field_str);
}
}
replace(_$result);
return _$result;
}
} |
AppUIState: class _$AppUIState extends AppUIState {
@override
final SelectablesStore selectables_store;
@override
final BuiltSet<int> side_selected_helix_idxs;
@override
final StrandsMove strands_move;
@override
final bool drawing_potential_crossover;
@override
final bool moving_dna_ends;
@override
final bool selection_box_displayed_main;
@override
final bool selection_box_displayed_side;
@override
final bool assign_complement_to_bound_strands_default;
@override
final bool warn_on_change_strand_dna_assign_default;
@override
final bool helix_change_apply_to_all;
@override
final BuiltList<MouseoverData> mouseover_datas;
@override
final ExampleDNADesigns example_dna_designs;
@override
final Dialog dialog;
@override
final StrandCreation strand_creation;
@override
final GridPosition side_view_grid_position_mouse_cursor;
@override
final Point<num> side_view_position_mouse_cursor;
@override
final ContextMenu context_menu;
@override
final bool changed_since_last_save;
@override
final String dna_sequence_png_uri;
@override
final Action disable_png_cache_until_action_completes;
@override
final bool is_zoom_above_threshold;
@override
final AppUIStateStorable storables;
factory _$AppUIState([void Function(AppUIStateBuilder) updates]) =>
(new AppUIStateBuilder()..update(updates)).build();
_$AppUIState._(
{this.selectables_store,
this.side_selected_helix_idxs,
this.strands_move,
this.drawing_potential_crossover,
this.moving_dna_ends,
this.selection_box_displayed_main,
this.selection_box_displayed_side,
this.assign_complement_to_bound_strands_default,
this.warn_on_change_strand_dna_assign_default,
this.helix_change_apply_to_all,
this.mouseover_datas,
this.example_dna_designs,
this.dialog,
this.strand_creation,
this.side_view_grid_position_mouse_cursor,
this.side_view_position_mouse_cursor,
this.context_menu,
this.changed_since_last_save,
this.dna_sequence_png_uri,
this.disable_png_cache_until_action_completes,
this.is_zoom_above_threshold,
this.storables})
: super._() {
if (selectables_store == null) {
throw new BuiltValueNullFieldError('AppUIState', 'selectables_store');
}
if (side_selected_helix_idxs == null) {
throw new BuiltValueNullFieldError(
'AppUIState', 'side_selected_helix_idxs');
}
if (drawing_potential_crossover == null) {
throw new BuiltValueNullFieldError(
'AppUIState', 'drawing_potential_crossover');
}
if (moving_dna_ends == null) {
throw new BuiltValueNullFieldError('AppUIState', 'moving_dna_ends');
}
if (selection_box_displayed_main == null) {
throw new BuiltValueNullFieldError(
'AppUIState', 'selection_box_displayed_main');
}
if (selection_box_displayed_side == null) {
throw new BuiltValueNullFieldError(
'AppUIState', 'selection_box_displayed_side');
}
if (assign_complement_to_bound_strands_default == null) {
throw new BuiltValueNullFieldError(
'AppUIState', 'assign_complement_to_bound_strands_default');
}
if (warn_on_change_strand_dna_assign_default == null) {
throw new BuiltValueNullFieldError(
'AppUIState', 'warn_on_change_strand_dna_assign_default');
}
if (helix_change_apply_to_all == null) {
throw new BuiltValueNullFieldError(
'AppUIState', 'helix_change_apply_to_all');
}
if (mouseover_datas == null) {
throw new BuiltValueNullFieldError('AppUIState', 'mouseover_datas');
}
if (example_dna_designs == null) {
throw new BuiltValueNullFieldError('AppUIState', 'example_dna_designs');
}
if (changed_since_last_save == null) {
throw new BuiltValueNullFieldError(
'AppUIState', 'changed_since_last_save');
}
if (is_zoom_above_threshold == null) {
throw new BuiltValueNullFieldError(
'AppUIState', 'is_zoom_above_threshold');
}
if (storables == null) {
throw new BuiltValueNullFieldError('AppUIState', 'storables');
}
}
@override
AppUIState rebuild(void Function(AppUIStateBuilder) updates) =>
(toBuilder()..update(updates)).build();
@override
AppUIStateBuilder toBuilder() => new AppUIStateBuilder()..replace(this);
@override
bool operator ==(Object other) {
if (identical(other, this)) return true;
return other is AppUIState &&
selectables_store == other.selectables_store &&
side_selected_helix_idxs == other.side_selected_helix_idxs &&
strands_move == other.strands_move &&
drawing_potential_crossover == other.drawing_potential_crossover &&
moving_dna_ends == other.moving_dna_ends &&
selection_box_displayed_main == other.selection_box_displayed_main &&
selection_box_displayed_side == other.selection_box_displayed_side &&
assign_complement_to_bound_strands_default ==
other.assign_complement_to_bound_strands_default &&
warn_on_change_strand_dna_assign_default ==
other.warn_on_change_strand_dna_assign_default &&
helix_change_apply_to_all == other.helix_change_apply_to_all &&
mouseover_datas == other.mouseover_datas &&
example_dna_designs == other.example_dna_designs &&
dialog == other.dialog &&
strand_creation == other.strand_creation &&
side_view_grid_position_mouse_cursor ==
other.side_view_grid_position_mouse_cursor &&
side_view_position_mouse_cursor ==
other.side_view_position_mouse_cursor &&
context_menu == other.context_menu &&
changed_since_last_save == other.changed_since_last_save &&
dna_sequence_png_uri == other.dna_sequence_png_uri &&
disable_png_cache_until_action_completes ==
other.disable_png_cache_until_action_completes &&
is_zoom_above_threshold == other.is_zoom_above_threshold &&
storables == other.storables;
}
int __hashCode;
@override
int get hashCode {
return __hashCode ??= $jf($jc(
$jc(
$jc(
$jc(
$jc(
$jc(
$jc(
$jc(
$jc(
$jc(
$jc(
$jc(
$jc(
$jc(
$jc(
$jc(
$jc(
$jc(
$jc($jc($jc($jc(0, selectables_store.hashCode), side_selected_helix_idxs.hashCode), strands_move.hashCode),
drawing_potential_crossover.hashCode),
moving_dna_ends.hashCode),
selection_box_displayed_main.hashCode),
selection_box_displayed_side.hashCode),
assign_complement_to_bound_strands_default.hashCode),
warn_on_change_strand_dna_assign_default.hashCode),
helix_change_apply_to_all.hashCode),
mouseover_datas.hashCode),
example_dna_designs.hashCode),
dialog.hashCode),
strand_creation.hashCode),
side_view_grid_position_mouse_cursor.hashCode),
side_view_position_mouse_cursor.hashCode),
context_menu.hashCode),
changed_since_last_save.hashCode),
dna_sequence_png_uri.hashCode),
disable_png_cache_until_action_completes.hashCode),
is_zoom_above_threshold.hashCode),
storables.hashCode));
}
@override
String toString() {
return (newBuiltValueToStringHelper('AppUIState')
..add('selectables_store', selectables_store)
..add('side_selected_helix_idxs', side_selected_helix_idxs)
..add('strands_move', strands_move)
..add('drawing_potential_crossover', drawing_potential_crossover)
..add('moving_dna_ends', moving_dna_ends)
..add('selection_box_displayed_main', selection_box_displayed_main)
..add('selection_box_displayed_side', selection_box_displayed_side)
..add('assign_complement_to_bound_strands_default',
assign_complement_to_bound_strands_default)
..add('warn_on_change_strand_dna_assign_default',
warn_on_change_strand_dna_assign_default)
..add('helix_change_apply_to_all', helix_change_apply_to_all)
..add('mouseover_datas', mouseover_datas)
..add('example_dna_designs', example_dna_designs)
..add('dialog', dialog)
..add('strand_creation', strand_creation)
..add('side_view_grid_position_mouse_cursor',
side_view_grid_position_mouse_cursor)
..add('side_view_position_mouse_cursor',
side_view_position_mouse_cursor)
..add('context_menu', context_menu)
..add('changed_since_last_save', changed_since_last_save)
..add('dna_sequence_png_uri', dna_sequence_png_uri)
..add('disable_png_cache_until_action_completes',
disable_png_cache_until_action_completes)
..add('is_zoom_above_threshold', is_zoom_above_threshold)
..add('storables', storables))
.toString();
}
}
class AppUIStateBuilder implements Builder<AppUIState, AppUIStateBuilder> {
_$AppUIState _$v;
SelectablesStoreBuilder _selectables_store;
SelectablesStoreBuilder get selectables_store =>
_$this._selectables_store ??= new SelectablesStoreBuilder();
set selectables_store(SelectablesStoreBuilder selectables_store) =>
_$this._selectables_store = selectables_store;
SetBuilder<int> _side_selected_helix_idxs;
SetBuilder<int> get side_selected_helix_idxs =>
_$this._side_selected_helix_idxs ??= new SetBuilder<int>();
set side_selected_helix_idxs(SetBuilder<int> side_selected_helix_idxs) =>
_$this._side_selected_helix_idxs = side_selected_helix_idxs;
StrandsMoveBuilder _strands_move;
StrandsMoveBuilder get strands_move =>
_$this._strands_move ??= new StrandsMoveBuilder();
set strands_move(StrandsMoveBuilder strands_move) =>
_$this._strands_move = strands_move;
bool _drawing_potential_crossover;
bool get drawing_potential_crossover => _$this._drawing_potential_crossover;
set drawing_potential_crossover(bool drawing_potential_crossover) =>
_$this._drawing_potential_crossover = drawing_potential_crossover;
bool _moving_dna_ends;
bool get moving_dna_ends => _$this._moving_dna_ends;
set moving_dna_ends(bool moving_dna_ends) =>
_$this._moving_dna_ends = moving_dna_ends;
bool _selection_box_displayed_main;
bool get selection_box_displayed_main => _$this._selection_box_displayed_main;
set selection_box_displayed_main(bool selection_box_displayed_main) =>
_$this._selection_box_displayed_main = selection_box_displayed_main;
bool _selection_box_displayed_side;
bool get selection_box_displayed_side => _$this._selection_box_displayed_side;
set selection_box_displayed_side(bool selection_box_displayed_side) =>
_$this._selection_box_displayed_side = selection_box_displayed_side;
bool _assign_complement_to_bound_strands_default;
bool get assign_complement_to_bound_strands_default =>
_$this._assign_complement_to_bound_strands_default;
set assign_complement_to_bound_strands_default(
bool assign_complement_to_bound_strands_default) =>
_$this._assign_complement_to_bound_strands_default =
assign_complement_to_bound_strands_default;
bool _warn_on_change_strand_dna_assign_default;
bool get warn_on_change_strand_dna_assign_default =>
_$this._warn_on_change_strand_dna_assign_default;
set warn_on_change_strand_dna_assign_default(
bool warn_on_change_strand_dna_assign_default) =>
_$this._warn_on_change_strand_dna_assign_default =
warn_on_change_strand_dna_assign_default;
bool _helix_change_apply_to_all;
bool get helix_change_apply_to_all => _$this._helix_change_apply_to_all;
set helix_change_apply_to_all(bool helix_change_apply_to_all) =>
_$this._helix_change_apply_to_all = helix_change_apply_to_all;
ListBuilder<MouseoverData> _mouseover_datas;
ListBuilder<MouseoverData> get mouseover_datas =>
_$this._mouseover_datas ??= new ListBuilder<MouseoverData>();
set mouseover_datas(ListBuilder<MouseoverData> mouseover_datas) =>
_$this._mouseover_datas = mouseover_datas;
ExampleDNADesignsBuilder _example_dna_designs;
ExampleDNADesignsBuilder get example_dna_designs =>
_$this._example_dna_designs ??= new ExampleDNADesignsBuilder();
set example_dna_designs(ExampleDNADesignsBuilder example_dna_designs) =>
_$this._example_dna_designs = example_dna_designs;
DialogBuilder _dialog;
DialogBuilder get dialog => _$this._dialog ??= new DialogBuilder();
set dialog(DialogBuilder dialog) => _$this._dialog = dialog;
StrandCreationBuilder _strand_creation;
StrandCreationBuilder get strand_creation =>
_$this._strand_creation ??= new StrandCreationBuilder();
set strand_creation(StrandCreationBuilder strand_creation) =>
_$this._strand_creation = strand_creation;
GridPositionBuilder _side_view_grid_position_mouse_cursor;
GridPositionBuilder get side_view_grid_position_mouse_cursor =>
_$this._side_view_grid_position_mouse_cursor ??=
new GridPositionBuilder();
set side_view_grid_position_mouse_cursor(
GridPositionBuilder side_view_grid_position_mouse_cursor) =>
_$this._side_view_grid_position_mouse_cursor =
side_view_grid_position_mouse_cursor;
Point<num> _side_view_position_mouse_cursor;
Point<num> get side_view_position_mouse_cursor =>
_$this._side_view_position_mouse_cursor;
set side_view_position_mouse_cursor(
Point<num> side_view_position_mouse_cursor) =>
_$this._side_view_position_mouse_cursor = side_view_position_mouse_cursor;
ContextMenuBuilder _context_menu;
ContextMenuBuilder get context_menu =>
_$this._context_menu ??= new ContextMenuBuilder();
set context_menu(ContextMenuBuilder context_menu) =>
_$this._context_menu = context_menu;
bool _changed_since_last_save;
bool get changed_since_last_save => _$this._changed_since_last_save;
set changed_since_last_save(bool changed_since_last_save) =>
_$this._changed_since_last_save = changed_since_last_save;
String _dna_sequence_png_uri;
String get dna_sequence_png_uri => _$this._dna_sequence_png_uri;
set dna_sequence_png_uri(String dna_sequence_png_uri) =>
_$this._dna_sequence_png_uri = dna_sequence_png_uri;
Action _disable_png_cache_until_action_completes;
Action get disable_png_cache_until_action_completes =>
_$this._disable_png_cache_until_action_completes;
set disable_png_cache_until_action_completes(
Action disable_png_cache_until_action_completes) =>
_$this._disable_png_cache_until_action_completes =
disable_png_cache_until_action_completes;
bool _is_zoom_above_threshold;
bool get is_zoom_above_threshold => _$this._is_zoom_above_threshold;
set is_zoom_above_threshold(bool is_zoom_above_threshold) =>
_$this._is_zoom_above_threshold = is_zoom_above_threshold;
AppUIStateStorableBuilder _storables;
AppUIStateStorableBuilder get storables =>
_$this._storables ??= new AppUIStateStorableBuilder();
set storables(AppUIStateStorableBuilder storables) =>
_$this._storables = storables;
AppUIStateBuilder() {
AppUIState._initializeBuilder(this);
_accessed = false;
}
bool _accessed = false;
AppUIStateBuilder get _$this {
if (!_accessed) {
_accessed = true;
if (_$v != null) {
_selectables_store = _$v.selectables_store?.toBuilder();
_side_selected_helix_idxs = _$v.side_selected_helix_idxs?.toBuilder();
_strands_move = _$v.strands_move?.toBuilder();
_drawing_potential_crossover = _$v.drawing_potential_crossover;
_moving_dna_ends = _$v.moving_dna_ends;
_selection_box_displayed_main = _$v.selection_box_displayed_main;
_selection_box_displayed_side = _$v.selection_box_displayed_side;
_assign_complement_to_bound_strands_default =
_$v.assign_complement_to_bound_strands_default;
_warn_on_change_strand_dna_assign_default =
_$v.warn_on_change_strand_dna_assign_default;
_helix_change_apply_to_all = _$v.helix_change_apply_to_all;
_mouseover_datas = _$v.mouseover_datas?.toBuilder();
_example_dna_designs = _$v.example_dna_designs?.toBuilder();
_dialog = _$v.dialog?.toBuilder();
_strand_creation = _$v.strand_creation?.toBuilder();
_side_view_grid_position_mouse_cursor =
_$v.side_view_grid_position_mouse_cursor?.toBuilder();
_side_view_position_mouse_cursor = _$v.side_view_position_mouse_cursor;
_context_menu = _$v.context_menu?.toBuilder();
_changed_since_last_save = _$v.changed_since_last_save;
_dna_sequence_png_uri = _$v.dna_sequence_png_uri;
_disable_png_cache_until_action_completes =
_$v.disable_png_cache_until_action_completes;
_is_zoom_above_threshold = _$v.is_zoom_above_threshold;
_storables = _$v.storables?.toBuilder();
}
}
return this;
}
@override
void replace(AppUIState other) {
if (other == null) {
throw new ArgumentError.notNull('other');
}
_$v = other as _$AppUIState;
}
@override
void update(void Function(AppUIStateBuilder) updates) {
if (updates != null) updates(this);
}
@override
_$AppUIState build() {
_$AppUIState _$result;
try {
_$result = _$v;
if (_$result == null) {
_$result = new _$AppUIState._(
selectables_store: selectables_store.build(),
side_selected_helix_idxs: side_selected_helix_idxs.build(),
strands_move: _strands_move?.build(),
drawing_potential_crossover: drawing_potential_crossover,
moving_dna_ends: moving_dna_ends,
selection_box_displayed_main: selection_box_displayed_main,
selection_box_displayed_side: selection_box_displayed_side,
assign_complement_to_bound_strands_default:
assign_complement_to_bound_strands_default,
warn_on_change_strand_dna_assign_default:
warn_on_change_strand_dna_assign_default,
helix_change_apply_to_all: helix_change_apply_to_all,
mouseover_datas: mouseover_datas.build(),
example_dna_designs: example_dna_designs.build(),
dialog: _dialog?.build(),
strand_creation: _strand_creation?.build(),
side_view_grid_position_mouse_cursor:
_side_view_grid_position_mouse_cursor?.build(),
side_view_position_mouse_cursor: side_view_position_mouse_cursor,
context_menu: _context_menu?.build(),
changed_since_last_save: changed_since_last_save,
dna_sequence_png_uri: dna_sequence_png_uri,
disable_png_cache_until_action_completes:
disable_png_cache_until_action_completes,
is_zoom_above_threshold: is_zoom_above_threshold,
storables: storables.build());
}
if (_$v != null && _accessed) {
var new_selectables_store = selectables_store.build();
var new_side_selected_helix_idxs = side_selected_helix_idxs.build();
var new_strands_move = _strands_move?.build();
var new_drawing_potential_crossover = drawing_potential_crossover;
var new_moving_dna_ends = moving_dna_ends;
var new_selection_box_displayed_main = selection_box_displayed_main;
var new_selection_box_displayed_side = selection_box_displayed_side;
var new_assign_complement_to_bound_strands_default =
assign_complement_to_bound_strands_default;
var new_warn_on_change_strand_dna_assign_default =
warn_on_change_strand_dna_assign_default;
var new_helix_change_apply_to_all = helix_change_apply_to_all;
var new_mouseover_datas = mouseover_datas.build();
var new_example_dna_designs = example_dna_designs.build();
var new_dialog = _dialog?.build();
var new_strand_creation = _strand_creation?.build();
var new_side_view_grid_position_mouse_cursor =
_side_view_grid_position_mouse_cursor?.build();
var new_side_view_position_mouse_cursor =
side_view_position_mouse_cursor;
var new_context_menu = _context_menu?.build();
var new_changed_since_last_save = changed_since_last_save;
var new_dna_sequence_png_uri = dna_sequence_png_uri;
var new_disable_png_cache_until_action_completes =
disable_png_cache_until_action_completes;
var new_is_zoom_above_threshold = is_zoom_above_threshold;
var new_storables = storables.build();
bool changed = !identical(
new_selectables_store, _$v.selectables_store) ||
!identical(
new_side_selected_helix_idxs, _$v.side_selected_helix_idxs) ||
!identical(new_strands_move, _$v.strands_move) ||
!identical(new_drawing_potential_crossover,
_$v.drawing_potential_crossover) ||
!identical(new_moving_dna_ends, _$v.moving_dna_ends) ||
!identical(new_selection_box_displayed_main,
_$v.selection_box_displayed_main) ||
!identical(new_selection_box_displayed_side,
_$v.selection_box_displayed_side) ||
!identical(new_assign_complement_to_bound_strands_default,
_$v.assign_complement_to_bound_strands_default) ||
!identical(new_warn_on_change_strand_dna_assign_default,
_$v.warn_on_change_strand_dna_assign_default) ||
!identical(
new_helix_change_apply_to_all, _$v.helix_change_apply_to_all) ||
!identical(new_mouseover_datas, _$v.mouseover_datas) ||
!identical(new_example_dna_designs, _$v.example_dna_designs) ||
!identical(new_dialog, _$v.dialog) ||
!identical(new_strand_creation, _$v.strand_creation) ||
!identical(new_side_view_grid_position_mouse_cursor,
_$v.side_view_grid_position_mouse_cursor) ||
!identical(new_side_view_position_mouse_cursor,
_$v.side_view_position_mouse_cursor) ||
!identical(new_context_menu, _$v.context_menu) ||
!identical(
new_changed_since_last_save, _$v.changed_since_last_save) ||
!identical(new_dna_sequence_png_uri, _$v.dna_sequence_png_uri) ||
!identical(new_disable_png_cache_until_action_completes,
_$v.disable_png_cache_until_action_completes) ||
!identical(
new_is_zoom_above_threshold, _$v.is_zoom_above_threshold) ||
!identical(new_storables, _$v.storables);
if (changed) {
_$result = new _$AppUIState._(
selectables_store: new_selectables_store,
side_selected_helix_idxs: new_side_selected_helix_idxs,
strands_move: new_strands_move,
drawing_potential_crossover: new_drawing_potential_crossover,
moving_dna_ends: new_moving_dna_ends,
selection_box_displayed_main: new_selection_box_displayed_main,
selection_box_displayed_side: new_selection_box_displayed_side,
assign_complement_to_bound_strands_default:
new_assign_complement_to_bound_strands_default,
warn_on_change_strand_dna_assign_default:
new_warn_on_change_strand_dna_assign_default,
helix_change_apply_to_all: new_helix_change_apply_to_all,
mouseover_datas: new_mouseover_datas,
example_dna_designs: new_example_dna_designs,
dialog: new_dialog,
strand_creation: new_strand_creation,
side_view_grid_position_mouse_cursor:
new_side_view_grid_position_mouse_cursor,
side_view_position_mouse_cursor:
new_side_view_position_mouse_cursor,
context_menu: new_context_menu,
changed_since_last_save: new_changed_since_last_save,
dna_sequence_png_uri: new_dna_sequence_png_uri,
disable_png_cache_until_action_completes:
new_disable_png_cache_until_action_completes,
is_zoom_above_threshold: new_is_zoom_above_threshold,
storables: new_storables);
}
}
} catch (_) {
String _$failedField;
try {
_$failedField = 'selectables_store';
selectables_store.build();
_$failedField = 'side_selected_helix_idxs';
side_selected_helix_idxs.build();
_$failedField = 'strands_move';
_strands_move?.build();
_$failedField = 'mouseover_datas';
mouseover_datas.build();
_$failedField = 'example_dna_designs';
example_dna_designs.build();
_$failedField = 'dialog';
_dialog?.build();
_$failedField = 'strand_creation';
_strand_creation?.build();
_$failedField = 'side_view_grid_position_mouse_cursor';
_side_view_grid_position_mouse_cursor?.build();
_$failedField = 'context_menu';
_context_menu?.build();
_$failedField = 'storables';
storables.build();
} catch (e) {
throw new BuiltValueNestedFieldError(
'AppUIState', _$failedField, e.toString());
}
rethrow;
}
replace(_$result);
return _$result;
}
}
// ignore_for_file: DD: I removed these because they are very wide |
A couple of updates:
Update: I have an idea for how to solve this. Redux has some notion of "action creators", which is a function that returns an action object. This always seemed like an unnecessary layer of abstraction to me. But perhaps a similar idea can be used here. The idea is to have a new type of action in lib/src/actions/actions.dart called A create(AppState state) In other words, it takes the whole Update: Writing that stackoverflow post helped me convince myself that accessing the AppState as a global variable is perfectly find to do within event-handling code, since it is inherently asynchronous anyway. That is to say, we don't care what the select mode was the last time a strand rendered; we care what it is at the time the strand is clicked. It doesn't appear to destroy any of the React guarantees about re-rendering when necessary based on a view that is a deterministic function of its props, since the event handler (though associated to the view) does not influence any of the visual appearance. This is implemented in PR #390. |
Switching from many connected components to only a few near the top (#87) drastically improved the performance when doing frequent Action dispatches on large designs.
However, it remains noticably janky for M13-length origami designs, particularly for anything editing a strand (e.g., adding a new strand, or nicking an existing one). For longer scaffolds (such as very_large_origami.dna, with a scaffold length about 25,000 bases, in the Python library examples folder), it is very slow.
Use a browser profiler to nail down the performance bottlenecks. I conjecture the following could improve performance, related to built_value:
cache
hashCode
for Built value objects. This is planned for a future release of built_value (allow caching of hashCode google/built_value.dart#760), but if it takes a while, we could fork built_value and implement it. (I've actually got some local code that does it already.) Update: This has been implemented in built_value, but it did not affect performance noticeably.cache built objects so that they are not duplicated. This is trickier to do in built_value, but I had some ideas: possible to copy already-computed memoized fields in build()? google/built_value.dart#774
The text was updated successfully, but these errors were encountered: