Skip to content

Render loop stops under certain conditions #3631

Open
@Wunderwuzzy

Description

@Wunderwuzzy

What happened?

I turned my device (physcial and emulator) (from portraint to landscape mode) and the render loop stops. This also means that there aren't any visual updates to the game anymore. This behavior seems to be the case when Flame is inside an OrientationBuilder widget.

What do you expect?

I expect that the render loop does NOT stop when I turn the device (rotate the screen).
Run the app, you see the render loop counter going up, both on screen and in the console. You can even toggle visibility of this. Rotate the screen. The render loop counter stops, the visibility toggling also doesn't work anymore.

How can we reproduce this?

Use the code below. Flame v1.29.0 was used.

What steps should take to fix this?

No response

Do have an example of where the bug occurs?

import 'dart:async';
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flame Rotation Bug Test'),
          backgroundColor: Colors.blue,
        ),
        body: SafeArea(
          child: OrientationBuilder(
            builder: (context, orientation) {
              return orientation == Orientation.portrait
                  ? _buildPortraitLayout()
                  : _buildLandscapeLayout();
            },
          ),
        ),
      ),
    );
  }

  RotationTestGameWithButton game = RotationTestGameWithButton();

  Widget _buildPortraitLayout() {
    return Column(
      children: [
        Expanded(
          flex: 1,
          child: GameWidget(game: game),
        ),
        Expanded(
          flex: 1,
          child: TestControlsContainer(game: game),
        ),
      ],
    );
  }

  Widget _buildLandscapeLayout() {
    return Row(
      children: [
        Expanded(
          flex: 1,
          child: GameWidget(game: game),
        ),
        Expanded(
          flex: 1,
          child: TestControlsContainer(game: game),
        ),
      ],
    );
  }
}

class TestControlsContainer extends StatelessWidget {
  final RotationTestGameWithButton game;

  TestControlsContainer({required this.game});

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.grey[200],
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text(
            'Rotation Bug Test Controls',
            style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
          ),
          SizedBox(height: 20),
          ElevatedButton(
            onPressed: () => game.toggleTestVisibility(),
            child: Text('Toggle Visibility'),
          ),
          SizedBox(height: 10),
          Text(
            'Rotate device to test!\nWatch debug console for render counts.',
            textAlign: TextAlign.center,
            style: TextStyle(fontSize: 14),
          ),
          SizedBox(height: 20),
          Container(
            padding: EdgeInsets.all(16),
            margin: EdgeInsets.all(16),
            decoration: BoxDecoration(
              color: Colors.blue[50],
              borderRadius: BorderRadius.circular(8),
              border: Border.all(color: Colors.blue),
            ),
            child: Text(
              'Expected Bug:\n'
                  '1. Before rotation: render() runs continuously\n'
                  '2. After rotation: render() stops or runs only once\n'
                  '3. Toggle button may not work properly',
              style: TextStyle(fontSize: 12),
            ),
          ),
        ],
      ),
    );
  }
}

class RotationTestGameWithButton extends FlameGame {
  TestRenderComponent? _testComponent;

  @override
  Color backgroundColor() => const Color(0xFF222222);

  @override
  FutureOr<void> onLoad() async {
    debugPrint('=== GAME ONLOAD ===');

    final camera = CameraComponent(world: world);
    camera.viewfinder.anchor = Anchor.topLeft;
    camera.viewport.anchor = Anchor.topLeft;
    await addAll([world, camera]);

    _testComponent = TestRenderComponent();
    world.add(_testComponent!);

    debugPrint('=== GAME ONLOAD COMPLETE ===');
  }

  @override
  void onGameResize(Vector2 size) {
    debugPrint('=== GAME RESIZE: $size ===');
    super.onGameResize(size);
    _testComponent?.onParentResize(size);
  }

  void toggleTestVisibility() {
    debugPrint('=== TOGGLE VISIBILITY CALLED ===');
    _testComponent?.toggleVisibility();
  }
}

class TestRenderComponent extends Component {
  int renderCount = 0;
  bool visible = true;

  @override
  void render(Canvas canvas) {
    renderCount++;
    debugPrint('TestRenderComponent.render() called #$renderCount, visible: $visible');

    if (!visible) return;

    final paint = Paint()..color = Colors.green;
    final size = Vector2(100, 100);

    canvas.drawRect(
        Rect.fromLTWH(50, 50, size.x, size.y),
        paint
    );

    final textStyle = TextStyle(
      color: Colors.white,
      fontSize: 20,
      fontWeight: FontWeight.bold,
    );

    final textPainter = TextPainter(
      text: TextSpan(text: 'Renders: $renderCount', style: textStyle),
      textAlign: TextAlign.center,
      textDirection: TextDirection.ltr,
    );

    textPainter.layout();
    textPainter.paint(canvas, Offset(60, 170));
  }

  @override
  void update(double dt) {
    super.update(dt);

    if (renderCount % 60 == 0 && renderCount > 0) {
      debugPrint('TestRenderComponent.update() - renderCount: $renderCount');
    }
  }

  void onParentResize(Vector2 newSize) {
    debugPrint('TestRenderComponent: Parent resized to $newSize');
  }

  void toggleVisibility() {
    visible = !visible;
    debugPrint('TestRenderComponent: visibility toggled to $visible');
  }
}

Execute in a terminal and put output into the code block below

Output of: flutter doctor -v

[√] Flutter (Channel stable, 3.27.1, on Microsoft Windows [Version 10.0.19045.5965], locale de-DE)
• Flutter version 3.27.1 on channel stable at D:\flutter
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision 17025dd882 (6 months ago), 2024-12-17 03:23:09 +0900
• Engine revision cb4b5fff73
• Dart version 3.6.0
• DevTools version 2.40.2

[√] Windows Version (Installed version of Windows is version 10 or higher)

[√] Android toolchain - develop for Android devices (Android SDK version 35.0.0)
• Android SDK at C:\Users\MyUserName\AppData\Local\Android\sdk
• Platform android-35, build-tools 35.0.0
• Java binary at: D:\Android Studio\jbr\bin\java
• Java version OpenJDK Runtime Environment (build 21.0.6+-13368085-b895.109)
• All Android licenses accepted.

[X] Chrome - develop for the web (Cannot find Chrome executable at .\Google\Chrome\Application\chrome.exe)
! Cannot find Chrome. Try setting CHROME_EXECUTABLE to a Chrome executable.

[X] Visual Studio - develop Windows apps
X Visual Studio not installed; this is necessary to develop Windows apps.
Download at https://visualstudio.microsoft.com/downloads/.
Please install the "Desktop development with C++" workload, including all of its default components

[√] Android Studio (version 2024.3.2)
• Android Studio at D:\Android Studio
• Flutter plugin can be installed from:
https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
https://plugins.jetbrains.com/plugin/6351-dart
• Java version OpenJDK Runtime Environment (build 21.0.6+-13368085-b895.109)

[√] Connected device (2 available)
• Windows (desktop) • windows • windows-x64 • Microsoft Windows [Version 10.0.19045.5965]
• Edge (web) • edge • web-javascript • Microsoft Edge 134.0.3124.51

[√] Network resources
• All expected network resources are available.

Affected platforms

All

Other information

No response

Are you interested in working on a PR for this?

  • I want to work on this

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions