Skip to content
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

Open
dave-doty opened this issue Feb 13, 2020 · 11 comments
Open

profile to see performance bottleneck in rendering #191

dave-doty opened this issue Feb 13, 2020 · 11 comments
Labels
enhancement New feature or request high priority Something cruicial to get working soon.

Comments

@dave-doty
Copy link
Member

dave-doty commented Feb 13, 2020

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:

  1. 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.

  2. 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

@dave-doty dave-doty added enhancement New feature or request high priority Something cruicial to get working soon. labels Feb 13, 2020
@dave-doty
Copy link
Member Author

dave-doty commented May 13, 2020

Caching hashCode is now implemented in built_value version 7.1.0: https://pub.dev/packages/built_value#-changelog-tab- But, we've implemented it in all our built_value classes, and it seems not to help. I think the reason is that new built objects are being liberally created. It doesn't help to cache the hashCode if it's only used once before the object is thrown away and a new one (== to the previous) is created.

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 StrandBuilder.

Currently, builders created from a built object (such as when calling reducers to update the Redux store) maintain a reference called _$v to the built object that they came from. There are internal fields for each field in the built object, but they are not initialized.

There is a getter _$this through which all access to fields goes. When that getter is accessed, it checks to see if _$v is null. If it is not null (which it is not the first time the _$this getter is accessed), the _$this getter copies values from each field of _$v into the builder's fields and then sets _$v to null. In any case it returns this, so it's essentially a way to invalidate the _$v object upon the first access (reading or writing) to any field, as long as those accesses go through the getter _$this.

The method build() creates a new built object if _$v is null. In other words, the builder only returns the same built object that created it if no access occurs at all.

The idea is that setting _$v to null is like setting a "dirty" bit true: the builder might have changed its internal values, so it is dirty: is no longer safe to return the original built object.

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();

builder was only accessed via the getter nestedBuilder, but that was used to modify the nested builder object. So the underlying built object _$v should be dirty/invalidated at this point; one of the fields truly changed, and a new built object should be created in the last line calling builder.build(). The lesson is that we cannot simply assume the original built object stored in _$v is the correct value to return just because only getters were accessed on builder.

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 _$v, rather than create a new built object, when build() is called.

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 _$this and build() methods to do the following check. Instead of checking _$v == null, we create a field bool _accessed;, initially false, and we never set _$v to null. (though we may set it to another value).

Then, in the _$this getter, we keep the reference _$v to the original built object, and instead of setting it to null, we set _accessed = true;. But we still keep the other code that copies fields from _$v into the builder's fields. In particular, we want to call toBuilder() on each nested builder.

When build() is called, if _accessed == false then we can simply return _$v as happens now. But we check for one more common case before deciding to create a new built object. If the check below fails, then we will create a new built object, set _$v equal to it, and return it from build(), just as the built_value library currently does.

For each immutable field (types int, String, double, bool, etc., which we will explicitly list), check for equality. (If everything else goes normally, if these haven't changed then they will be the same objects, so this will be a 1-step referential equality check, i.e., identical(field, _$v.field).

For each nested builder nested, we first store var nested_built = nested._$v. We then call var new_nested_built = nested.build(). We then check if identical(nested_built, new_nested_built) is true. If so, then whatever happened with the nested builder, it didn't create a new built object. If this check passes for all nested builders (and all listed immutable types from the previous paragraph), then we conclude that it is safe to return the original built object stored in _$v.

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 identical(nested_built, new_nested_built) rather than nested_built == new_nested_built. If the former is false but the latter is true, one might think it is safe to dispense with new_nested_built. But as the creator of built_value pointed out, it might be that there are fields not used in the equality check that are different between new_nested_built and nested_built. If so, the user's intention would be to have the new values, so we should use the new object. But if identical(nested_built, new_nested_built) == true, then they are the same object and it is safe to avoid creating another object than _$v.

@UnHumbleBen
Copy link
Collaborator

For each nested builder nested, we first store var nested_built = nested._$v

This happens when build() is called? In other words, nested_built is a field in addition to nested? So for example, in StrandBuilder, focusing on the _substrands field, there would be a new field called _substrands_built?

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 --
} 

@dave-doty
Copy link
Member Author

dave-doty commented May 14, 2020

It would be a local variable, not a field, since it's only needed during build().

In the concrete case of StrandBuilder, I'm suggesting these changes:

add a field _accessed:

class StrandBuilder implements Builder<Strand, StrandBuilder> {
  bool _accessed = false;

change _$this as follows:

  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 build() as follows:

  @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 nothing_changed is that every field must be checked, and the only question is whether to call identical (which is safer) or == (which is potentially unsafe). The rule is that the default is to call identical, unless the type is on an explicit list that we make, which we know to be immutable types that are well-behaved in the sense that, if somehow two different objects are created that are considered == to each other, then one can safely be substituted for the other. These types are Dart builtin types: int, double, bool, String.

I've never used Runes (https://api.dart.dev/stable/2.8.2/dart-core/Runes-class.html) but maybe that should be included as well. Primitive enumerated types should be safe to use == as well. I'm not sure about the Enum type in built_value. That might require more care, similar to the standard classes that implement Built.

I inspected the Color class (and its subclasses) from the Dart package color and saw it is immutable. Hopefully it is safe in the broader sense that if two different Color objects are == to each other, then they can be used interchangably. But this may not be true. If not, then we should replace color == _$v.color with identical(color, _$v.color).

Some other things would probably have to change, which I haven't thought much about, for instance, this is probably what replace would be now:

  @override
  void replace(Strand other) {
    if (other == null) {
      throw new ArgumentError.notNull('other');
    }
    _$v = other as _$Strand;
    _accessed = false;
  }

@dave-doty
Copy link
Member Author

One thing I haven't studied yet is the built collections. For instance, here's ListBuilder.build():

  BuiltList<E> build() {
    if (_listOwner == null) {
      _setOwner(_BuiltList<E>.withSafeList(_list));
    }
    return _listOwner;
  }

Hopefully, if nothing changed, then ListBuilder._listOwner != null, and this is a fast call that simply returns the original BuiltList called _listOwner. But I haven't really inspected the rest of the methods in ListBuilder.

@dave-doty
Copy link
Member Author

dave-doty commented May 14, 2020

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 _finalizeBuilder is defined for a built class, then it is called at the top of build(). We have such a method for Strand:

  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 build() to allocate a new Strand object. It's only updating integer id's and idx's, and if the Strand hasn't otherwise changed, then those will be the same when build() is called, so it will know not to allocate a new Strand object and just reuse _$v.

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 build() returned a identical object or not.

@UnHumbleBen
Copy link
Collaborator

UnHumbleBen commented May 15, 2020

It will all be for nothing if some reducer always produces a new built object.

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 $this, my understanding is that reading a builder's fields can cause it to build a completely new object, even if it was not modify. Is this the main cause of the performance issue?

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 identical is false between the old strands and new strands, even the ones that were not changed?

@dave-doty
Copy link
Member Author

Don't reducers have to produce a new built object whenever the object needs to be modify?

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 == to the old object.)

are you saying that when one strand is changed, all of the other strands in design will be rebuilt so that identical is false between the old strands and new strands, even the ones that were not changed?

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:

image

That's almost a full second of processing in that part on the right.

Anyway, first thing to test is which objects are identical to each other under various reducers. I could be wrong, and maybe the only objects that change identity are the ones that actually had their underlying value modified.

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 grid, helices, or strands change I can still see how this might generate a new object for them anyway. It's not checking the action.

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 BuiltList of all strands.

@dave-doty
Copy link
Member Author

dave-doty commented Jul 6, 2020

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:

  • It's not going to be safe if someone uses mutable data in a field, e.g., storing a List instead of a BuiltList. So there should be some check for that.

  • It's really tricky to write code-generating code; it looks like this:

    result.writeln('if (!_accessed) {');
    result.writeln('  _accessed = true;');

    So when implementing, use a Boolean flag for new code being added, and try to disentwine it as much as possible from what is already there. It's very difficult to debug, and it cannot be incrementally compiled, so on my system it takes about one minute to compile each time.

  • Currently, built_value uses _$v == null as the dirty bit. Switching from that to _accessed == true wasn't as straightforward as I thought, since there are a few reasons for _$v to be null, and they are not all as simple as "no access to this builder has occurred yet." If you directly instantiate a builder (instead of calling toBuilder() on a built value), then _$v starts out null. So this code didn't work at first:

    var b = MyBuilder();
    b.field = 123;
    var v = b.build();

    A few things that had to happen:

    • In the Builder constructor, immediately after the call to _initializeBuilder(), set _accessed = false. _accessed becomes true during a nontrivial _initializeBuilder, due to writing to the fields, but in the rest of the logic after _initializeBuilder, we actually want it to be false.
    • In the calls to Builder.build() and the getter _$this, _$v could be null, which has to be handled. (I mistakenly thought it would always be non-null since I replaced the line _$v = null in the _$this getter, but there are other ways for it to be null, such as directly instantiating a builder as above.)

Below I've pasted a concrete example of a small Builder, and a larger example of the builder for AppUIState for inspection.

@dave-doty
Copy link
Member Author

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;
  }
}

@dave-doty
Copy link
Member Author

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

@dave-doty
Copy link
Member Author

dave-doty commented Jul 16, 2020

A couple of updates:

  1. I've asked for some guidance on passing props to components that don't base the view on them (only the event-handling code): https://stackoverflow.com/questions/62942644/how-to-make-a-react-component-intelligently-dispatch-a-redux-action-on-user-inte I implemented in speed up time to switch edit or select modes on large designs, by changing css classes on strands and strand parts outside of React/Redux for efficiency, and pulling edit/select mode information out of view components to decrease re-rendering time when edit/select modes change #370 something similar to item 2 there, and don't like it.
  2. I profiled the time required to do == and identical on a large DNA design (very_large_origami.dna in Python examples folder, with 25,000 base pairs). It's pretty small. == takes typically a few dozen microseconds, maybe sometimes 300 microseconds (i.e., a third of a millisecond). So I don't think it's worth to optimize built_value in the way proposed above. It will save time (calls to identical are only a few microseconds), but it's actually not that expensive to call == on two designs that are equal, even if they are not the same object. (I don't know if some of the actions left sub-objects identical and that sped it up; I don't have a comparison for a truly deep equality check; maybe it's much larger).

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 ActionCreator<A extends Action> (subtype of Action). An ActionCreator<A> is an object with a method of type

A create(AppState state)

In other words, it takes the whole AppState and returns an Action. This lets it do the necessary data lookups. As an object, it can contain fields that describe data gathered from the code (usually View event handler code) that instantiated it. For example, it could reference a Selectable to select. create() returns either null or some special value to indicate that the action should be thrown away.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request high priority Something cruicial to get working soon.
Projects
None yet
Development

No branches or pull requests

2 participants