Skip to content

Move Card Animations to the EDT#14819

Open
johannes-wolf wants to merge 9 commits into
magefree:masterfrom
johannes-wolf:move-card-animation-to-edt
Open

Move Card Animations to the EDT#14819
johannes-wolf wants to merge 9 commits into
magefree:masterfrom
johannes-wolf:move-card-animation-to-edt

Conversation

@johannes-wolf
Copy link
Copy Markdown
Contributor

@johannes-wolf johannes-wolf commented Apr 30, 2026

Issues Solved

  • Fix alpha-blending for the MTGO-style renderer
  • Moved animations to a single Swing Timer that processes all animations per tick-callback
  • Ported the idea of smoothing (limiting animation time-skips because of a busy EDT to a certain maximum) to the new implementation, removing the sampling
  • Animations no longer busy-wait when waiting for them to finish
  • Make sure the image download thread calls refreshGUIAndCards on the EDT

Further Changes

  • Animation API now returns CompletableFuture to schedule actions after animations complete (see use in BattlefieldPanel.java)
  • Decreased show/hide duration from 600 ms to 250 ms to make fade animations look “snappier”

Future Plans

  • Move the player life-point animation to the animation API (it currently uses a raw-thread and busy-waiting) – not part of this PR

Also remove the busy-loop that waits for card alpha values
and make waiting for animations to finish possible.

The UI is responsive during running animations.
@johannes-wolf
Copy link
Copy Markdown
Contributor Author

Tested this change over > 10 real games - no crashes (anymore), worked smooth.

@JayDi85
Copy link
Copy Markdown
Member

JayDi85 commented May 1, 2026

Whats crashes are you talking about?

P.S. Also run debugger or visualvm and make sure no timer threads spamming on closed/disconnected games (it must be closed and cleaned on game end).

@johannes-wolf
Copy link
Copy Markdown
Contributor Author

johannes-wolf commented May 1, 2026

Whats crashes are you talking about?

P.S. Also run debugger or visualvm and make sure no timer threads spamming on closed/disconnected games (it must be closed and cleaned on game end).

Currently xmage crashes a lot during gameplay. From time to time it freezes and never comes back because of some threading/UI access issues. See the linked issue for example.
They are hard to reproduce. I could never catch them in a debugger.

@johannes-wolf
Copy link
Copy Markdown
Contributor Author

P.S. Also run debugger or visualvm and make sure no timer threads spamming on closed/disconnected games (it must be closed and cleaned on game end).

Will do.

@JayDi85
Copy link
Copy Markdown
Member

JayDi85 commented May 1, 2026

Also it’s better stability test with 3 players game (play with x2 ai, concede and view ai only play).

@johannes-wolf
Copy link
Copy Markdown
Contributor Author

I played 1vs1 and Commander online (> 10 games in total).

@johannes-wolf
Copy link
Copy Markdown
Contributor Author

Will check lots of AI games :)

@johannes-wolf
Copy link
Copy Markdown
Contributor Author

Are you positive about this PR overall? I would like to see it getting merged soon if everything works fine! I think it would improve stability a lot.

@JayDi85
Copy link
Copy Markdown
Member

JayDi85 commented May 1, 2026

Yes, it’s looks fine and legit, only static to local timer change raise the question.

@johannes-wolf
Copy link
Copy Markdown
Contributor Author

Yes, it’s looks fine and legit, only static to local timer change raise the question.

That is no problem, as the (new) swing Thread does not spawn any real-thread but uses a shared timer thread and runs on the EDT. See https://docs.oracle.com/javase/8/docs/api/javax/swing/Timer.html.

I've added tracking of animations by game-id and source(-card), so if a lot of animations get scheduled for the same object, all old ones get canceled so they don't “fight” each other.

Tested with a lot of 8 player AI games + some temporary logging.

@JayDi85
Copy link
Copy Markdown
Member

JayDi85 commented May 2, 2026

There are two problems:

no fully finished animation (you must call update(1.0f) on cancel to make sure alpha return to normal value).

Non-concurrent hash maps and other animation refs. It's ok to make it concurrent as fast fix, but the real fix (maybe as another PR-task) will be in:

  • insert ThreadUtils.ensureRunInGUISwingThread() in all basic operations like start-cancel animation.
  • research all animation calls and replace non edt thread by invokelater (as example: images download run on diff thread and call GUISizeHelper.refreshGUIAndCards -- so that gui refresh must use swing thread).

@johannes-wolf
Copy link
Copy Markdown
Contributor Author

johannes-wolf commented May 2, 2026

  1. I've added an update(1f) when we call cancel() on an unfinished animation. Currently this was done by the tasks running after the future got completed but I agree it is better to call the animation with its final state.
  2. I double-checked all calls and they all happen on the EDT. The fix for the download thread is in a separate PR already Call GUI Refresh on EDT #14821. I added proper EDT checks as you suggested. The only point that can be non-thread safe are functions where we call cancel() as Swing makes sure that the animation timer runs on the EDT.

@johannes-wolf johannes-wolf force-pushed the move-card-animation-to-edt branch from efda01d to 2c72330 Compare May 2, 2026 07:41
@johannes-wolf johannes-wolf force-pushed the move-card-animation-to-edt branch from 2c72330 to 6ed0e93 Compare May 2, 2026 07:42
@johannes-wolf
Copy link
Copy Markdown
Contributor Author

Update: I moved the changes to run refreshGUIAndCards on the EDT to this PR.
Can we merge this? I would like to use this one as a basis for further animation related improvements :)

