First
是否存在或在 Haskell 中实现 setTimeout 函数会有多大惊小怪?
主要思想是
setTimeout someFunction 1000 --someFunction will be called in 1000ms = 1s
我是 Haskell 的新手,可能不会使用它;我正在学习Haskell的踢球。
第二
游戏的事件循环通常需要您
- 根据规则(通常是物理(修改内部游戏对象
- 更新图形
管理时间在这里很重要(因此设置超时(以确保恒定的 FPS。
像这样的图案在哈斯克尔身上会是什么样子?
是否存在或在 Haskell 中实现 setTimeout 函数会有多大惊小怪?
JavaScript 的 setTimeout
和其他类似方法(如 Qt 的 QTimer(通常在单个线程的单个事件循环中工作(不包括 Web worker 或 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
请注意,您可能会在其中抛出其他参数,例如每秒最大世界更新数。
我们现在如何对设置超时进行建模?假设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
如果你想能够取消超时,你还需要一个Queue
上的键,看看GHC。活动.PSQ的灵感。
除此之外,您现在可以检查时间是否已过去,并通过简单地浏览队列并应用所有函数来在游戏循环中采取相应的行动。
这基本上是一个非常简单和粗糙的基础,你可以用它来激发你自己的工作。根据您实际想要执行的操作,您可能需要查看 gloss
,它已经实现了一个非常相似的概念,尽管没有超时,但在这种情况下,您仍然可以将超时和总时差添加到您的世界中,只需使用合适的更新功能(TimeDiff -> World -> World
部分(。
这个问题对于纯函数没有意义,因为Haskell会进行惰性求值。函数将在需要其结果时调用,而不是更早,也不会更晚。
但是,如果您有一些 IO 操作想要延迟,您可以在 GHC 中使用 Control.Concurrent
中的 threadDelay
功能。然后,您的setTimeout
将实现为
setTimeout :: IO () -> Int -> IO ThreadId
setTimeout ioOperation ms =
forkIO $ do
threadDelay (ms*1000)
ioOperation
老问题,但我对答案不满意(而且没有一个被标记为正确(,因为过于关注重新实现setTimeout
函数,我认为这是对 setTimeout 在使用 JavaScript 制作游戏时使用的误解。
我可以假设你想要这个javascript模式的替代品吗?
function update() {
undefined;
setInterval(update, 1000)
}
Javascript之外的游戏循环通常不使用setTimeout
编写,setTimeout是浏览器中JS异步性质的一个变通方法。顺便说一句,不建议将setTimeout
用于时间关键的视觉代码,requestAnimationFrame
是最佳功能。
编程游戏循环的惯用方法是一个简单的循环,伪 haskell 代码如下所示:
draw :: World -> IO ()
draw w = do
undefined
glSwapBuffers
updateWorld :: World -> World
updateWorld w = undefined
loop World -> Time -> IO ()
loop world time = do
inputs <- getInputs
time' <- getTime
let delta = time' - time
let world' = updateWorld delta world inputs
draw world'
loop world' time'
setup :: IO World
setup = do
undefined -- window setup etc
world <- undefined
return world
main :: IO ()
main = do
w <- setup
t <- getTime
loop w t
您的draw
函数应调用glSwapBuffers
(或等效功能(,在大多数设备上,它会阻塞直到 vsync,这将保持流畅的 fps,但不要假设所有计算机都启用了 vsync,也不要假设所有显示器都是 60hz、144hz 和可变帧速率技术现在在游戏玩家社区中很普遍。
像这样的循环如果不能使 16.67ms 窗口(不像使用 forkIO 构建循环的任何东西(也会安全地降级,尽管 Haskell 的微线程使得使用它们编程并不疯狂,但要确保使用它们是线程安全的,例如使用 STM 之类的东西。
时钟或时间包将提供 getTime 函数。
Zeta的mkGame
是更函数化的风格(因为它将函数作为参数(,并且可以很容易地适应我的代码。
另请注意,像 sdl 这样的库提供了自己的循环和输入以及计时方法,这可能会简化(或复杂化(您的代码。