Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

move returns values outside the bounds of current state #21

Open
jerith666 opened this issue Feb 19, 2021 · 5 comments
Open

move returns values outside the bounds of current state #21

jerith666 opened this issue Feb 19, 2021 · 5 comments

Comments

@jerith666
Copy link

Sometimes move will return a value that's outside the bounds of what's been passed to go to update the model. This is illustrated in this ellie; code reproduced below for reference.

See #19 for discussion of the general structure of this code. There's a new column now labeled "move". Sometimes the value in the "move" column will be larger than the "real" value, which should be impossible. You have to build with --debug and step through frame by frame to actually be able to see this. When values like this are being used to drive a visual animation, though, it manifests as a visible "bounce".

module Main exposing (..)

import Animator exposing (Animator, Timeline, arrived, at, current, go, move, previous, toSubscription, veryQuickly, watchingWith)
import Browser exposing (Document, element)
import Delay exposing (TimeUnit(..), after)
import Html exposing (Html, div, text)
import Html.Attributes exposing (style)
import Random exposing (Seed, float, initialSeed, step)
import String exposing (fromFloat, fromInt)
import Time exposing (Posix)


type alias AnimatedModel =
    { real : Int
    , timeline : Timeline Int
    , seed : Seed
    }


type AnimationMsg
    = Tick Posix
    | IncrementSomething


main : Program () AnimatedModel AnimationMsg
main =
    element
        { init = init
        , update = update
        , view = view
        , subscriptions = subscriptions
        }


delayMillis =
    100


fuzz =
    50


limit =
    8


init : () -> ( AnimatedModel, Cmd AnimationMsg )
init () =
    ( { real = 0
      , timeline = Animator.init 0
      , seed = initialSeed 1
      }
    , after delayMillis Millisecond IncrementSomething
    )


update : AnimationMsg -> AnimatedModel -> ( AnimatedModel, Cmd AnimationMsg )
update msg model =
    case msg of
        Tick posix ->
            ( Animator.update posix animator model, Cmd.none )

        IncrementSomething ->
            let
                newModel =
                    model.real + 1

                ( fuzzedDelay, newSeed ) =
                    step (float (delayMillis - fuzz) (delayMillis + fuzz)) model.seed

                maybeKeepGoing =
                    if continue newModel then
                        after fuzzedDelay Millisecond IncrementSomething

                    else
                        Cmd.none
            in
            ( { model
                | real = newModel
                , timeline = go veryQuickly newModel model.timeline
                , seed = newSeed
              }
            , maybeKeepGoing
            )


continue i =
    i <= limit


view : AnimatedModel -> Html AnimationMsg
view model =
    div
        [ style "display" "flex"
        , style "flex-direction" "row"
        ]
        [ viewImpl "real" <| toFloat model.real
        , viewImpl "previous" <| toFloat <| previous model.timeline
        , viewImpl "current" <| toFloat <| current model.timeline
        , viewImpl "arrived" <| toFloat <| arrived model.timeline
        , viewImpl "move" <| move model.timeline (toFloat >> at)
        ]


viewImpl : String -> Float -> Html msg
viewImpl label i =
    div [ style "margin" "10px" ]
        [ div [] [ text label ]
        , div [] [ text <| fromFloat i ]
        ]


animator : Animator AnimatedModel
animator =
    let
        updateTimeline nt m =
            { m | timeline = nt }
    in
    Animator.animator
        |> watchingWith .timeline
            updateTimeline
            continue


subscriptions : AnimatedModel -> Sub AnimationMsg
subscriptions model =
    toSubscription Tick model animator
@Erudition
Copy link

Your Ellie is broken: "The Delay module does not expose TimeUnit"

@jerith666
Copy link
Author

Weird -- elm-delay 3.0.0 does expose it: https://package.elm-lang.org/packages/andrewMacmurray/elm-delay/3.0.0/Delay#TimeUnit. 4.0.0 removed it (andrewMacmurray/elm-delay#15).

The Ellie claims it's still at 3.0.0, but it seems like it's not really.

@jerith666
Copy link
Author

Looks like it's a known issue with Ellie: ellie-app/ellie#121 (see also ellie-app/ellie#57). I'll see about updating to the latest though.

@jerith666
Copy link
Author

Here's a new Ellie that reproduces it with elm-delay 4.0.0: https://ellie-app.com/g24Z3wVcPVqa1. It's non-deterministic. Here's the code:

module Main exposing (..)

import Animator exposing (Animator, Timeline, arrived, at, current, go, move, previous, toSubscription, veryQuickly, watchingWith)
import Browser exposing (Document, element)
import Delay exposing (after)
import Html exposing (Html, div, text)
import Html.Attributes exposing (style)
import Random exposing (Seed, initialSeed, int, step)
import String exposing (fromFloat)
import Time exposing (Posix)


