如何在Haskell中编写游戏事件循环(例如setTimeout)? 弗斯特

如何在Haskell中编写游戏事件循环(例如setTimeout)? 弗斯特,haskell,timeout,Haskell,Timeout,在Haskell中实现一个函数是否存在或者有多麻烦 主要思想是 setTimeout someFunction 1000 --someFunction will be called in 1000ms = 1s 我是Haskell的新手,可能不会使用它;我学习哈斯克尔是为了好玩 第二 游戏的事件循环通常要求您 根据规则修改内部游戏对象(通常是物理) 更新您的图形 在这里,管理时间是非常重要的(因此设置超时),以确保恒定的FPS 像这样的模式在Haskell上看起来如何?这个问题对于纯函数没有

在Haskell中实现一个函数是否存在或者有多麻烦

主要思想是

setTimeout someFunction 1000 --someFunction will be called in 1000ms = 1s
我是Haskell的新手,可能不会使用它;我学习哈斯克尔是为了好玩


第二 游戏的事件循环通常要求您

  • 根据规则修改内部游戏对象(通常是物理)
  • 更新您的图形
  • 在这里,管理时间是非常重要的(因此设置超时),以确保恒定的FPS


    像这样的模式在Haskell上看起来如何?

    这个问题对于纯函数没有意义,因为Haskell进行惰性求值。函数将在需要其结果时被调用,而不是更早或更晚

    但是,如果要延迟某些IO操作,可以在GHC中使用
    Control.Concurrent
    中的
    threadDelay
    函数。然后,您的
    设置超时将实现为

    setTimeout :: IO () -> Int -> IO ThreadId
    
    setTimeout ioOperation ms =
      forkIO $ do
        threadDelay (ms*1000)
        ioOperation
    
    在Haskell中实现setTimeout函数是否存在或者会有多麻烦

    JavaScript和其他类似方法(如Qt)通常在单个线程的单个事件循环中工作(不包括web工作线程或
    QThread
    )。用Haskell对精确的行为进行建模有点困难,尽管并非不可能,因为GHC已经提供了一个(内部)模型

    如果您不关心实际的单线程行为(这也意味着具有几乎相同超时的两个函数可能同时执行),那么您可以简单地分叉一个新线程并延迟其操作:

    doLater :: Int -> IO () -> IO ThreadId
    doLater ms io = forkIO $ threadDelay (ms * 1000) >> io
    
    对于任何进一步的想法,我们实际上需要更多地了解您的特定事件循环

    哈斯克尔身上这样的图案看起来怎么样

    非常,非常概括起来,您需要这样的东西:

    mkGame :: World 
              -> (Input -> World -> World) 
              -> (TimeDiff -> World -> World)
              -> (World -> GraphicalRepresentation)
              -> IO ()
    mkGame initialWorld handleInput handleProgression drawWorld = undefined
    
    请注意,您可能会在其中抛出其他参数,例如每秒世界更新的最大数量

    我们现在如何建模setTimeout?假设
    World
    类似于:

    data World = World { 
         getWorldData    :: WorldData, 
         getCurrentTime  :: TimePoint, -- would get updated by your loop
         getTimeouts     :: Queue TimePoint (World -> World)
    }
    
    其中,
    Queue
    是任何工作优先级队列(有很多,或者构建自己的队列)。现在,您可以使用

    setTimeout world delay action = world { getTimeouts = timeouts' }
        where timeouts' = insert (getTimeouts world) t action
              t         = delay + getCurrentTime world
    
    -- insert :: Queue p v -> p -> v -> Queue p v
    
    如果你想取消超时,你还需要一个
    队列
    上的键,看看有没有灵感

    除此之外,您现在可以检查时间是否已过,并通过简单地遍历队列并应用所有函数在游戏循环中相应地执行操作


    这基本上是一个非常简单和粗糙的基础,你可以用它来激发你自己的工作。根据您实际想要做的事情,您可能想看一看,它已经实现了一个非常类似的概念,虽然没有超时,但在这种情况下,您仍然可以将超时和总时差添加到您的世界中,只需使用一个合适的更新功能(
    TimeDiff->world->world
    部分).

    这是一个老问题,但我对答案不满意(没有一个被标记为正确),因为太多的注意力集中在重新实现
    setTimeout
    函数上,我认为这是对setTimeout在使用javascript制作游戏时的用途的误解

    我可以假设您想要替换此javascript模式吗

    函数更新(){
    未定义;
    设置间隔(更新,1000)
    }
    
    Javascript之外的游戏循环通常不是使用
    setTimeout
    编写的,setTimeout是浏览器中JS异步特性的一种变通方法。 另外,对于时间关键型可视代码,不建议使用
    setTimeout
    requestAnimationFrame
    是最佳功能

    编程游戏循环的惯用方法是简单循环,伪haskell代码如下所示:

    mkGame :: World 
              -> (Input -> World -> World) 
              -> (TimeDiff -> World -> World)
              -> (World -> GraphicalRepresentation)
              -> IO ()
    mkGame initialWorld handleInput handleProgression drawWorld = undefined
    
    draw::World->IO()
    画w=do
    未定义
    glSwapBuffers
    updateWorld::World->World
    updateWorld w=未定义
    循环世界->时间->IO()
    循环世界时间=do
    
    输入可能应该在那里抛出一个
    forkIO
    ,我同意。
    setTimeout
    函数是异步的。因此,它应该是
    setTimeout op ms=forkIO$threadDelay(1000*ms)>>op
    。另外,无论如何,返回
    ThreadId
    更有意义。
    setTimeout
    函数返回JavaScript中的id。根据您的回答,我将问题修改为一些新的疑问。有许多事情我仍然不明白,主要是因为我对不可变状态缺乏经验。@DanielWagner我错过了!完成。有人能在最后将
    ->IO a
    修复为
    ->IO线程ID
    吗?我试图编辑明显的bug总是被拒绝。通常,至少有一位评论员告诉我我改变太多,而至少有一位评论员告诉我我改变太少。你的第一个问题有点基本,但内容相当完整。另外两个问题更为实质性,似乎是谷歌搜索的主要目标。你做到了吗?你想到了什么?结果你试过做什么?你的尝试失败在哪里?(确保只标记目标语言,除非问题是关于与多种语言相关的直接比较。)有点离题,但我的回答可能很有趣: