JetBrains / compose-multiplatform

Compose Multiplatform, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable.
https://jetbrains.com/lp/compose-multiplatform
Apache License 2.0
16.38k stars 1.18k forks source link

Can't represent a size of 355113 in Constraints #3799

Closed mgroth0 closed 1 year ago

mgroth0 commented 1 year ago

Describe the bug

Creating a large Text in a scrollable container causes this error.

xception in thread "AWT-EventQueue-0 @coroutine#8" java.lang.IllegalArgumentException: Can't represent a size of 355113 in Constraints
    at androidx.compose.ui.unit.Constraints$Companion.bitsNeedForSize(Constraints.kt:403)
    at androidx.compose.ui.unit.Constraints$Companion.createConstraints-Zbe2FdA$ui_unit(Constraints.kt:363)
    at androidx.compose.ui.unit.Constraints$Companion.fixed-JhjzzOo(Constraints.kt:314)
    at androidx.compose.foundation.text.modifiers.TextStringSimpleNode.measure-3p2s80s(TextStringSimpleNode.kt:238)
    at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:116)
    at androidx.compose.ui.graphics.SimpleGraphicsLayerModifier.measure-3p2s80s(GraphicsLayerModifier.kt:646)
    at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:116)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasure$2.invoke(LayoutNodeLayoutDelegate.kt:1499)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasure$2.invoke(LayoutNodeLayoutDelegate.kt:1495)
    at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2300)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:471)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:234)
    at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui(OwnerSnapshotObserver.kt:133)
    at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui(OwnerSnapshotObserver.kt:113)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:1495)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate.access$performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:35)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:560)
    at androidx.compose.ui.node.LayoutNode.remeasure-_Sx5XlM$ui(LayoutNode.kt:1140)
    at androidx.compose.ui.node.LayoutNode.remeasure-_Sx5XlM$ui$default(LayoutNode.kt:1131)
    at androidx.compose.ui.node.MeasureAndLayoutDelegate.doRemeasure-sdFAvZA(MeasureAndLayoutDelegate.kt:323)
    at androidx.compose.ui.node.MeasureAndLayoutDelegate.remeasureAndRelayoutIfNeeded(MeasureAndLayoutDelegate.kt:458)
    at androidx.compose.ui.node.MeasureAndLayoutDelegate.access$remeasureAndRelayoutIfNeeded(MeasureAndLayoutDelegate.kt:39)
    at androidx.compose.ui.node.MeasureAndLayoutDelegate.measureAndLayout(MeasureAndLayoutDelegate.kt:344)
    at androidx.compose.ui.platform.SkiaBasedOwner.measureAndLayout(SkiaBasedOwner.skiko.kt:246)
    at androidx.compose.ui.node.Owner.measureAndLayout$default(Owner.kt:223)
    at androidx.compose.ui.ComposeScene.render(ComposeScene.skiko.kt:546)
    at androidx.compose.ui.awt.ComposeBridge$skikoView$1$onRender$1.invoke(ComposeBridge.desktop.kt:156)
    at androidx.compose.ui.awt.ComposeBridge$skikoView$1$onRender$1.invoke(ComposeBridge.desktop.kt:155)
    at androidx.compose.ui.awt.ComposeBridge.catchExceptions(ComposeBridge.desktop.kt:128)
    at androidx.compose.ui.awt.ComposeBridge.access$catchExceptions(ComposeBridge.desktop.kt:59)
    at androidx.compose.ui.awt.ComposeBridge$skikoView$1.onRender(ComposeBridge.desktop.kt:155)
    at org.jetbrains.skiko.SkiaLayer.update$skiko(SkiaLayer.awt.kt:548)
    at org.jetbrains.skiko.redrawer.AWTRedrawer.update(AWTRedrawer.kt:54)
    at org.jetbrains.skiko.redrawer.MetalRedrawer$frameDispatcher$1.invokeSuspend(MetalRedrawer.kt:83)
    at org.jetbrains.skiko.redrawer.MetalRedrawer$frameDispatcher$1.invoke(MetalRedrawer.kt)
    at org.jetbrains.skiko.redrawer.MetalRedrawer$frameDispatcher$1.invoke(MetalRedrawer.kt)
    at org.jetbrains.skiko.FrameDispatcher$job$1.invokeSuspend(FrameDispatcher.kt:33)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
    at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:318)
    at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:773)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:720)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:714)
    at java.base/java.security.AccessController.executePrivileged(AccessController.java:778)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:400)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:87)
    at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:742)
    at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
    at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)
    Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [CoroutineId(8), "coroutine#8":StandaloneCoroutine{Cancelling}@38941da2, SwingDispatcher@66be5570]

