diff --git a/.github/actions/setup/action.yaml b/.github/actions/setup/action.yaml index 7b9150f..d769081 100644 --- a/.github/actions/setup/action.yaml +++ b/.github/actions/setup/action.yaml @@ -5,7 +5,7 @@ inputs: flutter-version: description: 'The version of Flutter to use' required: false - default: '3.24.5' + default: '3.35.3' pub-cache: description: 'The name of the pub cache variable' required: false diff --git a/.github/workflows/checkout.yml b/.github/workflows/checkout.yml index 73968cc..3e80abc 100644 --- a/.github/workflows/checkout.yml +++ b/.github/workflows/checkout.yml @@ -66,7 +66,7 @@ jobs: - name: 🚂 Setup Flutter and dependencies uses: ./.github/actions/setup with: - flutter-version: 3.27.1 + flutter-version: 3.35.3 - name: 👷 Install Dependencies timeout-minutes: 1 diff --git a/lib/src/collisions/quadtree.dart b/lib/src/collisions/quadtree.dart index 54fa0bc..fd01313 100644 --- a/lib/src/collisions/quadtree.dart +++ b/lib/src/collisions/quadtree.dart @@ -49,13 +49,8 @@ extension type QuadTree$QueryResult._(Float32List _bytes) { /// The walk stops when it iterates over all objects or /// when the callback returns false. void forEach( - bool Function( - int id, - double left, - double top, - double width, - double height, - ) cb, + bool Function(int id, double left, double top, double width, double height) + cb, ) { if (isEmpty) return; final ids = Uint32List.sublistView(_bytes); @@ -183,14 +178,13 @@ final class QuadTree { required Float32List objects, required Uint32List recycledIds, required Uint32List id2node, - }) : - // Nodes - _nodes = nodes, - _recycledNodes = recycledNodes, - // Objects - _objects = objects, - _recycledIds = recycledIds, - _id2node = id2node; + }) : // Nodes + _nodes = nodes, + _recycledNodes = recycledNodes, + // Objects + _objects = objects, + _recycledIds = recycledIds, + _id2node = id2node; // -------------------------------------------------------------------------- // PROPERTIES @@ -290,10 +284,7 @@ final class QuadTree { // Get the root node of the QuadTree // or create a new one if it does not exist. - final root = _root ??= _createNode( - parent: null, - boundary: boundary, - ); + final root = _root ??= _createNode(parent: null, boundary: boundary); // Create a new object in the QuadTree. final objectId = _getNextObjectId(); @@ -434,10 +425,7 @@ final class QuadTree { // Resize recycled ids array if needed if (_recycledIdsCount == _recycledIds.length) - _recycledIds = _resizeUint32List( - _recycledIds, - _recycledIds.length << 1, - ); + _recycledIds = _resizeUint32List(_recycledIds, _recycledIds.length << 1); _recycledIds[_recycledIdsCount++] = objectId; return true; @@ -452,13 +440,8 @@ final class QuadTree { /// The walk stops when it iterates over all objects or /// when the callback returns false. void forEach( - bool Function( - int id, - double left, - double top, - double width, - double height, - ) cb, + bool Function(int id, double left, double top, double width, double height) + cb, ) { final root = _root; if (root == null) return; @@ -1013,7 +996,8 @@ final class QuadTree { // -------------------------------------------------------------------------- @override - String toString() => 'QuadTree{' + String toString() => + 'QuadTree{' 'nodes: $nodes, ' 'objects: $length' '}'; @@ -1137,13 +1121,8 @@ final class QuadTree$Node { /// when the callback returns false. @pragma('vm:prefer-inline') void forEach( - bool Function( - int id, - double left, - double top, - double width, - double height, - ) cb, + bool Function(int id, double left, double top, double width, double height) + cb, ) { if (isEmpty) return; if (subdivided) { @@ -1184,12 +1163,7 @@ final class QuadTree$Node { final top = boundary.top; final nw = _northWest = tree._createNode( parent: this, - boundary: ui.Rect.fromLTWH( - left, - top, - halfWidth, - halfHeight, - ), + boundary: ui.Rect.fromLTWH(left, top, halfWidth, halfHeight), ), ne = _northEast = tree._createNode( parent: this, @@ -1327,7 +1301,8 @@ final class QuadTree$Node { identical(this, other) || other is QuadTree$Node && id == other.id; @override - String toString() => r'QuadTree$Node{' + String toString() => + r'QuadTree$Node{' 'id: $id, ' 'objects: $length, ' 'subdivided: $_subdivided' diff --git a/lib/src/repaint.dart b/lib/src/repaint.dart index b6b9777..352e139 100644 --- a/lib/src/repaint.dart +++ b/lib/src/repaint.dart @@ -46,17 +46,16 @@ class RePaint extends LeafRenderObjectWidget { Listenable? repaint, bool repaintBoundary = false, Key? key, - }) => - RePaintInline( - render: render, - setUp: setUp, - update: update, - tearDown: tearDown, - frameRate: frameRate, - repaint: repaint, - repaintBoundary: repaintBoundary, - key: key, - ); + }) => RePaintInline( + render: render, + setUp: setUp, + update: update, + tearDown: tearDown, + frameRate: frameRate, + repaint: repaint, + repaintBoundary: repaintBoundary, + key: key, + ); /// The painter controller, used to update and paint the scene. /// For example, a game controller or a custom painter. @@ -76,10 +75,10 @@ class RePaint extends LeafRenderObjectWidget { @override RenderObject createRenderObject(BuildContext context) => RePaintBox( - painter: painter, - context: context, - isRepaintBoundary: repaintBoundary, - ); + painter: painter, + context: context, + isRepaintBoundary: repaintBoundary, + ); @override void updateRenderObject(BuildContext context, RePaintBox renderObject) { @@ -92,7 +91,8 @@ class RePaint extends LeafRenderObjectWidget { renderObject._painter = painter ..mount(renderObject, renderObject.owner!) ..lifecycle( - WidgetsBinding.instance.lifecycleState ?? AppLifecycleState.resumed); + WidgetsBinding.instance.lifecycleState ?? AppLifecycleState.resumed, + ); } } @@ -136,9 +136,9 @@ class RePaintBox extends RenderBox with WidgetsBindingObserver { required RePainter painter, required BuildContext context, required bool isRepaintBoundary, - }) : _painter = painter, - _context = context, - _$isRepaintBoundary = isRepaintBoundary; + }) : _painter = painter, + _context = context, + _$isRepaintBoundary = isRepaintBoundary; /// Current controller. RePainter get painter => _painter; @@ -185,19 +185,17 @@ class RePaintBox extends RenderBox with WidgetsBindingObserver { _painter ..mount(this, owner) ..lifecycle( - WidgetsBinding.instance.lifecycleState ?? AppLifecycleState.resumed); + WidgetsBinding.instance.lifecycleState ?? AppLifecycleState.resumed, + ); WidgetsBinding.instance.addObserver(this); - _ticker = Ticker(_onTick, debugLabel: 'RePaintBox')..start(); + _ticker = Ticker(onTick, debugLabel: 'RePaintBox')..start(); } @override bool hitTestSelf(Offset position) => true; @override - bool hitTestChildren( - BoxHitTestResult result, { - required Offset position, - }) => + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) => false; @override @@ -250,10 +248,13 @@ class RePaintBox extends RenderBox with WidgetsBindingObserver { Duration _lastFrameTime = Duration.zero; /// This method is periodically invoked by the [_ticker]. - void _onTick(Duration elapsed) { + void onTick(Duration elapsed) { if (!attached) return; + // Delta can be negative when widget is paused. + // Sometimes even like "-15" seconds. final delta = elapsed - _lastFrameTime; - final deltaMs = delta.inMicroseconds / Duration.microsecondsPerMillisecond; + final deltaMs = + delta.inMicroseconds.abs() / Duration.microsecondsPerMillisecond; _lastFrameTime = elapsed; // Update game scene and prepare for rendering. _painter.update(this, elapsed, deltaMs); diff --git a/pubspec.yaml b/pubspec.yaml index 0603120..ffae8bf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -36,8 +36,8 @@ platforms: # path: example.png environment: - sdk: ^3.6.0 - flutter: ">=3.27.0" + sdk: ^3.8.0 + flutter: ">=3.35.3" dependencies: flutter: @@ -52,5 +52,5 @@ dev_dependencies: fake_async: ^1.3.0 flutter_lints: ">=4.0.0 <6.0.0" benchmark_harness: ^2.3.1 - flame: ^1.23.0 - vector_math: ^2.1.4 + flame: ^1.34.0 + vector_math: ^2.2.0 diff --git a/test/benchmark/quadtree_benchmark_test.dart b/test/benchmark/quadtree_benchmark_test.dart index 6fb2ab8..b32f0d5 100644 --- a/test/benchmark/quadtree_benchmark_test.dart +++ b/test/benchmark/quadtree_benchmark_test.dart @@ -7,128 +7,119 @@ import 'package:benchmark_harness/benchmark_harness.dart'; import 'package:flame/collisions.dart' as flame; import 'package:repaint/repaint.dart'; import 'package:test/test.dart'; -import 'package:vector_math/vector_math_64.dart' show Vector2; +import 'package:vector_math/vector_math.dart' show Vector2; // TODO(plugfox): Написать бенчмарк на поиск лучшего capacity для QuadTree // Mike Matiunin , 08 January 2025 -void main() => group( - 'QuadTree benchmark', - () { - var report = true; +void main() => group('QuadTree benchmark', () { + var report = true; - /* + /* RePaint QuadTree inserts(RunTime): 934.0045 us. Flame QuadTree inserts(RunTime): 26800.415584415583 us. */ - test('Inserts', () { - final repaint = _RePaintQuadTreeInsertsBenchmark(); - if (report) - // ignore: dead_code - repaint.report(); - if (repaint.qt.length != 1000) - throw Exception('Failed to insert all'); - final errors = repaint.qt.healthCheck(); - //if (errors.isNotEmpty) throw Exception(errors.join('\n')); - expect(errors, isEmpty); - final flame = _FlameQuadTreeInsertsBenchmark(); - if (report) - // ignore: dead_code - flame.report(); - if (!report) - // ignore: dead_code - expect( - repaint.measure(), - lessThanOrEqualTo(flame.measure()), - ); - }); - - /* + test('Inserts', () { + final repaint = _RePaintQuadTreeInsertsBenchmark(); + if (report) + // ignore: dead_code + repaint.report(); + if (repaint.qt.length != 1000) throw Exception('Failed to insert all'); + final errors = repaint.qt.healthCheck(); + //if (errors.isNotEmpty) throw Exception(errors.join('\n')); + expect(errors, isEmpty); + final flame = _FlameQuadTreeInsertsBenchmark(); + if (report) + // ignore: dead_code + flame.report(); + if (!report) + // ignore: dead_code + expect(repaint.measure(), lessThanOrEqualTo(flame.measure())); + }); + + /* RePaint QuadTree inserts & removes(RunTime): 114.04378531073446 us. Flame QuadTree inserts & removes(RunTime): 2244.599 us. */ - test('Inserts and removes', () { - final repaint = _RePaintQuadTreeInsertsAndRemovesBenchmark(); - if (report) - // ignore: dead_code - repaint.report(); - final errors = repaint.qt.healthCheck(); - //if (errors.isNotEmpty) throw Exception(errors.join('\n')); - expect(errors, isEmpty); - final flame = _FlameQuadTreeInsertsAndRemovesBenchmark(); - if (report) - // ignore: dead_code - flame.report(); - if (!report) - // ignore: dead_code - expect( - repaint.measure(), - lessThanOrEqualTo(flame.measure()), - ); - }); - - /* + test('Inserts and removes', () { + final repaint = _RePaintQuadTreeInsertsAndRemovesBenchmark(); + if (report) + // ignore: dead_code + repaint.report(); + final errors = repaint.qt.healthCheck(); + //if (errors.isNotEmpty) throw Exception(errors.join('\n')); + expect(errors, isEmpty); + final flame = _FlameQuadTreeInsertsAndRemovesBenchmark(); + if (report) + // ignore: dead_code + flame.report(); + if (!report) + // ignore: dead_code + expect(repaint.measure(), lessThanOrEqualTo(flame.measure())); + }); + + /* RePaint QuadTree query ids(RunTime): 1004.9575 us. RePaint QuadTree query map(RunTime): 1825.3253373313344 us. RePaint QuadTree query(RunTime): 2058.2428785607194 us. Flame QuadTree query(RunTime): 2466.44 us. */ - test('Static query', () { - // ~ 560 us to query, 567 us. - final repaintIds = _RePaintQuadTreeQueryIdsBenchmark(); - if (report) - // ignore: dead_code - repaintIds.report(); - var errors = repaintIds.qt.healthCheck(); - //if (errors.isNotEmpty) throw Exception(errors.join('\n')); - expect(errors, isEmpty); - // ~ + 510 us to query & + 500 us to create hash map, 1170 us. - final repaintMap = _RePaintQuadTreeQueryMapBenchmark(); - if (report) - // ignore: dead_code - repaintMap.report(); - errors = repaintMap.qt.healthCheck(); - //if (errors.isNotEmpty) throw Exception(errors.join('\n')); - expect(errors, isEmpty); - // ~ + 1000 us. to query and + 900 us to create hash map, 1945 us - final repaintB = _RePaintQuadTreeQueryBenchmark(); - if (report) - // ignore: dead_code - repaintB.report(); - errors = repaintB.qt.healthCheck(); - //if (errors.isNotEmpty) throw Exception(errors.join('\n')); - expect(errors, isEmpty); - final flame = _FlameQuadTreeQueryBenchmark(); - if (report) - // ignore: dead_code - flame.report(); - if (!report) - // ignore: dead_code - expect(repaintB.measure(), lessThanOrEqualTo(flame.measure())); - }); - - /* + test('Static query', () { + // ~ 560 us to query, 567 us. + final repaintIds = _RePaintQuadTreeQueryIdsBenchmark(); + if (report) + // ignore: dead_code + repaintIds.report(); + var errors = repaintIds.qt.healthCheck(); + //if (errors.isNotEmpty) throw Exception(errors.join('\n')); + expect(errors, isEmpty); + // ~ + 510 us to query & + 500 us to create hash map, 1170 us. + final repaintMap = _RePaintQuadTreeQueryMapBenchmark(); + if (report) + // ignore: dead_code + repaintMap.report(); + errors = repaintMap.qt.healthCheck(); + //if (errors.isNotEmpty) throw Exception(errors.join('\n')); + expect(errors, isEmpty); + // ~ + 1000 us. to query and + 900 us to create hash map, 1945 us + final repaintB = _RePaintQuadTreeQueryBenchmark(); + if (report) + // ignore: dead_code + repaintB.report(); + errors = repaintB.qt.healthCheck(); + //if (errors.isNotEmpty) throw Exception(errors.join('\n')); + expect(errors, isEmpty); + final flame = _FlameQuadTreeQueryBenchmark(); + if (report) + // ignore: dead_code + flame.report(); + if (!report) + // ignore: dead_code + expect(repaintB.measure(), lessThanOrEqualTo(flame.measure())); + }); + + /* RePaint QuadTree move(RunTime): 121.95089894606323 us. Flame QuadTree move(RunTime): 310.9901587301587 us. */ - test('Move', () { - final repaint = _RePaintQuadTreeMoveBenchmark(); - if (report) - // ignore: dead_code - repaint.report(); - final errors = repaint.qt.healthCheck(); - //if (errors.isNotEmpty) throw Exception(errors.join('\n')); - expect(errors, isEmpty); - final flame = _FlameQuadTreeMoveBenchmark(); - if (report) - // ignore: dead_code - flame.report(); - if (!report) - // ignore: dead_code - expect(repaint.measure(), lessThanOrEqualTo(flame.measure())); - }); - - /* + test('Move', () { + final repaint = _RePaintQuadTreeMoveBenchmark(); + if (report) + // ignore: dead_code + repaint.report(); + final errors = repaint.qt.healthCheck(); + //if (errors.isNotEmpty) throw Exception(errors.join('\n')); + expect(errors, isEmpty); + final flame = _FlameQuadTreeMoveBenchmark(); + if (report) + // ignore: dead_code + flame.report(); + if (!report) + // ignore: dead_code + expect(repaint.measure(), lessThanOrEqualTo(flame.measure())); + }); + + /* 18..24 - Best capacity 6: 4128.678 us. Max depth 8 nodes @@ -145,23 +136,21 @@ void main() => group( 28: 2896.32125 us. Max depth 6 nodes 30: 2836.28125 us. Max depth 6 nodes */ - test('Capacity', () { - final results = {}; - for (var i = 6; i < 32; i += 2) { - final repaint = _RePaintQuadTreeCapacityBenchmark(i); - final us = repaint.measure(); - results[i] = '$us us. Max depth ${repaint.maxDepth} nodes'; - final errors = repaint.qt.healthCheck(); - //if (errors.isNotEmpty) throw Exception(errors.join('\n')); - expect(errors, isEmpty); - } - if (report) - // ignore: dead_code, avoid_print - print( - results.entries.map((e) => '${e.key}: ${e.value}').join('\n')); - }); - }, - ); + test('Capacity', () { + final results = {}; + for (var i = 6; i < 32; i += 2) { + final repaint = _RePaintQuadTreeCapacityBenchmark(i); + final us = repaint.measure(); + results[i] = '$us us. Max depth ${repaint.maxDepth} nodes'; + final errors = repaint.qt.healthCheck(); + //if (errors.isNotEmpty) throw Exception(errors.join('\n')); + expect(errors, isEmpty); + } + if (report) + // ignore: dead_code, avoid_print + print(results.entries.map((e) => '${e.key}: ${e.value}').join('\n')); + }); +}); class _RePaintQuadTreeInsertsBenchmark extends BenchmarkBase { _RePaintQuadTreeInsertsBenchmark() : super('RePaint QuadTree inserts'); @@ -220,7 +209,7 @@ class _FlameQuadTreeInsertsBenchmark extends BenchmarkBase { class _RePaintQuadTreeInsertsAndRemovesBenchmark extends BenchmarkBase { _RePaintQuadTreeInsertsAndRemovesBenchmark() - : super('RePaint QuadTree inserts & removes'); + : super('RePaint QuadTree inserts & removes'); late QuadTree qt; @@ -251,7 +240,7 @@ class _RePaintQuadTreeInsertsAndRemovesBenchmark extends BenchmarkBase { class _FlameQuadTreeInsertsAndRemovesBenchmark extends BenchmarkBase { _FlameQuadTreeInsertsAndRemovesBenchmark() - : super('Flame QuadTree inserts & removes'); + : super('Flame QuadTree inserts & removes'); static final Vector2 _size = Vector2.all(10); late flame.QuadTree qt; @@ -368,8 +357,10 @@ class _FlameQuadTreeQueryBenchmark extends BenchmarkBase { _FlameQuadTreeQueryBenchmark() : super('Flame QuadTree query'); late flame.QuadTree qt; - static final camera = - flame.RectangleHitbox(size: Vector2.all(500), position: Vector2.all(250)); + static final camera = flame.RectangleHitbox( + size: Vector2.all(500), + position: Vector2.all(250), + ); @override void setup() { @@ -479,7 +470,7 @@ class _FlameQuadTreeMoveBenchmark extends BenchmarkBase { class _RePaintQuadTreeCapacityBenchmark extends BenchmarkBase { _RePaintQuadTreeCapacityBenchmark(this.capacity) - : super('RePaint QuadTree capacity: $capacity'); + : super('RePaint QuadTree capacity: $capacity'); late QuadTree qt; final int capacity; diff --git a/test/unit/quadtree_test.dart b/test/unit/quadtree_test.dart index a700f45..4b3b984 100644 --- a/test/unit/quadtree_test.dart +++ b/test/unit/quadtree_test.dart @@ -5,216 +5,189 @@ import 'package:repaint/repaint.dart'; import 'package:test/test.dart'; void main() => group('Quadtree', () { - test('Bytes', () { - const capacity = 10; - const objectSize = 5; - const nodeSize = capacity * objectSize; + test('Bytes', () { + const capacity = 10; + const objectSize = 5; + const nodeSize = capacity * objectSize; - final data = Float32List(64 * nodeSize); - final ids = Uint32List.sublistView(data); - expect(ids.length, data.length); + final data = Float32List(64 * nodeSize); + final ids = Uint32List.sublistView(data); + expect(ids.length, data.length); - // First object in node 0 - ids[0] = 0; - data - ..[1] = 10 - ..[2] = 10 - ..[3] = 48 - ..[4] = 24; + // First object in node 0 + ids[0] = 0; + data + ..[1] = 10 + ..[2] = 10 + ..[3] = 48 + ..[4] = 24; - // First object in node 1 - ids[nodeSize + 0] = 1; - data - ..[nodeSize + 1] = 20 - ..[nodeSize + 2] = 30 - ..[nodeSize + 3] = 12 - ..[nodeSize + 4] = 32; + // First object in node 1 + ids[nodeSize + 0] = 1; + data + ..[nodeSize + 1] = 20 + ..[nodeSize + 2] = 30 + ..[nodeSize + 3] = 12 + ..[nodeSize + 4] = 32; - var id = 0; - var idsView = Uint32List.sublistView( - data, - id * nodeSize, - id * nodeSize + nodeSize, - ); - var dataView = Float32List.sublistView( - data, - id * nodeSize, - id * nodeSize + nodeSize, - ); + var id = 0; + var idsView = Uint32List.sublistView( + data, + id * nodeSize, + id * nodeSize + nodeSize, + ); + var dataView = Float32List.sublistView( + data, + id * nodeSize, + id * nodeSize + nodeSize, + ); - expect(dataView.length, nodeSize); - expect(idsView.length, dataView.length); - expect(idsView[0], 0); - expect(dataView[1], 10); - expect(dataView[2], 10); - expect(dataView[3], 48); - expect(dataView[4], 24); + expect(dataView.length, nodeSize); + expect(idsView.length, dataView.length); + expect(idsView[0], 0); + expect(dataView[1], 10); + expect(dataView[2], 10); + expect(dataView[3], 48); + expect(dataView[4], 24); - id = 1; - idsView = Uint32List.sublistView( - data, - id * nodeSize, - id * nodeSize + nodeSize, - ); - dataView = Float32List.sublistView( - data, - id * nodeSize, - id * nodeSize + nodeSize, - ); + id = 1; + idsView = Uint32List.sublistView( + data, + id * nodeSize, + id * nodeSize + nodeSize, + ); + dataView = Float32List.sublistView( + data, + id * nodeSize, + id * nodeSize + nodeSize, + ); - expect(idsView.length, nodeSize); - expect(idsView[0], 1); - expect(dataView.length, nodeSize); - expect(dataView[1], 20); - expect(dataView[2], 30); - expect(dataView[3], 12); - expect(dataView[4], 32); - }); + expect(idsView.length, nodeSize); + expect(idsView[0], 1); + expect(dataView.length, nodeSize); + expect(dataView[1], 20); + expect(dataView[2], 30); + expect(dataView[3], 12); + expect(dataView[4], 32); + }); - test('Create', () { - expect( - () => QuadTree( - boundary: const ui.Rect.fromLTWH(0, 0, 100, 100), - capacity: 6, - ), - returnsNormally, - ); - }); + test('Create', () { + expect( + () => QuadTree( + boundary: const ui.Rect.fromLTWH(0, 0, 100, 100), + capacity: 6, + ), + returnsNormally, + ); + }); - test('Insert', () { - final qt = QuadTree( - boundary: const ui.Rect.fromLTWH(0, 0, 100, 100), - capacity: 6, - ); - expect(qt.length, equals(0)); - expect( - () => qt.insert(const ui.Rect.fromLTWH(10, 10, 10, 10)), - returnsNormally, - ); - expect(qt.length, equals(1)); - expect( - () => qt.insert(const ui.Rect.fromLTWH(10, 10, 10, 10)), - returnsNormally, - ); - expect(qt.length, equals(2)); - }); + test('Insert', () { + final qt = QuadTree( + boundary: const ui.Rect.fromLTWH(0, 0, 100, 100), + capacity: 6, + ); + expect(qt.length, equals(0)); + expect( + () => qt.insert(const ui.Rect.fromLTWH(10, 10, 10, 10)), + returnsNormally, + ); + expect(qt.length, equals(1)); + expect( + () => qt.insert(const ui.Rect.fromLTWH(10, 10, 10, 10)), + returnsNormally, + ); + expect(qt.length, equals(2)); + }); - test('Move', () { - final qt = QuadTree( - boundary: const ui.Rect.fromLTWH(0, 0, 100, 100), - capacity: 6, - ); - final id = qt.insert(const ui.Rect.fromLTWH(10, 10, 10, 10)); - expect(id, isNotNull); - expect(() => qt.move(id, 10, 10), returnsNormally); - expect(() => qt.move(id, 20, 20), returnsNormally); - expect(qt.length, equals(1)); - }); + test('Move', () { + final qt = QuadTree( + boundary: const ui.Rect.fromLTWH(0, 0, 100, 100), + capacity: 6, + ); + final id = qt.insert(const ui.Rect.fromLTWH(10, 10, 10, 10)); + expect(id, isNotNull); + expect(() => qt.move(id, 10, 10), returnsNormally); + expect(() => qt.move(id, 20, 20), returnsNormally); + expect(qt.length, equals(1)); + }); - test('Remove', () { - final qt = QuadTree( - boundary: const ui.Rect.fromLTWH(0, 0, 100, 100), - capacity: 6, - ); - final id = qt.insert(const ui.Rect.fromLTWH(10, 10, 10, 10)); - expect(id, isNotNull); - expect(qt.length, equals(1)); - expect(() => qt.remove(id), returnsNormally); - expect(qt.length, equals(0)); - }); + test('Remove', () { + final qt = QuadTree( + boundary: const ui.Rect.fromLTWH(0, 0, 100, 100), + capacity: 6, + ); + final id = qt.insert(const ui.Rect.fromLTWH(10, 10, 10, 10)); + expect(id, isNotNull); + expect(qt.length, equals(1)); + expect(() => qt.remove(id), returnsNormally); + expect(qt.length, equals(0)); + }); - test('Query', () { - final qt = QuadTree( - boundary: const ui.Rect.fromLTWH(0, 0, 100, 100), - capacity: 6, - )..insert(const ui.Rect.fromLTWH(10, 10, 10, 10)); - expect( - () => qt.query(const ui.Rect.fromLTWH(10, 10, 10, 10)), - returnsNormally, - ); - var result = qt.query(const ui.Rect.fromLTWH(10, 10, 10, 10)); - expect( - result, - isA() - .having( - (qr) => qr.isEmpty, - 'isEmpty', - isFalse, - ) - .having( - (qr) => qr.isNotEmpty, - 'isNotEmpty', - isTrue, - ) - .having( - (qr) => qr.length, - 'length', - 1, - ), - ); - final map = result.toMap(); - expect( - map, - allOf( - isA>(), - hasLength(1), - ), - ); - }); + test('Query', () { + final qt = QuadTree( + boundary: const ui.Rect.fromLTWH(0, 0, 100, 100), + capacity: 6, + )..insert(const ui.Rect.fromLTWH(10, 10, 10, 10)); + expect( + () => qt.query(const ui.Rect.fromLTWH(10, 10, 10, 10)), + returnsNormally, + ); + var result = qt.query(const ui.Rect.fromLTWH(10, 10, 10, 10)); + expect( + result, + isA() + .having((qr) => qr.isEmpty, 'isEmpty', isFalse) + .having((qr) => qr.isNotEmpty, 'isNotEmpty', isTrue) + .having((qr) => qr.length, 'length', 1), + ); + final map = result.toMap(); + expect(map, allOf(isA>(), hasLength(1))); + }); - test('Create/Move/Query/Remove/Optimize/Clear', () { - final qt = QuadTree( - boundary: const ui.Rect.fromLTWH(0, 0, 10000, 50000), - capacity: 6, - ); - expect(qt.length, equals(0)); - expect(qt.nodes, equals(0)); - for (var i = 0; i < 10; i++) { - expect( - () => qt.insert(ui.Rect.fromLTWH(i * 10.0, i * 10.0, 10, 10)), - returnsNormally, - ); - } - expect(qt.length, equals(10)); - expect(qt.nodes, greaterThan(0)); - expect(() => qt.get(10), returnsNormally); - expect( - qt.get(10), - allOf( - isNotNull, - isA(), - ), - ); - for (var i = 0; i < 10; i++) { - expect( - () => qt.move(10, i * 10.0, i * 10.0), - returnsNormally, - ); - } - expect(qt.length, equals(10)); - expect( - qt.query(const ui.Rect.fromLTWH(5, 5, 1000, 1000)).length, - equals(10), - ); - expect(qt.optimize, returnsNormally); - expect( - qt.query(const ui.Rect.fromLTWH(5, 5, 1000, 1000)).length, - equals(10), - ); - expect(qt.length, equals(10)); - expect(() => qt.remove(9), returnsNormally); - expect(() => qt.remove(5), returnsNormally); - expect(qt.length, equals(8)); - expect( - qt.queryIds(const ui.Rect.fromLTWH(-10000, -10000, 20000, 20000)), - allOf( - isA>(), - hasLength(8), - containsAll([0, 1, 2, 3, 4, 6, 7, 8]), - ), - ); - expect(qt.optimize, returnsNormally); - expect(qt.healthCheck(), isEmpty); - expect(qt.clear, returnsNormally); - }); - }); + test('Create/Move/Query/Remove/Optimize/Clear', () { + final qt = QuadTree( + boundary: const ui.Rect.fromLTWH(0, 0, 10000, 50000), + capacity: 6, + ); + expect(qt.length, equals(0)); + expect(qt.nodes, equals(0)); + for (var i = 0; i < 10; i++) { + expect( + () => qt.insert(ui.Rect.fromLTWH(i * 10.0, i * 10.0, 10, 10)), + returnsNormally, + ); + } + expect(qt.length, equals(10)); + expect(qt.nodes, greaterThan(0)); + expect(() => qt.get(10), returnsNormally); + expect(qt.get(10), allOf(isNotNull, isA())); + for (var i = 0; i < 10; i++) { + expect(() => qt.move(10, i * 10.0, i * 10.0), returnsNormally); + } + expect(qt.length, equals(10)); + expect( + qt.query(const ui.Rect.fromLTWH(5, 5, 1000, 1000)).length, + equals(10), + ); + expect(qt.optimize, returnsNormally); + expect( + qt.query(const ui.Rect.fromLTWH(5, 5, 1000, 1000)).length, + equals(10), + ); + expect(qt.length, equals(10)); + expect(() => qt.remove(9), returnsNormally); + expect(() => qt.remove(5), returnsNormally); + expect(qt.length, equals(8)); + expect( + qt.queryIds(const ui.Rect.fromLTWH(-10000, -10000, 20000, 20000)), + allOf( + isA>(), + hasLength(8), + containsAll([0, 1, 2, 3, 4, 6, 7, 8]), + ), + ); + expect(qt.optimize, returnsNormally); + expect(qt.healthCheck(), isEmpty); + expect(qt.clear, returnsNormally); + }); +}); diff --git a/test/unit/repaint_test.dart b/test/unit/repaint_test.dart index ee2e8c4..81baa9d 100644 --- a/test/unit/repaint_test.dart +++ b/test/unit/repaint_test.dart @@ -5,72 +5,56 @@ import 'package:test/test.dart'; import '../fake/repainter_fake.dart'; void main() => group('Repaint', () { - test('Instance', () { - final widget = RePaint(painter: RePainterFake()); - expect( - widget, + test('Instance', () { + final widget = RePaint(painter: RePainterFake()); + expect( + widget, + allOf( + isNotNull, + isA(), + isA(), + isA().having( + (w) => w.painter, + 'painter', allOf( isNotNull, - isA(), - isA(), - isA().having( - (w) => w.painter, - 'painter', - allOf( - isNotNull, - isA(), - isA(), - isA(), - ), - ), - ), - ); - expect( - widget.createElement(), - isA().having( - (e) => e.widget, - 'widget', - same(widget), + isA(), + isA(), + isA(), ), - ); - expect( - widget.createRenderObject(RePaintElement(widget)), - isA() - .having( - (r) => r.painter, - 'painter', - allOf( - isNotNull, - isA(), - isA(), - isA(), - ), - ) - .having( - (r) => r.context, - 'context', - allOf( - isNotNull, - isA(), - isA(), - isA(), - ), - ) - .having( - (r) => r.isRepaintBoundary, - 'isRepaintBoundary', - true, - ) - .having( - (r) => r.painter, - 'painter', - same(widget.painter), - ) - .having( - (r) => r.size, - 'size', - equals(Size.zero), - ), - ); - }); - }); + ), + ), + ); + expect( + widget.createElement(), + isA().having((e) => e.widget, 'widget', same(widget)), + ); + expect( + widget.createRenderObject(RePaintElement(widget)), + isA() + .having( + (r) => r.painter, + 'painter', + allOf( + isNotNull, + isA(), + isA(), + isA(), + ), + ) + .having( + (r) => r.context, + 'context', + allOf( + isNotNull, + isA(), + isA(), + isA(), + ), + ) + .having((r) => r.isRepaintBoundary, 'isRepaintBoundary', true) + .having((r) => r.painter, 'painter', same(widget.painter)) + .having((r) => r.size, 'size', equals(Size.zero)), + ); + }); +}); diff --git a/test/unit/util_test.dart b/test/unit/util_test.dart index f896aaa..8a54d7f 100644 --- a/test/unit/util_test.dart +++ b/test/unit/util_test.dart @@ -1,37 +1,37 @@ import 'package:test/test.dart'; void main() => group('Util', () { - group('Time', () { - test('Seconds', () { - const duration = Duration(seconds: 5); - expect( - duration.inSeconds * Duration.millisecondsPerSecond, - equals(5 * 1000), - ); - expect( - duration.inSeconds * - Duration.millisecondsPerSecond * - Duration.microsecondsPerMillisecond, - equals(5 * 1000 * 1000), - ); - expect( - duration.inSeconds * Duration.microsecondsPerSecond, - equals(5 * 1000 * 1000), - ); - }); + group('Time', () { + test('Seconds', () { + const duration = Duration(seconds: 5); + expect( + duration.inSeconds * Duration.millisecondsPerSecond, + equals(5 * 1000), + ); + expect( + duration.inSeconds * + Duration.millisecondsPerSecond * + Duration.microsecondsPerMillisecond, + equals(5 * 1000 * 1000), + ); + expect( + duration.inSeconds * Duration.microsecondsPerSecond, + equals(5 * 1000 * 1000), + ); + }); - test('Delta', () { - const elapsed = Duration(seconds: 15); - const previous = Duration(seconds: 14); - final delta = elapsed - previous; - final ms = delta.inMicroseconds / Duration.microsecondsPerMillisecond; - expect(ms, equals(1000)); - }); + test('Delta', () { + const elapsed = Duration(seconds: 15); + const previous = Duration(seconds: 14); + final delta = elapsed - previous; + final ms = delta.inMicroseconds / Duration.microsecondsPerMillisecond; + expect(ms, equals(1000)); + }); - test('Frame rate', () { - const frameRate = 60; - const deltaMs = 1000 / frameRate; - expect(deltaMs, equals(1000 / 60)); - }); - }); + test('Frame rate', () { + const frameRate = 60; + const deltaMs = 1000 / frameRate; + expect(deltaMs, equals(1000 / 60)); }); + }); +}); diff --git a/test/widget_test.dart b/test/widget_test.dart index d5140ba..be390e0 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -6,135 +6,103 @@ import 'fake/repainter_fake.dart'; void main() { group('Widget', () { - testWidgets( - 'Pump RePaint', - (tester) async { - final painter = RePainterFake(); - final widget = RePaint(painter: painter); - await tester.pumpWidget(widget); - expect(find.byWidget(widget), allOf(isNotNull, findsOneWidget)); - expect( - find.byType(RePaint).evaluate(), - allOf(isNotNull, isNotEmpty, hasLength(1)), - ); - final context = find.byType(RePaint).evaluate().single; - expect( - context, - allOf( - isNotNull, - isA(), - isA() - .having( - (e) => e.widget, - 'widget', - same(widget), - ) - .having( - (e) => e.renderObject, - 'renderObject', - allOf( - isNotNull, - isA(), - isA(), - ), + testWidgets('Pump RePaint', (tester) async { + final painter = RePainterFake(); + final widget = RePaint(painter: painter); + await tester.pumpWidget(widget); + expect(find.byWidget(widget), allOf(isNotNull, findsOneWidget)); + expect( + find.byType(RePaint).evaluate(), + allOf(isNotNull, isNotEmpty, hasLength(1)), + ); + final context = find.byType(RePaint).evaluate().single; + expect( + context, + allOf( + isNotNull, + isA(), + isA() + .having((e) => e.widget, 'widget', same(widget)) + .having( + (e) => e.renderObject, + 'renderObject', + allOf(isNotNull, isA(), isA()), + ), + ), + ); + await tester.pumpWidget(RePaint(painter: painter)); + expect(find.byType(RePaint), findsOneWidget); + final box = + find.byType(RePaint).evaluate().single.renderObject as RePaintBox; + expect( + box, + allOf( + isNotNull, + isA(), + isA() + .having( + (r) => r.context.widget, + 'painter', + allOf( + isNotNull, + isA(), + isA(), + isA(), + isNot(same(widget)), + ), + ) + .having( + (r) => r.painter, + 'painter', + allOf( + isNotNull, + isA(), + isA(), + isA(), + same(painter), ), - ), - ); - await tester.pumpWidget(RePaint(painter: painter)); - expect(find.byType(RePaint), findsOneWidget); - final box = - find.byType(RePaint).evaluate().single.renderObject as RePaintBox; - expect( - box, - allOf( - isNotNull, - isA(), - isA() - .having( - (r) => r.context.widget, - 'painter', - allOf( - isNotNull, - isA(), - isA(), - isA(), - isNot(same(widget)), - ), - ) - .having( - (r) => r.painter, - 'painter', - allOf( - isNotNull, - isA(), - isA(), - isA(), - same(painter), - ), - ) - .having( - (r) => r.context, - 'context', - allOf( - isNotNull, - isA(), - isA(), - isA(), - same(context), - ), - ) - .having( - (r) => r.isRepaintBoundary, - 'isRepaintBoundary', - true, - ) - .having( - (r) => r.painter, - 'painter', - same(widget.painter), - ) - .having( - (r) => r.attached, - 'attached', - true, - ) - .having( - (r) => r.owner, - 'owner', + ) + .having( + (r) => r.context, + 'context', + allOf( isNotNull, - ) - .having( - (r) => r.hasSize, - 'hasSize', - isTrue, - ) - .having( - (r) => r.size, - 'size', - allOf( - isNotNull, - isA(), - isNot(equals(Size.zero)), - ), + isA(), + isA(), + isA(), + same(context), ), - ), - ); - await tester.pumpWidget(const SizedBox.shrink()); - expect(find.byWidget(widget), findsNothing); - expect(box.attached, isFalse); - }, - ); + ) + .having((r) => r.isRepaintBoundary, 'isRepaintBoundary', true) + .having((r) => r.painter, 'painter', same(widget.painter)) + .having((r) => r.attached, 'attached', true) + .having((r) => r.owner, 'owner', isNotNull) + .having((r) => r.hasSize, 'hasSize', isTrue) + .having( + (r) => r.size, + 'size', + allOf(isNotNull, isA(), isNot(equals(Size.zero))), + ), + ), + ); + await tester.pumpWidget(const SizedBox.shrink()); + expect(find.byWidget(widget), findsNothing); + expect(box.attached, isFalse); + }); testWidgets('Implicit', (tester) async { - await tester.pumpWidget(RePaint.inline( - setUp: (box) => Paint()..color = Colors.red, - frameRate: 60, - update: (box, paint, _) => - paint..color = paint.color == Colors.red ? Colors.blue : Colors.red, - render: (box, paint, canvas) => - canvas.drawRect(Offset.zero & box.size, paint), - tearDown: (paint) {}, - )); + await tester.pumpWidget( + RePaint.inline( + setUp: (box) => Paint()..color = Colors.red, + frameRate: 60, + update: (box, paint, _) => + paint + ..color = paint.color == Colors.red ? Colors.blue : Colors.red, + render: (box, paint, canvas) => + canvas.drawRect(Offset.zero & box.size, paint), + tearDown: (paint) {}, + ), + ); expect(find.byType(RePaint), findsOneWidget); await tester.pump(const Duration(milliseconds: 32)); expect(find.byType(RePaint), findsOneWidget); @@ -143,9 +111,7 @@ void main() { }); testWidgets('Implicit empty', (tester) async { - await tester.pumpWidget(RePaint.inline( - render: (_, __, ___) {}, - )); + await tester.pumpWidget(RePaint.inline(render: (_, __, ___) {})); expect(find.byType(RePaint), findsOneWidget); await tester.pump(const Duration(milliseconds: 32)); expect(find.byType(RePaint), findsOneWidget);