From 6270353af9a6ec58ee9275ddfa6a8b26276a2c20 Mon Sep 17 00:00:00 2001 From: DevKage <33748002+ufrshubham@users.noreply.github.com> Date: Sun, 18 Feb 2024 23:23:29 +0530 Subject: [PATCH] feat: Add `HasPerformanceTracker` mixin on `Game` (#3043) This PR adds a `HasPerformanceTracker` mixin on `Game`. This mixin will allow for tracking the update and render time of the game. --- doc/flame/game.md | 15 ++++++ .../lib/components/bullet_component.dart | 2 +- .../lib/components/player_component.dart | 8 +--- .../rogue_shooter/lib/rogue_shooter_game.dart | 46 +++++++++++++------ packages/flame/lib/game.dart | 1 + .../game/mixins/has_performance_tracker.dart | 34 ++++++++++++++ .../mixins/has_performance_tracker_test.dart | 46 +++++++++++++++++++ 7 files changed, 130 insertions(+), 22 deletions(-) create mode 100644 packages/flame/lib/src/game/mixins/has_performance_tracker.dart create mode 100644 packages/flame/test/game/mixins/has_performance_tracker_test.dart diff --git a/doc/flame/game.md b/doc/flame/game.md index 51db73c568..350c7bbb34 100644 --- a/doc/flame/game.md +++ b/doc/flame/game.md @@ -244,3 +244,18 @@ class MyGame extends FlameGame { On the current Flutter stable (3.13), this flag is effectively ignored on non-mobile platforms including the web. + + +## HasPerformanceTracker mixin + +While optimizing a game, it can be useful to track the time it took for the game to update and render +each frame. This data can help in detecting areas of the code that are running hot. It can also help +in detecting visual areas of the game that are taking the most time to render. + +To get the update and render times, just add the `HasPerformanceTracker` mixin to the game class. + +```dart +class MyGame extends FlameGame with HasPerformanceTracker { + // access `updateTime` and `renderTime` getters. +} +``` diff --git a/examples/games/rogue_shooter/lib/components/bullet_component.dart b/examples/games/rogue_shooter/lib/components/bullet_component.dart index 5630c29c76..f4d714693a 100644 --- a/examples/games/rogue_shooter/lib/components/bullet_component.dart +++ b/examples/games/rogue_shooter/lib/components/bullet_component.dart @@ -9,7 +9,7 @@ class BulletComponent extends SpriteAnimationComponent final Vector2 deltaPosition = Vector2.zero(); BulletComponent({required super.position, super.angle}) - : super(size: Vector2(10, 20)); + : super(size: Vector2(10, 20), anchor: Anchor.center); @override Future onLoad() async { diff --git a/examples/games/rogue_shooter/lib/components/player_component.dart b/examples/games/rogue_shooter/lib/components/player_component.dart index 46b79ef0ff..0bd242a598 100644 --- a/examples/games/rogue_shooter/lib/components/player_component.dart +++ b/examples/games/rogue_shooter/lib/components/player_component.dart @@ -8,15 +8,11 @@ class PlayerComponent extends SpriteAnimationComponent with HasGameRef, CollisionCallbacks { late TimerComponent bulletCreator; - PlayerComponent() - : super( - size: Vector2(50, 75), - position: Vector2(100, 500), - anchor: Anchor.center, - ); + PlayerComponent() : super(size: Vector2(50, 75), anchor: Anchor.center); @override Future onLoad() async { + position = game.size / 2; add(CircleHitbox()); add( bulletCreator = TimerComponent( diff --git a/examples/games/rogue_shooter/lib/rogue_shooter_game.dart b/examples/games/rogue_shooter/lib/rogue_shooter_game.dart index 3c73f4f36b..1bd2436ea2 100644 --- a/examples/games/rogue_shooter/lib/rogue_shooter_game.dart +++ b/examples/games/rogue_shooter/lib/rogue_shooter_game.dart @@ -6,32 +6,44 @@ import 'package:rogue_shooter/components/player_component.dart'; import 'package:rogue_shooter/components/star_background_creator.dart'; class RogueShooterGame extends FlameGame - with PanDetector, HasCollisionDetection { + with PanDetector, HasCollisionDetection, HasPerformanceTracker { static const String description = ''' A simple space shooter game used for testing performance of the collision detection system in Flame. '''; - late final PlayerComponent player; - late final TextComponent componentCounter; - late final TextComponent scoreText; + late final PlayerComponent _player; + late final TextComponent _componentCounter; + late final TextComponent _scoreText; - int score = 0; + final _updateTime = TextComponent( + text: 'Update time: 0ms', + position: Vector2(0, 0), + priority: 1, + ); + + final TextComponent _renderTime = TextComponent( + text: 'Render time: 0ms', + position: Vector2(0, 25), + priority: 1, + ); + + int _score = 0; @override Future onLoad() async { - add(player = PlayerComponent()); + add(_player = PlayerComponent()); addAll([ FpsTextComponent( position: size - Vector2(0, 50), anchor: Anchor.bottomRight, ), - scoreText = TextComponent( + _scoreText = TextComponent( position: size - Vector2(0, 25), anchor: Anchor.bottomRight, priority: 1, ), - componentCounter = TextComponent( + _componentCounter = TextComponent( position: size, anchor: Anchor.bottomRight, priority: 1, @@ -40,36 +52,40 @@ class RogueShooterGame extends FlameGame add(EnemyCreator()); add(StarBackGroundCreator()); + + addAll([_updateTime, _renderTime]); } @override void update(double dt) { super.update(dt); - scoreText.text = 'Score: $score'; - componentCounter.text = 'Components: ${children.length}'; + _scoreText.text = 'Score: $_score'; + _componentCounter.text = 'Components: ${children.length}'; + _updateTime.text = 'Update time: $updateTime ms'; + _renderTime.text = 'Render time: $renderTime ms'; } @override void onPanStart(_) { - player.beginFire(); + _player.beginFire(); } @override void onPanEnd(_) { - player.stopFire(); + _player.stopFire(); } @override void onPanCancel() { - player.stopFire(); + _player.stopFire(); } @override void onPanUpdate(DragUpdateInfo info) { - player.position += info.delta.global; + _player.position += info.delta.global; } void increaseScore() { - score++; + _score++; } } diff --git a/packages/flame/lib/game.dart b/packages/flame/lib/game.dart index 27fdf508e0..b9f26f8987 100644 --- a/packages/flame/lib/game.dart +++ b/packages/flame/lib/game.dart @@ -11,6 +11,7 @@ export 'src/extensions/vector2.dart'; export 'src/game/flame_game.dart'; export 'src/game/game.dart'; export 'src/game/game_widget/game_widget.dart'; +export 'src/game/mixins/has_performance_tracker.dart'; export 'src/game/mixins/single_game_instance.dart'; export 'src/game/notifying_vector2.dart'; export 'src/game/transform2d.dart'; diff --git a/packages/flame/lib/src/game/mixins/has_performance_tracker.dart b/packages/flame/lib/src/game/mixins/has_performance_tracker.dart new file mode 100644 index 0000000000..afc1f881e7 --- /dev/null +++ b/packages/flame/lib/src/game/mixins/has_performance_tracker.dart @@ -0,0 +1,34 @@ +import 'dart:ui'; + +import 'package:flame/game.dart'; + +/// A mixin that adds performance tracking to a game. +mixin HasPerformanceTracker on Game { + int _updateTime = 0; + int _renderTime = 0; + final _stopwatch = Stopwatch(); + + /// The time it took to update the game in milliseconds. + int get updateTime => _updateTime; + + /// The time it took to render the game in milliseconds. + int get renderTime => _renderTime; + + @override + void update(double dt) { + _stopwatch.reset(); + _stopwatch.start(); + super.update(dt); + _stopwatch.stop(); + _updateTime = _stopwatch.elapsedMilliseconds; + } + + @override + void render(Canvas canvas) { + _stopwatch.reset(); + _stopwatch.start(); + super.render(canvas); + _stopwatch.stop(); + _renderTime = _stopwatch.elapsedMilliseconds; + } +} diff --git a/packages/flame/test/game/mixins/has_performance_tracker_test.dart b/packages/flame/test/game/mixins/has_performance_tracker_test.dart new file mode 100644 index 0000000000..8c89e6fc7f --- /dev/null +++ b/packages/flame/test/game/mixins/has_performance_tracker_test.dart @@ -0,0 +1,46 @@ +import 'dart:io'; +import 'dart:ui'; + +import 'package:flame/components.dart'; +import 'package:flame/game.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets( + 'tracks update and render times.', + (widgetTester) async { + final game = _GameWithPerformanceTracker( + children: [_SlowComponent()], + ); + + expect(game.updateTime, 0); + expect(game.renderTime, 0); + + await widgetTester.pumpFrames( + GameWidget(game: game), + const Duration(seconds: 1), + ); + + expect( + game.updateTime, + greaterThanOrEqualTo(_SlowComponent.duration.inMilliseconds), + ); + expect( + game.renderTime, + greaterThanOrEqualTo(_SlowComponent.duration.inMilliseconds), + ); + }, + ); +} + +class _GameWithPerformanceTracker extends FlameGame with HasPerformanceTracker { + _GameWithPerformanceTracker({super.children}); +} + +class _SlowComponent extends Component { + static const duration = Duration(milliseconds: 8); + @override + void update(double dt) => sleep(duration); + @override + void render(Canvas canvas) => sleep(duration); +}