From b7e420f96e5649ebc6bbbfc012fd002e5069fefa Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sun, 22 Dec 2024 09:01:12 -0800 Subject: Use fixed time points in animation. --- Spear/App.hs | 29 +++++++++++++++++------------ Spear/Sys/Timer.hsc | 13 +++++++++++++ Spear/Sys/Timer/timer.c | 9 +++++++++ Spear/Sys/Timer/timer.h | 3 +++ 4 files changed, 42 insertions(+), 12 deletions(-) diff --git a/Spear/App.hs b/Spear/App.hs index f8afc9e..ac5f7a5 100644 --- a/Spear/App.hs +++ b/Spear/App.hs @@ -59,50 +59,55 @@ loop app window = do resizeApp app (ResizeEvent width height) renderApp app - let ddt = fpsToDdt . maxFPS . options $ app -- Desired frame delta time. + let ddt = fpsToDdt . maxFPS . options $ app -- Desired render time step. let animationDdt = fpsToDdt . animationFPS . options $ app -- Desired animation time step. timer <- gameIO newTimer gameIO $ Timer.start timer - loop' window ddt animationDdt 0 timer app + let lastAnimationTime = lastTick timer + loop' window ddt animationDdt lastAnimationTime timer app loop' :: Window -> TimeDelta -> -- Desired frame time delta. TimeDelta -> -- Desired animation time delta. - TimeDelta -> -- Time budget. + TimePoint -> -- Time point of last animation update. Timer -> App s -> Game s () -loop' window ddt animationDdt timeBudget inputTimer app = do +loop' window ddt animationDdt lastAnimationTime inputTimer app = do timer <- gameIO $ tick inputTimer windowEvents <- gameIO $ pollWindowEvents window close <- gameIO $ shouldWindowClose window - (continue, timeBudgetNextFrame) <- case animationDdt of + (continue, lastAnimationTimeNextFrame) <- case animationDdt of 0 -> do -- Variable time step game animation. let t = timeDeltaToSec $ runningTime timer let dt = timeDeltaToSec $ deltaTime timer inputEvents <- gameIO $ pollInputEvents window continue <- stepApp app t dt inputEvents - return (continue, 0) -- budget unused. + return (continue, lastAnimationTime) _ -> do -- Fixed time step animation. - let elapsed = runningTime timer + {- let elapsed = runningTime timer let dt = timeDeltaToSec ddt let timeBudgetThisFrame = timeBudget + deltaTime timer let timeBudgetNextFrame = timeBudgetThisFrame `mod` ddt + let steps = timeBudgetThisFrame `div` ddt -} + --gameIO . print $ "Steps: " ++ show steps ++ ", Budget: " ++ show timeBudgetThisFrame ++ ", ddt: " ++ show ddt + let elapsed = runningTime timer + let dt = timeDeltaToSec ddt + let timeBudgetThisFrame = timeDiff lastAnimationTime (lastTick timer) let steps = timeBudgetThisFrame `div` ddt - gameIO . print $ steps - + let lastAnimationTimeNextFrame = timeAdd lastAnimationTime (timeBudgetThisFrame `mod` ddt) + --gameIO . print $ "Steps: " ++ show steps ++ ", Budget: " ++ show timeBudgetThisFrame ++ ", ddt: " ++ show ddt continue <- and <$> forM [1..steps] (\i -> do inputEvents <- gameIO $ pollInputEvents window let t = timeDeltaToSec $ elapsed + i * ddt stepApp app t dt inputEvents) - - return (continue, timeBudgetNextFrame) + return (continue, lastAnimationTimeNextFrame) -- Process window events. resized <- or <$> forM windowEvents (\event -> case event of @@ -121,4 +126,4 @@ loop' window ddt animationDdt timeBudget inputTimer app = do gameIO $ Timer.sleep (ddt - frameTime) when (continue && not close) $ do - loop' window ddt animationDdt timeBudgetNextFrame timer app + loop' window ddt animationDdt lastAnimationTimeNextFrame timer app diff --git a/Spear/Sys/Timer.hsc b/Spear/Sys/Timer.hsc index 98b88d6..fb18521 100644 --- a/Spear/Sys/Timer.hsc +++ b/Spear/Sys/Timer.hsc @@ -14,6 +14,7 @@ module Spear.Sys.Timer , timeDeltaToSec , secToTimeDelta , timePointToNs +, timeAdd , sleep ) where @@ -115,6 +116,9 @@ foreign import ccall safe "timer.h sec_to_time_delta" foreign import ccall safe "timer.h time_point_to_ns" c_time_point_to_ns :: Ptr TimePoint -> Word64 +foreign import ccall safe "timer.h time_add" + c_time_add :: Ptr TimePoint -> TimeDelta -> Ptr TimePoint -> IO () + foreign import ccall "timer.h time_sleep" c_time_sleep :: TimeDelta -> IO () @@ -172,6 +176,15 @@ timePointToNs t = unsafeDupablePerformIO $ alloca $ \ptr -> do poke ptr t return $ c_time_point_to_ns ptr +-- | Add a time delta to a timestamp. +timeAdd :: TimePoint -> TimeDelta -> TimePoint +timeAdd t dt = unsafeDupablePerformIO $ + alloca $ \tPtr -> + alloca $ \ptr -> do + poke tPtr t + c_time_add tPtr dt ptr + peek ptr + -- | Put the caller thread to sleep for the given amount of time. sleep :: TimeDelta -> IO () sleep = c_time_sleep diff --git a/Spear/Sys/Timer/timer.c b/Spear/Sys/Timer/timer.c index 707cd63..4a2fb2b 100644 --- a/Spear/Sys/Timer/timer.c +++ b/Spear/Sys/Timer/timer.c @@ -87,6 +87,15 @@ uint64_t time_point_to_ns(time_point* t) { #endif } +void time_add(const time_point* t, time_delta dt, time_point* out) { +#ifdef _WIN32 + *out = *t + dt; +#else + out->tv_sec = t->tv_sec + (dt / nanoseconds); + out->tv_nsec = t->tv_nsec + (dt % nanoseconds); +#endif +} + void time_sleep(time_delta dt) { #ifdef _WIN32 const int64_t ms = dt / microseconds; diff --git a/Spear/Sys/Timer/timer.h b/Spear/Sys/Timer/timer.h index e426135..da4e7c7 100644 --- a/Spear/Sys/Timer/timer.h +++ b/Spear/Sys/Timer/timer.h @@ -53,6 +53,9 @@ time_delta sec_to_time_delta(double seconds); /// Convert the time point to nanoseconds. uint64_t time_point_to_ns(time_point*); +/// Add a time delta to a timestamp. +void time_add(const time_point*, time_delta, time_point* out); + /// Put the caller thread to sleep for the given amount of time. void time_sleep(time_delta dt); -- cgit v1.2.3