mdgriffith / elm-animator

A timeline-based animation engine for Elm
https://package.elm-lang.org/packages/mdgriffith/elm-animator/latest/
BSD 3-Clause "New" or "Revised" License
133 stars 14 forks source link

arrived returns incorrect state #19

Open jerith666 opened 3 years ago

jerith666 commented 3 years ago

Sometimes arrived will return an old state. This is illustrated in this ellie. Code reproduced below for reference.

Basically the program is randomly incrementing an Int every 50 - 150 ms, and updating the Timeline Int with veryQuickly which is 100 ms. When these timings overlap, the arrived value occasionally reverts to the initial state for some reason. In the example, you'll see this as the third column occasionally zeroing out.

I haven't explored all the permutations of timings, but you can vary delayMillis and fuzz in the code below to experiment.

module Sandbox.AnimatorTest exposing (..)

import Animator exposing (Animator, Timeline, arrived, current, go, toSubscription, veryQuickly, watchingWith)
import Browser exposing (Document, element)
import Delay exposing (TimeUnit(..), after)
import Dict exposing (Dict, fromList)
import Html exposing (Html, div, text)
import Html.Attributes exposing (style)
import Random exposing (Seed, float, initialSeed, step)
import String exposing (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" model.real
        , viewImpl "current" <| current model.timeline
        , viewImpl "arrived" <| arrived model.timeline
        ]

viewImpl : String -> Int -> Html msg
viewImpl label i =
    div [ style "margin" "10px" ]
        [ div [] [ text label ]
        , div [] [ text <| fromInt 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
jerith666 commented 3 years ago

I've simplified the example a bit.

Augustin82 commented 3 years ago

I'm experiencing this issue as well, where my Timeline eventually reaches "Nothing", which is correctly returned by "current", but "arrived" still shows the previous "Just X".