Skip to content

Commit

Permalink
feat! allow specifying default extent (#45)
Browse files Browse the repository at this point in the history
This improves performance in cases where default extent is same for all
items.

This is a breaking change because the signature of extent provider has
changed.
  • Loading branch information
knopp authored Mar 19, 2024
1 parent 8e3b022 commit 93b5d12
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 22 deletions.
2 changes: 1 addition & 1 deletion lib/src/element.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class SuperSliverMultiBoxAdaptorElement extends SliverMultiBoxAdaptorElement
}

@override
double estimateExtentForItem(int index) {
double estimateExtentForItem(int? index) {
return renderObject.estimateExtentForItem(index);
}

Expand Down
59 changes: 44 additions & 15 deletions lib/src/extent_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,20 @@ class ResizableFloat64List {
_maybeTrim();
}

void resizeWithDefault(int newSize, double defaultValue) {
assert(newSize >= 0);
if (newSize == _length) {
return;
}
_ensureCapacity(newSize);
final list = _list;
for (var i = _length; i < newSize; ++i) {
list[i] = defaultValue;
}
_length = newSize;
_maybeTrim();
}

void insert(int index, double element) {
_ensureCapacity(_length + 1);
if (index < _length) {
Expand Down Expand Up @@ -214,24 +228,44 @@ class ExtentList {
_fenwickTree = null;
}

void resize(int newSize, double Function(int index) defaultExtent) {
void resize(int newSize, double Function(int? index) defaultExtent) {
assert(_extents.length == _dirty.length);
final prevSize = _extents.length;
final prevExtents = _extents;

if (newSize < prevSize) {
for (var i = newSize; i < prevSize; ++i) {
_totalExtent -= prevExtents[i];
_dirtyCount -= _dirty[i] ? 1 : 0;
if (prevSize - newSize < newSize) {
for (var i = newSize; i < prevSize; ++i) {
_totalExtent -= prevExtents[i];
_dirtyCount -= _dirty[i] ? 1 : 0;
}
} else {
// In this case it's less work to count the extent and dirty items
// from scratch.
_totalExtent = 0;
_dirtyCount = 0;
for (var i = 0; i < newSize; ++i) {
_totalExtent += _extents[i];
_dirtyCount += _dirty[i] ? 1 : 0;
}
}
}

double addedDefaultExtent = 0.0;
_extents.resize(newSize, (index) {
final extent = defaultExtent(index);
addedDefaultExtent += extent;
return extent;
});
final sameExtent = defaultExtent(null);
if (sameExtent > 0) {
_extents.resizeWithDefault(newSize, sameExtent);
if (newSize > prevSize) {
addedDefaultExtent = (newSize - prevSize) * sameExtent;
}
} else {
_extents.resize(newSize, (index) {
final extent = defaultExtent(index);
addedDefaultExtent += extent;
return extent;
});
}

_dirty.length = newSize;

if (newSize > prevSize) {
Expand All @@ -251,12 +285,7 @@ class ExtentList {
}

FenwickTree _getOrBuildFenwickTree() {
if (_fenwickTree == null) {
_fenwickTree = FenwickTree(size: _extents.length);
for (var i = 0; i < _extents.length; ++i) {
_fenwickTree!.update(i, _extents[i]);
}
}
_fenwickTree ??= FenwickTree.fromList(list: _extents._list);
return _fenwickTree!;
}

Expand Down
2 changes: 1 addition & 1 deletion lib/src/extent_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ abstract class ExtentManagerDelegate {
const ExtentManagerDelegate();

void onMarkNeedsLayout();
double estimateExtentForItem(int index);
double estimateExtentForItem(int? index);
double getOffsetToReveal(
int index,
double alignment, {
Expand Down
16 changes: 15 additions & 1 deletion lib/src/fenwick_tree.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,21 @@ import "dart:typed_data";

/// Zero indexed Fenwick Tree
class FenwickTree {
FenwickTree({required this.size}) : _tree = Float64List(size + 1);
FenwickTree({
required this.size,
}) : _tree = Float64List(size + 1);

FenwickTree.fromList({
required Float64List list,
}) : size = list.length,
_tree = Float64List(list.length + 1) {
for (int i = 1; i <= size; ++i) {
_tree[i] += list[i - 1];
if (i + (i & -i) <= size) {
_tree[i + (i & -i)] += _tree[i];
}
}
}

final int size;
final Float64List _tree;
Expand Down
2 changes: 1 addition & 1 deletion lib/src/render_object.dart
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ class RenderSuperSliverList extends RenderSliverMultiBoxAdaptor
});
}

double estimateExtentForItem(int index) {
double estimateExtentForItem(int? index) {
return estimateExtent(index, constraints.crossAxisExtent);
}

Expand Down
9 changes: 7 additions & 2 deletions lib/src/super_sliver_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ class ListController extends ChangeNotifier {
}

typedef ExtentEstimationProvider = double Function(
int index,
int? index,
double crossAxisExtent,
);

Expand Down Expand Up @@ -396,6 +396,11 @@ class SuperSliverList extends SliverMultiBoxAdaptorWidget {
/// out, either through scrolling or [extentPrecalculationPolicy], the actual
/// extents are calculated and the scroll offset is adjusted to account for
/// the difference between estimated and actual extents.
///
/// The item index argument is nullable. If all estimated items have same extent,
/// the implementation should return non-zero extent for the `null` index. This saves
/// calls to extent estimation provider for large lists.
/// If each item has different extent, return zero for the `null` index.
final ExtentEstimationProvider? extentEstimation;

/// Optional policy that can be used to asynchronously precalculate the extents
Expand Down Expand Up @@ -484,6 +489,6 @@ class _TimeSuperSliverListLayoutBudget extends SuperSliverListLayoutBudget {
final Duration budget;
}

double _defaultEstimateExtent(int index, double crossAxisExtent) {
double _defaultEstimateExtent(int? index, double crossAxisExtent) {
return 100.0;
}
17 changes: 16 additions & 1 deletion test/extent_list_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ void main() {
expect(list[0], equals(10));
expect(list[299], equals(10));
});
test("resizeWithDefault", () {
final list = ResizableFloat64List();
list.resizeWithDefault(300, 10);
expect(list.length, equals(300));
expect(list[0], equals(10));
expect(list[299], equals(10));
});
});
group("ExtentList", () {
test("empty", () {
Expand Down Expand Up @@ -75,14 +82,22 @@ void main() {
expect(extentList.hasDirtyItems, isFalse);
expect(extentList.totalExtent, equals(100));

extentList.resize(4, (_) => 150.0);
extentList.resize(4, (i) => i == null ? 0 : 150.0);
expect(extentList.hasDirtyItems, isTrue);
expect(extentList.totalExtent, equals(400));

extentList.setExtent(2, 70);
extentList.setExtent(3, 80);
expect(extentList.hasDirtyItems, isFalse);
expect(extentList.totalExtent, equals(100 + 70 + 80));

extentList.resize(100, (index) => 50);
expect(extentList.totalExtent, equals(5050));
expect(extentList.dirtyItemCount, 96);

extentList.resize(10, (index) => 50);
expect(extentList.totalExtent, equals(550));
expect(extentList.dirtyItemCount, 6);
});
test("cleanRange", () {
final extentList = ExtentList();
Expand Down
41 changes: 41 additions & 0 deletions test/fenwick_tree_test.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import "dart:typed_data";

import "package:super_sliver_list/src/fenwick_tree.dart";
import "package:test/test.dart";

Expand Down Expand Up @@ -37,6 +39,26 @@ void main() {
expect(tree.query(6), 17);
expect(tree.query(10), 31);
});
test("FenwickTree from list", () {
final list = Float64List(10);
list[0] = 2;
list[1] = 1;
list[2] = 3;
list[3] = 4;
list[4] = 5;
list[5] = 1;
list[6] = 2;
list[7] = 3;
list[8] = 4;
list[9] = 5;
final tree = FenwickTree.fromList(list: list);
expect(tree.query(0), 0);
expect(tree.query(3), 6);
expect(tree.query(5), 15);
expect(tree.query(6), 16);
expect(tree.query(9), 25);
expect(tree.query(10), 30);
});
test("large FenwickTree", () {
const size = 10000000;
final tree = FenwickTree(size: size);
Expand All @@ -55,4 +77,23 @@ void main() {
total += i == 0 ? 100 : i;
}
});
test("large FenwickTree from list", () {
const size = 10000000;
final list = Float64List(size);
for (var i = 0; i < size; ++i) {
list[i] = i.toDouble();
}
final tree = FenwickTree.fromList(list: list);
double total = 0;
for (var i = 0; i < size; ++i) {
expect(tree.query(i), total);
total += i;
}
tree.update(0, 100);
total = 0;
for (var i = 0; i < size; ++i) {
expect(tree.query(i), total);
total += i == 0 ? 100 : i;
}
});
}

0 comments on commit 93b5d12

Please sign in to comment.