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

feat: Add HasPerformanceTracker mixin on Game #3043

Merged
merged 8 commits into from
Feb 18, 2024
15 changes: 15 additions & 0 deletions doc/flame/game.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> onLoad() async {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> onLoad() async {
position = game.size / 2;
add(CircleHitbox());
add(
bulletCreator = TimerComponent(
Expand Down
46 changes: 31 additions & 15 deletions examples/games/rogue_shooter/lib/rogue_shooter_game.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> 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,
Expand All @@ -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++;
}
}
1 change: 1 addition & 0 deletions packages/flame/lib/game.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
34 changes: 34 additions & 0 deletions packages/flame/lib/src/game/mixins/has_performance_tracker.dart
Original file line number Diff line number Diff line change
@@ -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;
}
}
46 changes: 46 additions & 0 deletions packages/flame/test/game/mixins/has_performance_tracker_test.dart
Original file line number Diff line number Diff line change
@@ -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);
}
Loading