Copy link
Copy Markdown
Member

@JayDi85 JayDi85 left a comment

Choose a reason for hiding this comment

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

Also make two lists in issue description:

  • fixed bugs and added features like massive animations support;
  • farther improvements and left bugs;

* when finish() gets called.
*/
private static final Map<UUID, Set<Animation>> activeByGameId = new HashMap<>();
private static final Map<Object, Animation> activeByTarget = new HashMap<>();
Copy link
Copy Markdown
Member

@JayDi85 JayDi85 May 10, 2026

Choose a reason for hiding this comment

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

Concurrent problem still active - it’s can fail on multiple games/calls. Make it ConcurrentMap or call ensureGUIThread on any create/start/finish animation methods (that can change static lists)

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.

Client and server allow to start multiple tables/games/ai - it’s can be a good use case for stability testing (start two parallel tables/games with ai, concede and keep active to view/switch between it by main menu).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Concurrent problem still active - it’s can fail on multiple games/calls. Make it ConcurrentMap or call ensureGUIThread on any create/start/finish animation methods (that can change static lists)

Done. I opted for the ConcurentMap – although, I checked that the code never gets called from non-EDT; but you are right, it is safer that way.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Client and server allow to start multiple tables/games/ai - it’s can be a good use case for stability testing (start two parallel tables/games with ai, concede and keep active to view/switch between it by main menu).

I already did a lot of testing with parallel AI games and did a litte re-test now – all fine.

Copy link
Copy Markdown
Contributor Author

@johannes-wolf johannes-wolf May 10, 2026

Choose a reason for hiding this comment

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

One thing that is noticeable: Since we can now run animations truly async the speed at which AI player play the game against each other can lead to stuttering and a slow client. This is not a problem with human controlled games, as no one untaps and taps like 20+ permanents per second ;).

I guess, since we know the total number of animations per game, we could cancel animations to make sure we are not running more then n animations per game at a time.

Copy link
Copy Markdown
Member

@JayDi85 JayDi85 May 10, 2026

Choose a reason for hiding this comment

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

as no one untaps and taps like 20+ permanents per second

But that's a normal use case, e.g. cancel on massive mana payment, start/cancel all attack button or some tap/untap effects with a big battlefield. Make sure it's fine and do not freeze the client.

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 can use test mode to generate big battlefields:
https://github.com/magefree/mage/wiki/Development-Testing-Tools#server-test-mode

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thank your, I will try that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

as no one untaps and taps like 20+ permanents per second

But that's a normal use case, e.g. cancel on massive mana payment, start/cancel all attack button or some tap/untap effects with a big battlefield. Make sure it's fine and do not freeze the client.

You are right. And I am very unhappy with the performance of this PR when tapping 25+ permanents at once. I will switch back to the old raw Timer but make sure we do UI stuff on the UI thread + keep the new API.

@johannes-wolf johannes-wolf force-pushed the move-card-animation-to-edt branch from 3feb2b6 to c9eb1dc Compare May 10, 2026 01:29
@johannes-wolf
Copy link
Copy Markdown
Contributor Author

Also make two lists in issue description:
* fixed bugs and added features like massive animations support;
* farther improvements and left bugs;

I changed the PR description to a list.

@johannes-wolf johannes-wolf requested a review from JayDi85 May 10, 2026 01:32
// extra space for animation and other drawing
// WTF, I'm tired with render calcs, so make BIG draw spaces for any needs
MageCardSpace outerSpace = new MageCardSpace(width * 2, width * 2, height * 2, height * 2);
MageCardSpace outerSpace = getAnimationOuterSpace(width, height);
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.

For info: there are card icons over card panel like commander icon -- it must have outer space for good drawing/repaint and rotation. Use "main menu - debug - render" to test it without real game.

Copy link
Copy Markdown
Contributor Author

@johannes-wolf johannes-wolf May 10, 2026

Choose a reason for hiding this comment

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

For info: there are card icons over card panel like commander icon -- it must have outer space for good drawing/repaint and rotation. Use "main menu - debug - render" to test it without real game.

I will look into this 👍, should be no problem adding some border margin for the icons. This gives better performance as far as I noticed, as the old update-rects are huge.

Marked this PR as a draft, I will ping you if it is ready again.

With the latest change back to a single timer that handles all animation ticks in one go performance is on-par with the old implementation, even on huge boards (it stutters, but so did the old one).

@johannes-wolf johannes-wolf force-pushed the move-card-animation-to-edt branch from 2562974 to f47bdac Compare May 10, 2026 13:50
@johannes-wolf johannes-wolf marked this pull request as draft May 10, 2026 13:58
@johannes-wolf johannes-wolf force-pushed the move-card-animation-to-edt branch 2 times, most recently from 77a96ad to 8c37d1b Compare May 10, 2026 23:34
@johannes-wolf johannes-wolf force-pushed the move-card-animation-to-edt branch from 8c37d1b to bc33cb6 Compare May 12, 2026 16:34
@johannes-wolf johannes-wolf marked this pull request as ready for review May 12, 2026 16:34
@johannes-wolf
Copy link
Copy Markdown
Contributor Author

johannes-wolf commented May 12, 2026

  • I double checked cards bounding rect calculation there is still more then enough space for icons but we went down from 5 × card size to ~2 × card size
  • Animating masses of cards at once is on-par to the old implementation, performance wise
  • Updated the PR description to reflect new changes

@johannes-wolf johannes-wolf requested a review from JayDi85 May 12, 2026 16:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants