jinjor / elm-break-dom

https://elm-break-dom.netlify.com/
53 stars 6 forks source link

Cannot have a function that takes a model and returns (the Html element containing) an elm-3d-scene #35

Open perkee opened 1 year ago

perkee commented 1 year ago

The Problem

This was originally filed in the bug tracker for elm-3d-scene but this patch is the root cause https://github.com/ianmackenzie/elm-3d-scene/issues/105

Expected behavior:

Clicking the one button in this example should update the model and otherwise do nothing. The console should not have any errors.

Actual behavior

The console shows many copies of this error (1 per frame in a 60fps browser maybe?)

main.html:2843 Uncaught TypeError: Cannot read properties of undefined (reading 'length')
    at _VirtualDom_render (main.html:2843:43)
    at _VirtualDom_applyPatchRedraw (main.html:3878:16)
    at _VirtualDom_addDomNodesHelp (main.html:3728:5)
    at _VirtualDom_addDomNodesHelp (main.html:3643:5)
    at _VirtualDom_addDomNodesHelp (main.html:3739:8)
    at _VirtualDom_addDomNodes (main.html:3617:2)
    at _VirtualDom_applyPatches (main.html:3762:2)
    at main.html:4038:15
    at updateIfNeeded (main.html:4107:56)

The line in the stack trace points to kids.length in the last for loop in the code below. Lines included above just so it's easier to find in the generated JS. The error will be the same if I build with a JS file as output, only the line numbers will be different naturally.

    // at this point `tag` must be 1 or 2

    var domNode = vNode.f
        ? _VirtualDom_doc.createElementNS(vNode.f, vNode.c)
        : _VirtualDom_doc.createElement(vNode.c);
    domNode.created_by_elm = true;

    if (_VirtualDom_divertHrefToApp && vNode.c == 'a')
    {
        domNode.addEventListener('click', _VirtualDom_divertHrefToApp(domNode));
    }

    _VirtualDom_applyFacts(domNode, eventNode, vNode.d);

    for (var kids = vNode.e, i = 0; i < kids.length; i++) 
    {
        _VirtualDom_appendChild(domNode, _VirtualDom_render(tag === 1 ? kids[i] : kids[i].b, eventNode));
    }

    return domNode;

Reproducing the error

Platform

Build command

elm make src/Main.elm --output=main.html but this persists whether or not I have --debug or --optimize or --output=main.js then include into main.js into a handwritten index.html. It persists whether I open the HTML file in the browser (i.e. with file:///path/to/index.html) or if I serve it using a minimal webserver (I tend to use python3 -m http.server 4321). I have even reproduced it in a lamdera page.

elm.json

{
    "type": "application",
    "source-directories": [
        "src"
    ],
    "elm-version": "0.19.1",
    "dependencies": {
        "direct": {
            "avh4/elm-color": "1.0.0",
            "elm/browser": "1.0.2",
            "elm/core": "1.0.5",
            "elm/html": "1.0.0",
            "ianmackenzie/elm-3d-camera": "3.1.0",
            "ianmackenzie/elm-3d-scene": "1.0.1",
            "ianmackenzie/elm-geometry": "3.11.0",
            "ianmackenzie/elm-units": "2.10.0"
        },
        "indirect": {
            "elm/json": "1.1.3",
            "elm/random": "1.0.0",
            "elm/time": "1.0.0",
            "elm/url": "1.0.0",
            "elm/virtual-dom": "1.0.3",
            "elm-explorations/linear-algebra": "1.0.3",
            "elm-explorations/webgl": "1.1.3",
            "ianmackenzie/elm-1d-parameter": "1.0.1",
            "ianmackenzie/elm-float-extra": "1.1.0",
            "ianmackenzie/elm-geometry-linear-algebra-interop": "2.0.2",
            "ianmackenzie/elm-interval": "3.1.0",
            "ianmackenzie/elm-triangular-mesh": "1.1.0",
            "ianmackenzie/elm-units-interval": "3.2.0"
        }
    },
    "test-dependencies": {
        "direct": {},
        "indirect": {}
    }
}

src/Main.elm

module Main exposing (main)

import Angle
import Browser
import Camera3d
import Color
import Direction3d
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import Length
import Pixels
import Point3d
import Scene3d
import Scene3d.Material as Material
import Viewpoint3d

type Model
    = Model

type Msg
    = MakeErrorsHappen

update : Msg -> Model -> ( Model, Cmd msg )
update MakeErrorsHappen _ =
    ( Model, Cmd.none )

main =
    Browser.element
        { init = \() -> ( Model, Cmd.none )
        , subscriptions = \Model -> Sub.none
        , update = update
        , view = view
        }

view : Model -> Html Msg
view model =
    Html.div
        []
        [ scene model
        , button [ onClick MakeErrorsHappen ]
            [ text "make bugs"
            ]
        ]

scene : Model -> Html msg
scene _ =
    let
        underlayment : Scene3d.Entity coordinates
        underlayment =
            Scene3d.quad (Material.color (Color.rgba 255 0 0 0.5))
                (Point3d.meters -5 -5 -1)
                (Point3d.meters 5 -5 -1)
                (Point3d.meters 5 5 -1)
                (Point3d.meters -5 5 -1)

        r =
            42.0

        ( camX, camY, camZ ) =
            ( r * cos 0.0, r * sin 0.0, 24.0 )

        camera =
            Camera3d.perspective
                { viewpoint =
                    Viewpoint3d.lookAt
                        { focalPoint = Point3d.origin
                        , eyePoint = Point3d.meters camX camY camZ
                        , upDirection = Direction3d.positiveZ
                        }
                , verticalFieldOfView = Angle.degrees 30
                }
    in
    Scene3d.cloudy
        { entities = [ underlayment ]
        , camera = camera
        , clipDepth = Length.meters 1
        , background = Scene3d.transparentBackground
        , dimensions = ( Pixels.int 800, Pixels.int 1000 )
        , upDirection = Direction3d.negativeZ
        }

Things that get rid of the Problem

Make scene not depend on the model

In the minimum example above, changing scene : Model -> Html msg to scene : Html msg gets rid of the problem. It means, of course, that the scene cannot change with the values in the model.

perkee commented 1 year ago

WOW OK A solution emerges. It's pretty small. If we apply this repo's patch to VirtualDom.js then we must also similarly patch https://github.com/elm-explorations/webgl/blob/main/src/Elm/Kernel/WebGL.js#L789 to have canvas.created_by_elm = true right after it. That fixes the whole thing. I went looking in my ~/.elm for other calls to _VirtualDom_doc.create but there weren't any others that stood out similarly needing a patch.