Libgdx: Something about Tables broke in recent Snapshot

Created on 29 Sep 2020  路  23Comments  路  Source: libgdx/libgdx

Excuse the vague report, I'm trying to debug on my own and figure it out to no avail.

Situation: My app was updated the other day, I had to update some Gradle dependencies, recompile, and upload to Google Play. (So the LibGDX Snapshot probably updated.) Nothing else was changed. The next day I received reports from only a couple users showing me what is in the below screenshots. Some buttons in my app's menu are the wrong size, huge and broken in some cases.

I installed on all of my 7 test devices, no issue. I cannot reproduce this.
Seemingly nobody else is reporting this issue either.
I thought it a fluke, but today a couple more reported to me as well.

Even odder, a reinstall of the app fixes it, as these users have told me. The way the UI is built is absolutely not dependent with any persistent/saved properties where a reinstall would fix it. This is very bizarre.

Screenshots (notice the left-side, the top buttons are either "too big" or "take up the whole screen big", also you'll notice another button is seemingly shifted out of the menu horizontally as well).

I notice a common factor in the code where these buttons are added to the ScrollPane's Table:

_undoButton = createToolTextButton(App.bundle.format("undo"), Module.getNormalButtonStyle());
add(_undoButton).align(Align.right).height(_undoButton.getHeight() * 0.75f);

public static TextButton createToolTextButton(String text, TextButtonStyle textButtonStyle)
{
    TextButton textButton = new TextButton(text, textButtonStyle);
    textButton.getLabel().setWrap(true);
    textButton.getCell(textButton.getLabel()).pad(0, 4, 0, 4);

    return textButton;
}

I modify the height by taking the height of the Button and scaling it down 0.75f. This code has been in place for years but likely something is either wrong or this is now deprecated behavior?

Similarly, the button which is shifted out of view horitontally has width-edit code:

_importStickfigureButton = createToolImageTextButton2(App.bundle.format("importStickfigure"), Module.getLargeStickfigureButtonStyle());
importTable.add(_importStickfigureButton).width(_importStickfigureButton.getWidth() * 0.75f);

And the Table object these buttons are added to is initialize like:

public void initialize(Drawable tableGrayBackground)
{
    // Setup the table.
    if (tableGrayBackground != null)
        setBackground(tableGrayBackground);
    pad(20, 0, 20, 0);
    defaults().space(0).spaceRight(10).spaceBottom(10).pad(0).align(Align.center).expandX().uniformX();
}

I notice commits were made recently to scene2d recently (https://github.com/libgdx/libgdx/commits/master/gdx/src/com/badlogic/gdx/scenes/scene2d/ui), specifically about layout() stuff (41e65e25cfd24ec72aaf01fbf1aa39e1001019b2).

I'd have to believe something is wrong in one of these recent commits. I apologize again for the vague issue report where I can't pinpoint the exact problem, but hoping maybe the source of this problem is more apparent to one of you guys?

I'm still confused as to why this affects a very small percentage of users, and a reinstall fixes it.

Version of LibGDX and/or relevant dependencies

1.9.12-SNAPSHOT

Please select the affected platforms

  • [ X] Android
  • [ ] iOS
  • [ ] HTML/GWT
  • [ ] Windows
  • [ ] Linux
  • [ ] MacOS
core scene2d

Most helpful comment

as usual, not reviewed code committed to master causes issues to ton of people

All 23 comments

Also been told so far problematic devices (and idk if "all" of them or just particular individual ones) are Samsung A20, A40, A50
Kinda a coincidence...

Also just been told by A20 user a reinstall fixed it again.

Is it possible this is somehow an issue of Google Play serving an update to users that was somehow not correctly "updating"? Or something to do with a user's app cache?

Probable cause: 41e65e25cfd24ec72aaf01fbf1aa39e1001019b2 @NathanSweet

Probable cause: 41e65e2 @NathanSweet

Yeah I don't mean to call him out on that, especially when I can't pinpoint the exact issue. It's the reinstall=fix that's throwing me for a loop tbh, anyone have ideas on what that could mean so I know what to look at?

It's good to ping him, he knows best what changed and what outcomes the change has and he will be interested if something went wrong. After all, it's the only change to Table layouts for a longer time.

as usual, not reviewed code committed to master causes issues to ton of people

No problem pinging me. Table working correctly is very important!

The first screenshot is very blurry and I don't know what I'm looking at. The second I can see the undo and redo buttons are very tall, which I guess is unwanted. I can't tell what else might be placed incorrectly, but it probably wouldn't help determine why the bad layout happened anyway.

How you are configuring the table doesn't look odd at all. The recent changes to Table were mostly about how rounding is performed, but I don't see how they could cause such wildly incorrect layouts. I'm afraid we probably can't do much without being able to reproduce the problem. Even if I had a good guess at the cause, which I unfortunately don't, without being able to reproduce it we couldn't know if that really fixed it.

Maybe it happens only at specific sizes? Have you tried running your app as a desktop app where you can resize the window?

What coordinates do you use for your stage? Eg, do you use a ScreenViewport where 1 pixels is 1 unit in the stage, or something else?

Maybe there is something weird on those devices with rounding? I hate to blame it on a hardware specific failure because it seems so unlikely. If you don't already, you could try disabling rounding via Table setRound(false). Or to narrow it down to the rounding and not the other, supposedly minor Table changes, you could modify Table so rounding is not done during layout, and is instead done after layout like it used to be. However, effort is probably better spent trying to make a reproduction case so we can be sure about a fix.

@RUSshy I make commits to master when I feel the changes don't need input from others, otherwise I create a PR. master is a development branch and changes made there may be unstable. Users who run from a development branch are helping to find issues and improve the library. That is a very useful thing, quite unlike your complaining. This has already been explained to you, now you are just wasting my time.

@NathanSweet Thanks for comment!

Actually came here to post another case, where its resulting in even different results (he posted a video) https://v.redd.it/bbobldopw6q51/DASH_1080.mp4?source=fallback

Seems the entire Table is all wrong, looks like typical layout issues.

Have you tried running your app as a desktop app where you can resize the window?

Thought of that and tried it, nothing. I even have an A20 which was one of the problematic devices, installed it and nothing there as well.

Also the app uses an ExtendViewport with a virtual width/height to basically fill the screen, since it's mainly a UI app.

Still trying to ask users for info in order to come up with a reproduction case or something. I understand how the seemingly random nature of this makes me feel its not LibGDX causing it, but the fact Table/layout was just modified prior to this build makes me unsure.

The thing that is still bothering me, and I don't know if you have any insight or past experience with this with Android in general - why would a reinstall fix it? Another way of ruling out LibGDX is I'd like to use the older snapshot just prior to this one. Is there a way to get my gradle to fetch that one? (I'd use the most recent stable build, but unfortunately that has GlyphLayout issues where text renders out of bounds a bit)

You can use older snapshots by specifying the timestamp.

I make commits to master when I feel the changes don't need input from others

The thing is, sometimes more pairs of eyes see more things than just one pair - and, speaking of me, sometimes even my pair see new things after sleeping over it. Is there any real disadvantage of using PR and waiting some days before merging?

@MrStahlfelge Sorry for kinda derailing this issue into more general support, lol, but that's what I see when I've searched the topic (use the timestamp) - it's just I can't find where do I get that information? (the timestamp, that is)

You can either browse through the snapshot repos, but I prefer to check my local gradle/maven cache (depending on your os and settings, but usually ~/.m2 or ~./.gradle).

Note that each artifact has its own timestamp, unfortunately, so you need to pass in different versions.

ah okay, I see, thank you - for sake of simplicity I'm just digging out the jars and placing into /libs/ folder for now

will continue trying to debug this issue however, pushing out an updated version with a regressed version of LibGDX should help narrow things down

the fact Table/layout was just modified prior to this build makes me unsure.

Aye, it could very well have something to do with the recent changes. FWIW, I use Table extensive in a large app (Spine) and have done a recent release to thousands of users without layout issues, but it could be you are using it differently. My app has 1:1 pixel:stage units, which is pretty big difference.

Since your viewport setup likely doesn't map 1:1, you may not want to use Table rounding at all. Eg say an actor is 10.5 tall in stage units and you have Table round to 11 stage units, when displayed on the screen both the 10.5 and 11 are unlikely to be mapped to an integer pixel. Since the rounding doesn't help you avoid texture filtering, there's not much point to doing it. Still, if the issue is with rounding we should of course figure out why it happens.

why would a reinstall fix it?

No idea, this is pretty mysterious. Maybe your UI is slightly different when refreshly installed? I wonder if just running it multiple times would have "fixed" it.

The thing is, sometimes more pairs of eyes see more things than just one pair - and, speaking of me, sometimes even my pair see new things after sleeping over it. Is there any real disadvantage of using PR and waiting some days before merging?

@MrStahlfelge It's rare for others to pull a PR and try it out, especially for small to medium sized changes. Eg, a PR would not have helped avoid this Table issue. Generally a PR is appropriate for changes where discussion is needed about if it should be done at all or how it should be done. If I think a change needs that then I'll make a PR, as I've done in the past. Otherwise, I am already the gatekeeper for the parts of libgdx I maintain. It is unreasonable to run from master and expect there to never be problems. Let's please stop derailing this and other issues with this discussion.

Alright so I'm apparently too dense to get an older Snapshot working nicely with gradle, so I'll keep the current version. More debugging and attempting to recreate to no avail. Will disable rounding and push out an update tomorrow and see if anyone brings this up again and update with any news over the next few days. I almost feel like it's some kind of an corrupted build/device hardware issue, if that even is remotely possible which I doubt. Due to the fact how I've seen the scrollpane/table messed up in at least 4 varying ways including massive buttons.

Anyway thanks for assistance, I'll report with news of results.

This may not be at all related, but the issue you're reporting caught my eye, as I've experienced something vaguely similar before.

Back in 2018, people using my app started reporting an issue where two random tables near the top of the screen were flipping their contents and positioning them directly below where they should be. There was nothing out of the ordinary about these tables, and none of them had any "dynamic" layout or anything that could possibly be tied to persistent state.

I tried to reproduce the issue on 3 different Android devices and on an emulator, with no success. The issue did not occur on a single desktop or iOS device either - it was only on Android, and only on specific devices.

I worked with a user who had the issue, and sent them a modified APK to print out some of the layout problems (with no conclusive results). I then sent them the exact same APK they were using before, and after they installed it, the problem magically disappeared. Other users reported having the same experience as well - just like in your case, a reinstall would fix the problem for seemingly no reason.

Eventually, the bug disappeared on its own. I still don't know what caused it, or how it was fixed.

In short: This could be a problem directly associated with the recent libGDX changes. But it could also be an insignificant change in your code, libraries or build system that caused some Android demons to awaken and mess up your UI layout. Either way, this doesn't make any sense.

table.add(button).height(button.getHeight() * 0.75f);

The button's height will be the pref height, so it could be that is too large for some reason. Maybe you could print the height or similar so we can know what that height was when the layout is weird? Might be hard since Android doesn't make getting the log output easy.

TextButton has a Label, so the problem could be in Label or GlyphLayout or BitmapFont. Or DistanceFieldFont or gdx-freetype -- how do you create your fonts? Otherwise TextButton is a Button is a Table, so the problem could still be Table, either the button/table itself or the table the button is placed in. It seems nuts for the devices to be just completely wonky.

Your label wraps but the button height is computed in the TextButton constructor, before setWrap(true). Even if the height were computed after, the wrapping label's pref width is 0, and GlyphLayout setText turns off wrapping for very narrow widths. I don't think the label wrap matters.

Something to note is that you change the padding on the label's cell, but:
1) Changing table constraints don't invalidate the layout. You should call table.invalidate() after changing table/cell constraints.
2) The button's height is set in the constructor to the pref height, so won't reflect your table constraint changes made later, even if you call invalidate. If you care about that, you could it like this:

TextButton textButton = new TextButton(text, textButtonStyle);
textButton.getLabel().setWrap(true);
textButton.getCell(textButton.getLabel()).pad(0, 4, 0, 4);
textButton.invalidate(); // let the table know its size and layout needs to be recomputed
textButton.pack(); // equivalent to setSize(getPrefWidth(), getPrefHeight()) then validate()
return textButton;

If the button is already placed in another actor, you'd probably want to call invalidateHierarchy() if you changed the table constraints, so all the parent actors to the root know their layout needs to be recomputed.

BTW, you can use TextButton getLabelCell instead of getCell(label).

@Anuken thats very interesting, sounds almost identical, I wonder if just a simple recompile would actually just make this go away

@NathanSweet thanks for all that - actually I've added some prints into the build just in case someone comes at me with this again, there's a button in the app to create the logcat file and I'll just request it from them

So an interesting development, I never checked the crash logs until now, and crashlytics is reporting a sudden spike in Cell.java crashes, it's this:

Fatal Exception: java.lang.IllegalArgumentException: spaceRight cannot be < 0: -3.0837466E-27
       at com.badlogic.gdx.scenes.scene2d.ui.Cell.spaceRight(Cell.java:390)
       at org.fortheloss.sticknodes.animationscreen.modules.tooltables.FrameToolTable.initialize(FrameToolTable.java:1037)

Sometimes the number is miniscule, like above, other times its:

Fatal Exception: java.lang.IllegalArgumentException: spaceRight cannot be < 0: -6001.039

Bizarre.

The line of code in question:

_subSoundsTable = new Table();
_subSoundsTable.pad(0).align(Align.center);
_subSoundsTable.defaults().pad(0).space(0).spaceRight(10 * App.assetScaling).expandX(); // <-----

This code has been in place for years. Never had a crash with Cell.java.
Also, App.assetScaling is either 1.0f or 0.5f, decided on startup where either HD or SD assets are selected.

I can't even fathom what would have to be happening here to get a range of random-ass -0.0000003 or -6001 as a result...

Regardless, these reports indicate a crash. The initial report here was of the layout just being "wrong" - obviously not crashing though. But maybe indicates something similar going wrong elsewhere in size calculations that isn't causing an exception. Still, maybe 10 people have mentioned this to me whereas my app has thousands of daily users so, it's extremely rare and once again - reinstall fixes it.

Been busy as hell this week but am pushing out a new APK in an hour with just a setRound(false) on all Tables and some of the minor things you mentioned above.

Also, 100% Samsung, 100% Android 10 devices according to Crashlytics

I'm actually laughing. Anyway, will update with news after this is live for a bit.

I wonder if just a simple recompile would actually just make this go away

In my case, the bug persisted across many versions, and even survived some refactoring. I tried to switch from Table to Container for one specific element at some point, and even that didn't fix it.

@Anuken @NathanSweet Okay, been 36 hours now since I pushed out new update. The Cell.java crash is not in Crashlytics as of the new version, and so far nobody has reported the layout issue. Don't want to jinx it, but may be "resolved"

As for what caused it...I'm going to chalk it up to compiler demons.

As mentioned before, all I've changed was to add setRound(false) to as many Tables as I could in the menu (not to things that extend Table, however) and wiped the gradle cache to redownload all LibGDX dependencies, just because. I did also update build target to Android 29 as I was planning this anyway per Google's upcoming deadline, but uncertain if this mattered.

If a false alarm, which it's looking like, sorry. Lol. Still, very, very weird and would like to know what this was.

Long shot, but this seems eerily similar to a bug from a distant age... Well, 2016-2020. https://github.com/libgdx/libgdx/issues/4065
I believe the conclusion was, certain (Intel Baytrail) processors, when JIT mode was on, emitted incorrect machine code when JIT compiling bytecode, but only rarely. The bug was somewhere in the combination of non-interpreted JVM operation (which usually happens after startup), that generation of processor, and it seems like some unpredictable operations were affected. If this is a hardware bug, the best you can do is search for some kind of community support for Samsung phones. If it's OS-related, maybe post an issue to Android technical support somehow... Then there's potential compiler demons on your side, too. You can try building the APK on some other machine if possible, and maybe a beta program could distribute the apps built on other hardware. There's always the possibility that something on Samsung's massive supply chain went wrong (again), and some tiny portion of phones are incorrectly executing apps (or apps saved in a particular part of storage). There's not much that can be done about that. If reinstalling the same APK is a solution that works, encourage users to try it if they encounter this bug; it's not too hard for users to do, and maybe you can implement some kind of "export save data" functionality if your game would lose any important info on a reinstall.

@tommyettinger interesting, very similar to that old issue

as mentioned before, i've already simply rebuilt the APK and uploaded and the issues are seemingly gone - i still find it interesting the crashes were only 100% Samsung/Android 10 devices, making me believe it was a device issue but then why would it be gone on a rebuild - also I own one of the exact problematic devices and couldn't replicate, overall just a strange experience

combining the two issues (the crash with spaceRight() and the overall wonky Table layout) it just seemed like math was itself just "wrong" in the old build, which purports to what was discussed in that issue you linked

anyway I will close this now as it seems to be unrelated to LibGDX in general, thanks all for advice

i've already simply rebuilt the APK and uploaded and the issues are seemingly gone

Well to be fair I believe you also turned off table rounding. That shouldn't matter, but could be why you no longer see a problem. Whether it's because table has bugs or that avoids problems with hardware/JVM bugs, we can't say for sure. At any rate, glad it's resolved!

This makes it apparent that it's a little awkward to use scene2d without rounding, as you need to turn it off for every table, container, horiz and vertical group, and progress bar. We might consider some centralized place for scene2d.ui-wide settings to make it easier (though I don't personally use scene2d that way).

Well to be fair I believe you also turned off table rounding

True and I would like to test it. Though I looked at the code for rounding in Table and I don't see anything there that would cause such wild values. Secondly, the spaceRight(10 * assetScaling) resulting in a crash due to it evaluating to <0 at random values also indicates something more voodoo-y at work. I just suspect LibGDX is fine and it was a fault on my end. But true @ what you say about the rounding thing, maybe even a static value would do the trick.

Was this page helpful?
0 / 5 - 0 ratings