Improve UI-thread animation performance (#159288)

The following PR (https://github.com/flutter/flutter/pull/138481) got
split in 2, this is part 2.

We now have the microbenchmarks to compare this change against (and
hopefully see improvements).

Close: https://github.com/flutter/flutter/issues/146211
Part 1: https://github.com/flutter/flutter/pull/153368

---------

Co-authored-by: Nate Wilson <nathan.wilson1232@gmail.com>
This commit is contained in:
Bernardo Ferrari 2024-12-07 13:59:10 -03:00 committed by GitHub
parent 61d7d0ed23
commit c1a6c5500c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 30 additions and 6 deletions

View File

@ -91,7 +91,7 @@ mixin AnimationEagerListenerMixin {
/// and [didUnregisterListener]. Implementations of these methods can be obtained /// and [didUnregisterListener]. Implementations of these methods can be obtained
/// by mixing in another mixin from this library, such as [AnimationLazyListenerMixin]. /// by mixing in another mixin from this library, such as [AnimationLazyListenerMixin].
mixin AnimationLocalListenersMixin { mixin AnimationLocalListenersMixin {
final ObserverList<VoidCallback> _listeners = ObserverList<VoidCallback>(); final HashedObserverList<VoidCallback> _listeners = HashedObserverList<VoidCallback>();
/// Called immediately before a listener is added via [addListener]. /// Called immediately before a listener is added via [addListener].
/// ///

View File

@ -43,12 +43,15 @@ class ObserverList<T> extends Iterable<T> {
/// ///
/// Returns whether the item was present in the list. /// Returns whether the item was present in the list.
bool remove(T item) { bool remove(T item) {
_isDirty = true; final bool removed = _list.remove(item);
_set.clear(); // Clear the set so that we don't leak items. if (removed) {
return _list.remove(item); _isDirty = true;
_set.clear(); // Clear the set so that we don't leak items.
}
return removed;
} }
/// Removes all items from the list. /// Removes all items from the [ObserverList].
void clear() { void clear() {
_isDirty = false; _isDirty = false;
_list.clear(); _list.clear();
@ -78,6 +81,10 @@ class ObserverList<T> extends Iterable<T> {
@override @override
bool get isNotEmpty => _list.isNotEmpty; bool get isNotEmpty => _list.isNotEmpty;
/// Creates a List containing the elements of the [ObserverList].
///
/// Overrides the default implementation of the [Iterable] to reduce number
/// of allocations.
@override @override
List<T> toList({bool growable = true}) { List<T> toList({bool growable = true}) {
return _list.toList(growable: growable); return _list.toList(growable: growable);
@ -126,6 +133,9 @@ class HashedObserverList<T> extends Iterable<T> {
return true; return true;
} }
/// Removes all items from the [HashedObserverList].
void clear() => _map.clear();
@override @override
bool contains(Object? element) => _map.containsKey(element); bool contains(Object? element) => _map.containsKey(element);
@ -137,4 +147,18 @@ class HashedObserverList<T> extends Iterable<T> {
@override @override
bool get isNotEmpty => _map.isNotEmpty; bool get isNotEmpty => _map.isNotEmpty;
/// Creates a List containing the elements of the [HashedObserverList].
///
/// Overrides the default implementation of [Iterable] to reduce number of
/// allocations.
@override
List<T> toList({bool growable = true}) {
final Iterator<T> iterator = _map.keys.iterator;
return List<T>.generate(
_map.length,
(_) => (iterator..moveNext()).current,
growable: growable,
);
}
} }

View File

@ -40,7 +40,7 @@ void main() {
log.clear(); log.clear();
controller.value = 0.4; controller.value = 0.4;
expect(log, <String>['listener2', 'listener4', 'listener4']); expect(log, <String>['listener2', 'listener4']);
log.clear(); log.clear();
}); });