Skip to content

perf: Cache decorator and camera render closures instead of allocating per frame - #3979

Open
spydon wants to merge 1 commit into
perf/lifecycle-queue-early-outfrom
perf/render-closure-caching
Open

perf: Cache decorator and camera render closures instead of allocating per frame#3979
spydon wants to merge 1 commit into
perf/lifecycle-queue-early-outfrom
perf/render-closure-caching

Conversation

@spydon

@spydon spydon commented Aug 5, 2026

Copy link
Copy Markdown
Member

Description

The render pass allocated closures every frame: a super.renderTree tear-off per PositionComponent (and HasDecorator/Route/PostProcessComponent) passed to Decorator.applyChain, a chain-forwarding closure inside applyChain itself, and CameraComponent's renderWorld local function per camera. These are now cached fields or instance methods.

Extracted from #3960 so the data-structure change there stands alone (as requested in this comment). Stacked on #3978.

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

Relates to #3957

@spydon
spydon force-pushed the perf/render-closure-caching branch from a984917 to f549bd5 Compare August 5, 2026 20:18
void renderTree(Canvas canvas) {
if (isRendered) {
_renderEffect.applyChain(super.renderTree, canvas);
_renderEffect.applyChain(_superRenderTree ??= super.renderTree, canvas);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is quite unfortunate that the compiler can't optimize this

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

an alternative to consider, make applyChain receive a generic component or superclass with renderTree and have it call the function directly, avoiding the lambda entirely

@override
void renderTree(Canvas canvas) {
decorator.applyChain(super.renderTree, canvas);
decorator.applyChain(_superRenderTree ??= super.renderTree, canvas);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

curious why all the others can't use the has decorator mixin? is there a way to share this logic upstream?

_renderContext.postProcess = postProcess;
}

return (canvas) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't want to tear-off-ify this lambda too?

if (_next == null) {
apply(draw, canvas);
} else {
if (!identical(_chainedDrawSource, draw)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the need for this makes me question if the decorator pattern is the best one for the dart language.
the other tear-offs are ok but this is adding many more layers of complexity, state management and potential for mistakes.
but since this is a hot path for all render calls of all components (regardless of having decorators), this is ok. still trying to think more holistically in the meanwhile

@luanpotter luanpotter left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

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