type alias AnimatedModel =
    { real : Int
    , timeline : Timeline Int
    , arrivedEverMoved : Bool
    , arrivedWasBad : Bool
    , moveWasBad : Bool
    , seed : Seed
    }


type AnimationMsg
    = Tick Posix
    | IncrementSomething


main : Program () AnimatedModel AnimationMsg
main =
    element
        { init = init
        , update = update
        , view = view
        , subscriptions = subscriptions
        }


delayMillis =
    100


fuzz =
    50


limit =
    8


init : () -> ( AnimatedModel, Cmd AnimationMsg )
init () =
    ( { real = 0
      , timeline = Animator.init 0
      , arrivedEverMoved = False
      , arrivedWasBad = False
      , moveWasBad = False
      , seed = initialSeed 4
      }
    , after delayMillis IncrementSomething
    )


update : AnimationMsg -> AnimatedModel -> ( AnimatedModel, Cmd AnimationMsg )
update msg model =
    case msg of
        Tick posix ->
            ( updateEverWas <| Animator.update posix animator model, Cmd.none )

        IncrementSomething ->
            let
                newModel =
                    model.real + 1

                ( fuzzedDelay, newSeed ) =
                    step (int (delayMillis - fuzz) (delayMillis + fuzz)) model.seed

                maybeKeepGoing =
                    if continue newModel then
                        after fuzzedDelay IncrementSomething

                    else
                        Cmd.none

                t =
                    go veryQuickly newModel model.timeline

                m =
                    { model
                        | real = newModel
                        , timeline = t
                        , seed = newSeed
                    }
            in
            ( updateEverWas m
            , maybeKeepGoing
            )


updateEverWas : AnimatedModel -> AnimatedModel
updateEverWas model =
    let
        m =
            { model
                | arrivedEverMoved = model.arrivedEverMoved || (arrived model.timeline > 0)
            }

        mWB =
            m.moveWasBad || moveIsBad m

        aWB =
            m.arrivedWasBad || arrivedIsBad m
    in
    { m
        | moveWasBad = mWB
        , arrivedWasBad = aWB
    }


continue i =
    i <= limit


view : AnimatedModel -> Html AnimationMsg
view model =
    div
        [ style "display" "flex"
        , style "flex-direction" "row"
        ]
        [ viewImpl "real" False False <| toFloat model.real
        , viewImpl "previous" False False <| toFloat <| previous model.timeline
        , viewImpl "current" False False <| toFloat <| current model.timeline
        , viewImpl "arrived" (arrivedIsBad model) model.arrivedWasBad <| toFloat <| arrived model.timeline
        , viewImpl "move" (moveIsBad model) model.moveWasBad <| move model.timeline (toFloat >> at)
        ]


arrivedIsBad : AnimatedModel -> Bool
arrivedIsBad model =
    case model.arrivedEverMoved of
        False ->
            False

        True ->
            case model.real of
                0 ->
                    False

                _ ->
                    case arrived model.timeline of
                        0 ->
                            True

                        _ ->
                            False


moveIsBad : AnimatedModel -> Bool
moveIsBad model =
    let
        moveVal =
            move model.timeline (toFloat >> at)
    in
    toFloat model.real < moveVal


viewImpl : String -> Bool -> Bool -> Float -> Html msg
viewImpl label isBad wasBad i =
    let
        c =
            case isBad of
                True ->
                    "red"

                False ->
                    case wasBad of
                        True ->
                            "darksalmon"

                        False ->
                            "black"
    in
    div [ style "margin" "10px" ]
        [ div [ style "color" c ] [ text label ]
        , div [] [ text <| fromFloat i ]
        ]


animator : Animator AnimatedModel
animator =
    let
        updateTimeline nt m =
            { m | timeline = nt }
    in
    Animator.animator
        |> watchingWith .timeline
            updateTimeline
            continue


subscriptions : AnimatedModel -> Sub AnimationMsg
subscriptions model =
    toSubscription Tick model animator

@tankorsmash
Copy link

I'm having the same issue, where the ranges should be constrained to 0.0 and 1.0 at the bounds, but it goes above that when calling this:

valueProgress : Float
valueProgress =
    Animator.move timeline <|
        \state ->
            case state of
                Idle ->
                    Animator.at 0

                Active ->
                    Animator.at 1.0

                Inplace ->
                    Animator.at 0.5

It appears to depend on the duration of the animation itself, where if its short enough, it'll stick inside the bounds, but otherwise it'll go out, as high as 1.3.

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

No branches or pull requests

3 participants