Skip to content

feat: Add reload, restart and logs commands to flame_cli - #4056

Merged
spydon merged 6 commits into
mainfrom
feat/flame-cli-run
Sep 30, 2026
Merged

spydon merged 6 commits into
mainfrom
feat/flame-cli-run

Conversation

@spydon

@spydon spydon commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

Description

Stacked on #4055. This closes the edit, reload, check loop for scripts and agents, which
previously still needed a person to press r in the terminal of flutter run.

  • flame run now starts flutter run with piped standard streams instead of inheriting them. A
    RunController mirrors the output to the terminal and to .dart_tool/flame/log, forwards the
    terminal input to flutter run (putting the terminal in the same raw mode that flutter run
    uses, and restoring it afterwards), and listens on a loopback port that it writes to
    .dart_tool/flame/control_port. The port and the URI file are removed when the game stops, the
    log is kept.
  • flame reload and flame restart connect to that port. flame run presses r or R on
    their behalf and answers with the line that flutter run prints when it is done (Reloaded ...,
    Restarted application ... or Try again after fixing the above error(s).), together with the
    output in between. A failed reload prints the compiler errors and exits with 70. Requests are
    serialized, and there is a two minute timeout in case flutter run never answers.
  • flame logs [--lines N] [--follow] prints the recorded output, which includes everything the
    game printed and the exceptions it threw, also after a crash.
  • The project files are now handled by one project_files.dart, with the old
    vm_service_uri_file.dart names kept as thin wrappers. flame run keeps them in the root of
    the project (the closest pubspec.yaml), so a running game is tied to its project: different
    projects, including different git worktrees of the same game, are separate instances.
  • Several games can run from the same project at once, for example on two devices, and the
    commands go to the one started last. A RunController owns the files while control_port
    holds its port; when a newer run takes over it stops writing to the log and leaves the files
    alone when it exits, and when the newer run stops it takes the files back, including the URI
    it remembered, so the older game becomes reachable again. Any game in the project can also be
    addressed explicitly: --uri on the commands that talk to the game, and --port on reload
    and restart, using the control port that flame run prints when it starts.
  • flame run --help prints an introduction about what it adds before handing over to the help
    of flutter run.

The docs describe the three files, the new commands, how to run flame run in the background of
an interactive shell (redirect its input from /dev/null, like flutter run), and that a hot
reload keeps component state so constructor changes need a restart.

Tests cover the controller with a fake process (port file lifecycle, r and R being pressed,
success, failure with output, timeout, process exiting during a request, unknown requests), the
commands' error paths and logs. I also verified it end to end on the flutter-tester device:
flame reload after a code change, a broken change reporting the compiler error with exit code
70, flame restart, flame logs, and both commands after the game had stopped. With two games
from the same project, flame reload from a subdirectory reached the second one and only its
output landed in the log, and after stopping it the first game took the project back and
flame reload reached it again.

Checklist

  • I have followed the Contributor Guide when preparing my PR.
  • I have updated/added tests for ALL new/updated/fixed functionality.
  • I have updated/added relevant documentation in docs and added dartdoc comments with ///.
  • [-] I have updated/added relevant examples in examples or docs.

Breaking Change?

  • Yes, this PR is a breaking change.
  • No, this PR is not a breaking change.

Related Issues