Affected platforms

Versions

To Reproduce

  1. The code below reproduces the error
  2. Hit the last button, the one with the biggest number
  3. Observe that an error is only thrown when scroll at the top of the code is true
        val scroll = true /* error only thrown when true */
        val scrollBar = scroll && false
        val scrollState by lazy { ScrollState(0) }
        val scrollBarAdapter by lazy { ScrollbarAdapter(scrollState) }
        val strings = mutableMapOf<Int, String>()
        application {
            Window(visible = true, onCloseRequest = {
                exitApplication()
            }) {

                var string by remember { mutableStateOf("") }
                Column(
                    modifier = Modifier.padding(10.dp)
                ) {
                    Row {

                        generateSequence(1) {
                            it * 10
                        }.take(7).forEach { num ->
                            Button(
                                onClick = {
                                    string += strings.getOrPut(num) {
                                        String(
                                            CharArray(num) {
                                                ('a'.code + it % 5).toChar()
                                            }
                                        )
                                    }
                                }
                            ) {
                                Text("add $num")
                            }
                        }
                    }
                    Row {
                        Box(
                            modifier = Modifier.padding(10.dp).let {
                                if (scroll) {
                                    it.verticalScroll(scrollState)
                                } else it
                            }
                        ) {
                            Text(string)
                        }
                        if (scrollBar) VerticalScrollbar(
                            modifier = Modifier.requiredSize(
                                width = LocalScrollbarStyle.current.thickness,
                                height = Dp.Unspecified
                            ).fillMaxHeight(),
                            adapter = scrollBarAdapter
                        )
                    }

                }

            }
        }

Expected behavior

I am pushing the limits of what can be handled with huge amounts of text like this, so it is not surprising that I will run into errors. However, this error in particular is not very informative on what caused the issue, or how to solve it. "Can't represent a size of 355113 in Constraints" is only helpful to internally to the developers of the library itself, since users (in this case) are not directly working with Constraints. if Constraints is supposed to be abstracted away by the comonents used in the reproducers, then a more user-friendly error message sould be thrown.

Given the vagueness of the error message, I am not exactly sure what I did "wrong". Was it wrong of me to use so much text? or did I implement scrolling wrong somehow? Or should i have broken the text up into multiple comonents? Or used some sort of lazy view? It is unclear.

So basically, the question is whether this is user error or not. If it is user error, a more friendly, informative, actionable error message should be thrown. If it is not user error, then the bug itself can be fixed.

Additional context

I am working on a simple log viewer, and this started happening to me with large amounts of text in a single log. I think I am probably doing it wrong, but this error is not helpful in pointing me in the right direction.

mgroth0 commented 1 year ago

Maybe related to https://github.com/google/accompanist/issues/847, but not sure because the number reported in the error message 355113 is much smaller.

mgroth0 commented 1 year ago

Also might be related to https://stackoverflow.com/questions/71400647/cant-represent-a-size-of-214748364-in-constraints, but again the number here is much smaller

mgroth0 commented 1 year ago

Also https://github.com/JetBrains/compose-multiplatform/issues/2420, again this one had a larger number

elijah-semyonov commented 1 year ago

The repro can be simplified to this:

val constraints = Constraints.fixedWidth(width = 355113)

This error has to do with an algorithm of how Compose packs Constraints input values bits. Apparently, it's too much for what was considered by Google to be a sane value, so they explicitly throw an exception in this case.

Unfortunately, we don't own this piece of logic. So if you think it's something that needs a change for some specific (and should I notice, extreme) scenarios, you can make a report on Google Tracker.

okushnikov commented 3 months ago

Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.