Skip to content

Reduce Animate shape command memory usage - #55

Open
Tutez64 wants to merge 2 commits into
openfl:masterfrom
Tutez64:optimize/compact-animate-shape-commands
Open

Reduce Animate shape command memory usage#55
Tutez64 wants to merge 2 commits into
openfl:masterfrom
Tutez64:optimize/compact-animate-shape-commands

Conversation

@Tutez64

@Tutez64 Tutez64 commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Summary

This changes how Animate shape commands are stored at runtime:

  • keep parsed commands in a compact numeric representation instead of eagerly
    allocating one AnimateShapeCommand enum object per drawing command;
  • render compact commands directly into Graphics when a shape is first
    instantiated;
  • cache the rendered graphics and release the compact command data afterward;
  • preserve the existing public commands path for manually constructed
    AnimateShapeSymbol instances;
  • add a regression test comparing compact rendering with the legacy command
    representation.

Motivation

Animate exports already represent shape commands as flat JSON command streams.
AnimateLibrary currently expands every stream while loading into enum objects,
matrices and nested arrays, even for shapes that are never instantiated.

Large Animate libraries can therefore retain hundreds of thousands of managed
objects. Besides the additional memory usage, these objects increase the live
graph that hxcpp must traverse during garbage collection, resulting in longer
GC stalls as more assets are loaded.

The new representation keeps the command stream compact and delays graphics
creation until the shape is actually used.

Benchmark

SWFAnimateShapeCommandMemoryRepro.zip
It parses and retains 1,000 unrendered shapes containing
1,050 commands each.

Results on Linux x86_64 with Haxe 4.3.6 and HXCPP_GC_BIG_BLOCKS:

Metric master This PR
Parse time 119–140 ms 59–69 ms
Retained hxcpp heap 107.6 MiB 31.8 MiB
First full GC 13–14 ms 0–1 ms
Median forced compacting GC 28–31 ms 0–1 ms

Real-world validation

Metric Before This PR Change
Surviving AnimateShapeCommand instances 759,391 (~52 MiB shallow) 0 eliminated
Non-full-scan GC pause, median 58.30 ms 37.65 ms -35%
Non-full-scan GC pause, p95 69.11 ms 50.17 ms -27%
Non-full-scan GC pause, maximum 84.67 ms 66.60 ms -21%
Mark phase, median 51.33 ms 32.56 ms -37%
Peak live hxcpp heap 824.6 MiB 767.6 MiB -7%

These values should be considered indicative rather than a controlled benchmark.
However, the complete removal of roughly 759,000 surviving command objects,
together with consistently shorter marking and GC pauses, confirms that the
improvement also applies under a real game workload.

The patched run completed normally with no visible shape rendering regression.

Related change

This PR has a small textual overlap with #54, which adds
lineGradientStyle support. The changes are functionally independent, but both
modify AnimateLibrary.hx and AnimateShapeSymbol.hx.

Once either PR is merged, I can rebase the other one. The combined resolution
handles LINE_GRADIENT_STYLE through the same compact gradient command path and
calls Graphics.lineGradientStyle while rendering. This combined version has
already been tested downstream.

@Tutez64

Tutez64 commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

The new Animate shape test caused AnimateTimeline to be compiled on Haxe 3.4.7, exposing a pre-existing parser incompatibility in a combined conditional expression. The condition has been split into nested directives in both the Animate and SWFLite timeline implementations.

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.

1 participant