@spydon
spydon added this pull request to stack #4058 September 25, 2026 16:13
@spydon
spydon force-pushed the feat/flame-cli-run branch 2 times, most recently from 303dbe8 to 1c6c5a3 Compare September 30, 2026 13:11
Base automatically changed from feat/flame-cli-control to main September 30, 2026 13:14
spydon and others added 6 commits September 30, 2026 15:14
…ds to flame_cli (#4055)

Stacked on #4054. This adds the commands that let a script or an AI
agent control and inspect a
running game, so that a snapshot can be taken of a stable, known state:

- `flame pause`, `flame resume` and `flame step [--frames N] [--time
S]`, backed by the existing
game loop service extensions. `step` pauses the game first if it is
running.
- `flame inspect <id> [--json]` prints the type, parent, child count,
debug mode and attributes
of a component, through a new `ext.flame_devtools.getComponentInfo`
service extension.
- `flame set <id> --position x,y --size w,h --angle a --scale s --anchor
name --priority p`
changes a component and prints it afterwards.
`setPositionComponentAttributes` now also
accepts `anchor` and `priority` (the latter for any component),
validates the value, and
responds with JSON instead of the plain string `Success`, which is not
valid JSON and made
  `vm_service` clients wait forever.
- `flame debug [on|off] [--component <id>]` shows or changes the debug
mode.
- `flame overlays [--json]`, `flame overlay show|hide|only <name>`.
`getOverlays` now also
returns the `active` overlays, and a new `ext.flame_devtools.setOverlay`
extension shows or
  hides a single overlay without touching the others.
- `flame tree` now shows the attributes of every component (position,
size, angle, scale,
anchor, priority, with defaults left out), and gets `--filter <regex>`,
`--depth <n>` and
`--json`. The `ComponentTreeNode` carries an `attributes` map, which the
DevTools extension
  ignores, and `fromJson` accepts trees without it.

All of this is documented on the Flame CLI page, and the service
extension reference in the
debugging page now lists every extension.

I verified every command end to end against a game on the
`flutter-tester` device started with
`flame run`, including the error paths (unknown component, unknown
overlay, invalid values).
flame run now owns the flutter run process: it forwards the terminal input, mirrors the output to .dart_tool/flame/log, and accepts reload and restart requests on a loopback port written to .dart_tool/flame/control_port. The reload and restart commands send those requests and report the line that flutter run prints when it is done, and logs prints the recorded output.
…ne receiving the commands

flame run keeps its files in the root of the project so that a game is tied to its project. A RunController only owns the files while the control port file holds its port: a superseded run stops writing to the log and leaves the files alone on exit, and takes the project back when the newer run stops.
…rt from flame run

With several games started from the same project, --uri already reaches any of them for the commands that talk to the game, and --port now does the same for reload and restart. flame run prints its control port when it starts so that it is easy to find.
@spydon
spydon merged commit 17c3520 into main Sep 30, 2026
8 checks passed
@spydon
spydon deleted the feat/flame-cli-run branch September 30, 2026 14:53
spydon added a commit that referenced this pull request Sep 30, 2026
Stacked on #4056. This turns the CLI from an observer into something
that can play test a game,
and gives agents a way to compare snapshots that does not rely on
eyeballing two images.

- A new `InputConnector` in `flame` registers `ext.flame_devtools.tap`,
`drag` and `key`. Taps
and drags are delivered through the game's `MultiTapDispatcher` and
`MultiDragScaleDispatcher`,
so they reach `TapCallbacks` and `DragCallbacks` components with the
same propagation as real
input, in canvas coordinates (the same as the pixels of a snapshot).
Keys go to `onKeyEvent` of
a `HasKeyboardHandlerComponents` or `KeyboardEvents` game, with `press`,
`down` and `up`
actions. Key names are matched against the `LogicalKeyboardKey` names
without regard to case,
spaces and underscores (`arrowLeft`, `Arrow Left`, `space`, `a`,
`digit1`). A missing mixin is
reported as an invalid parameters error instead of silently doing
nothing.
- `flame input tap <x,y>`, `flame input drag <x,y> <x,y> [--steps N]`
and
  `flame input key <name> [--down|--up]` call those.
- `getGameSnapshot` takes `world=true` or `rect=x,y,w,h` to render the
world directly without the
camera, so off screen components are visible. Without a rect the image
covers the bounding
rectangle of all the position components in the world. `flame snapshot
--world` and
`--rect` expose it. Snapshotting the `World` component itself used to
give a blank 100x100
image, since `World.renderTree` is a no-op, it now renders the world's
children the same way.
- `flame diff <before.png> <after.png> [--output diff.png] [--threshold
N] [--exit-code]`
reports the percentage of differing pixels and the bounding rectangle of
the change, and can
write an image with the changes in red on a dimmed copy of the second
image. This adds the
  `image` package as a dependency of `flame_cli`.

Tests cover the input connector with real components (tap position, drag
steps, key down and up
with the pressed set, the missing mixin messages, key name lookup),
world bounds and world
snapshots, the diff algorithm and command, and the usage errors of the
new commands. Verified
end to end on the `flutter-tester` device: a tap toggled a component's
color and `flame diff`
reported exactly the 200x100 rectangle at 300,250 with exit code 1, a
drag and arrow keys moved
the component, and `--world` and `--rect` rendered an off screen
component.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants