Uh oh!
There was an error while loading. Please reload this page.
feat!: Making add,addAll sync methods - #3968
Conversation
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Warn about the deadlock footgun when awaiting a child's loaded/mounted futures from inside the parent's onLoad (both in the components doc and in the Component.add dartdoc) - Keep the classic Future<void> onLoad() async signature in the game.md example and add the missing super.onLoad() call
| ]); | ||
| ]; | ||
| addAll(blobParts); | ||
| await Future.wait(blobParts.map((part) => part.loaded)); |
There was a problem hiding this comment.
trying to think of having a less clunky alternative, just random thoughts (and these can be followups), how about a
await Component.loaded(blobParts)
shape? (just does Future.wait(components.map(it => it.loaded)))
same for the other 3.
There was a problem hiding this comment.
I kinda like that helper, I would be happy to add it to this PR if we all agree on having it and using it around our code.
@spydon you onboard of this helper?
There was a problem hiding this comment.
Hmm... It's a bit strange, doesn't really feel Dart idiomatic, but I don't have another suggestion for now (except for just using Future.wait).
There was a problem hiding this comment.
Maybe an extension on Iterable that would allow us to wait them? Something like:
extensionComponentIterableExtensiononIterable<Component> {
Future<void> loaded =>Future.wait(map((el) => el.loaded));
}Then the developer can just call await myList.loaded, and that would be in pair with how the user wait for a single component to be loaded
| await world.add(Background()); | ||
| final background = Background(); | ||
| world.add(background); | ||
| await background.loaded; |
There was a problem hiding this comment.
to be honest, looking only at the PR it is not clear to me which ones of these need the load after all, and which (many ones) didn't, and way. even wondering if we want to justify the ones that kept the await with a comment on why
There was a problem hiding this comment.
I didn't checked that tbh, I will double check for obvious places where we know it isn't needed!
luanpotter
left a comment
There was a problem hiding this comment.
thanks @erickzanardo , sorry for the delay!
LGTM, aligned on removing. my only two thoughts:
- it is unfortunate we made waiting for a list a bit clunkier than v1. I think we can explore some possible patterns on followups unless we really want to discourage this (but then we shouldn't use it on our own examples either).
- not super clear to me when the await is truly needed. I guess I've been just defaulting to having it over the years, I've had issues of for example an update(dt) failing to find a child w/o it.
in summary: I think we need to be even more crisp on either "no you should never need to do wait add on onLoad, and there is no un-clunked way of doing" or "yes there are some cases it is needed, here they are, here are some helpers with docs"
would love to see that explored on followups!
spydon
left a comment
There was a problem hiding this comment.
Here are some things that we should consider before merging this PR:
Blocking
- Deadlock warning is factually wrong.
add()starts the child loading synchronously (hasLayoutis always true byonLoadtime), soawait child.loadedin the parent'sonLoadworks fine. Onlymounteddeadlocks. Rewrite the warning in both the dartdoc andcomponents.md; the PR's ownflame_test/example/lib/game.dartandgame.mdcontradict it. _failLoadinghangsloaded. Readingloadedafter the error fired, or a second time, hangs forever. Persist the failure (_loadErrorfield or state bit) and haveloadedreturnFuture.error(...).GameWidget.errorBuilderno longer catches child load failures (verified: true on main, false here). Document in migration instructions, which are still commented out.
Should fix
- Dartdoc oversells recoverability: a failed load permanently blocks the lifecycle queue, so catching via
loadedrecovers nothing. Soften the wording, or fix it by returningdonefromhandleLifecycleEventAddonce the failure is persistent. ensureAddhangs instead of throwing on load failure.- Sync
onLoadthrows escapeadd()directly; async ones go through_failLoading. Inconsistent with "safe to call from anywhere, including insideupdate". - No direct test for
_failLoading. The rethrow, late-await, and double-await branches are all uncovered.
Nits
_addChildstill returnsFutureOr<void>that all callers discard; make the discard explicit.- Mention
lifecycleEventsProcessedin the new prose as the batch-wait idiom (answers Luan's clunkiness point;blob_example.dart'sFuture.wait(...loaded)is the shape he disliked).
18b765d to
6307768Compareerickzanardo
commented
Aug 11, 2026
@spydon addressed all, take another look please. |
Releases `flame_forge2d` 0.20.0, scoped to only this package so that the pending unreleased changes in `flame` and the other packages stay unreleased. New version: `flame_forge2d` 0.19.3+7 -> 0.20.0 Included changes: - **FIX**: Adapt to Flutter 3.47 (#3995) - **BREAKING** **FEAT**: Migrate flame_forge2d to the Box2D v3 based forge2d (#3952) The flame-side breaking changes that also touched this package's directory (#3968, #3961) only affected its tests and example, so they are intentionally left out of the changelog: they do not apply to flame_forge2d consumers until flame v2 is released. The package keeps its `flame: ^1.38.0` dependency, verified by resolving against the published flame 1.38.0 and forge2d 0.15.1 (`dart analyze` clean, all 91 tests pass, `flutter pub publish --dry-run` passes). Since melos refuses to version a package whose workspace dependency (`flame`) has pending changes outside the scope filter, the version bump, changelogs, and dependent constraint updates were applied manually in the same format melos generates. On merge, the `release-tag` workflow tags `flame_forge2d-v0.20.0` and triggers the publish workflow for it.
Description
Makes
add,addAllandaddToParentmethods sync.Checklist
docsand added dartdoc comments with///.examplesordocs.Breaking Change?
Yes, this PR is a breaking change.
No, this PR is not a breaking change.
Migration instructions
Component.add,Component.addAllandComponent.addToParentnow returnvoidinstead of afuture. That future only ever covered the child's loading, never its mounting, so awaiting it was
misleading, and forgetting to await it (or to wrap it in
unawaited) tripped thediscarded_futureslint in a lot of games.Drop the
await:If you were relying on the returned future to know when the child had finished loading, await the
child's
loadedfuture instead:Awaiting
loadedis safe from inside the parent's ownonLoad, since the child starts loading assoon as it is added. Awaiting
mounted,removedorgame.lifecycleEventsProcessedthere is not:the parent only mounts after its
onLoadcompletes, so those waits deadlock.For a batch of children, or when you need them to be present in
childrenrather than just loaded,await
game.lifecycleEventsProcessedonce after adding them.Load errors are no longer reported by
GameWidget.errorBuilderGameWidget.errorBuilderused to catch a failing child'sonLoad, becauseawait add(child)chained the child's error onto the game's own
onLoadfuture. That chain is gone: a child thatthrows in
onLoadno longer reacheserrorBuilder.The component is not added to the tree, and the rest of the game keeps running. The error is
reported through the child's
loadedfuture, and if nothing is awaiting it, it is handed to thecurrent
Zoneas an uncaught error. To get the old behavior for a specific child, await itsloadedfuture inside the parent'sonLoad:Related Issues
Indicate which issues this PR resolves, if any. For example:
Part of #1938