Haskell Gloss: временная переменная остается постоянной

Я пытаюсь повернуть изображение в Haskell, используя текущее время в качестве значения для функции поворота. У меня есть такая main функция:

main :: IO ()
main = do
    args <- getArgs
    time <- round <$> getPOSIXTime
    let initial'        = initial time
    let (w, h, display) = chooseDisplay args
    let background      = black
    let fps             = 60
    play display background fps initial' (draw w h) eventHandler timeHandler

Треугольник (= player) хранится внутри типа данных World: module Model, где

data World = World {
        -- all kinds of World properties --
        player           :: Picture
    }

Затем у меня есть функция initial, которая инициализирует мир, и функция playerBody, которая, учитывая значение поворота, возвращает изображение player:

initial :: Int -> World
initial _ = World{player = playerBody 0}

playerBody :: Float -> Picture
playerBody rot = Rotate rot $ color red $ Polygon[(-10, 100), (10, 100), (0, 125)]

Функция рисования определяется следующим образом:

draw :: Float -> Float -> World -> Picture
draw _ _ world = player world

В настоящее время он просто возвращает playerPicture.

Теперь в моем модуле timeHandler я хочу использовать время (заданное timeHandler в функции main) для поворота player следующим образом:

timeHandler :: Float -> World -> World
timeHandler time = (\world -> world {player = playerBody time} )

Это не работает. Я заменил time постоянным значением (в функции timeHandler), и это действительно повернуло изображение. Похоже, time не обновляется .. что я делаю не так?


person Felix    schedule 22.10.2016    source источник
comment
Не ответ: нужно прояснить одно: time в main и time в timeHandler не имеют ничего общего друг с другом - фактически, у них даже нет одного и того же типа. Первое - это локальное значение, полученное при выполнении getPosixTime, а второе - параметр в независимом определении функции. Просто у них одно и то же имя.   -  person duplode    schedule 22.10.2016
comment
Я знаю об этом @duplode! Однако, согласно описанию функции play, timeHandler следует указывать время в секундах.   -  person Felix    schedule 22.10.2016
comment
Хорошо, просто ваша фраза, указанная для timeHandler в функции main, меня немного смутила. (Вы сами не передаете time в `timeHandler`; скорее, Gloss сам подбирает его где-нибудь в реализации play.)   -  person duplode    schedule 22.10.2016
comment
В любом случае, если я понимаю документы, ожидаемым поведением вашей программы будет поворот изображения на один градус в секунду (т.е. 1/60 градуса за кадр), верно?   -  person duplode    schedule 22.10.2016
comment
Именно этого я и ожидал.   -  person Felix    schedule 22.10.2016
comment
Что draw делает? Вы должны включить весь свой код. Также этот пример не кажется минимальным - seed не используется в initial, поэтому вам нужно просто удалить его. Сведение к минимуму примера может помочь вам увидеть ошибку.   -  person user2407038    schedule 22.10.2016
comment
Вы правы, я обновил свой пост, включая функцию рисования!   -  person Felix    schedule 22.10.2016


Ответы (1)


Естественно, это не работает, timeHandler получает число, которое на практике близко к временной дельте с момента предыдущего кадра - docs говорится:" Функция, которая шагает по миру на одну итерацию. Ей передан период времени ( в секундах) необходимо продвинуть вперед ". - и предположительно время кадра примерно постоянное, поэтому, естественно, можно было бы ожидать, что результат будет примерно постоянным (и числом, очень близким к 0).

Вам нужно собрать все дельты и сложить их. Если вас интересует только время с начала моделирования, тогда вам не нужно getCurrentTime в main - просто добавьте дельты. Это означает, что вы должны сохранить время в своем состоянии. Если вам действительно нужно иметь дело с реальным временем, я предлагаю вам придерживаться UTCTime или другой абстракции, которая проясняет, каким количеством вы манипулируете:

import Graphics.Gloss 
import Graphics.Gloss.Interface.Pure.Game
import Data.Time.Clock

data World = World 
  { startTime 
  , time :: UTCTime 
  } 

-- make explicit what units are being used
seconds2DiffTime :: Float -> NominalDiffTime
seconds2DiffTime = realToFrac 
diffTime2seconds :: NominalDiffTime -> Float 
diffTime2seconds = realToFrac 

displayWorld World{time,startTime} = 
  Rotate rot $ color red $ Polygon[(-10, 100), (10, 100), (0, 125)]
    where rot = diffTime2seconds $ diffUTCTime time startTime 

updateWorld dt w = w {time = addUTCTime (seconds2DiffTime dt) $ time w}

main = do 
  t0 <- getCurrentTime
  let initialState = World { time = t0, startTime = t0 }
  play (InWindow "" (400,400) (400,400)) 
       white
       30 
       intialState 
       displayWorld 
       (const id) 
       updateWorld

Это дает вам как время, прошедшее с начала моделирования, так и реальное время на часах.

Обратите внимание, что вы не должны помещать код рисования изображения внутри timeHandler - эту функцию можно было бы вызывать гораздо чаще, чем необходимо для перерисовки изображения, что потребовало бы большой дополнительной работы. Вместо этого сделайте свой рисунок в displayWorld, как указано выше.

person user2407038    schedule 22.10.2016
comment
Танки за ответ! Единственное, что меня немного смущает: согласно документации play, updateWorld должен получать время в секундах. В вашем коде вы создаете свою собственную временную переменную с time <- getCurrentTime. Как это передается updateWorld? - person Felix; 22.10.2016
comment
Откуда у вас эта идея? Я связался с документами и скопировал вставленный соответствующий бит - пошаговая функция не получает время в секундах с момента начала моделирования или в любое другое время, но количество времени, в течение которого симуляция должна быть ступенчатым (т.е. временной дельтой). В любом случае, начальные startTime и time, объявленные в main, находятся в начальном состоянии - play ... World{..} ... - что эквивалентно World{field1 = field1, field2 = field2, <etc>} - это RecordWildCards. - person user2407038; 23.10.2016
comment
RecordWildCards действительно может сбивать с толку, если вы не знакомы с ним, и не имеет отношения к проблеме, поэтому я избавился от него и использовал обычный синтаксис записи. - person user2407038; 23.10.2016
comment
В этом есть смысл. Отслеживание времени в состоянии на таком языке, как Haskell, логично. Спасибо! - person Felix; 23.10.2016