BGE: Improve clock management
authorArnaud Degroote <arnaud.degroote@isae-supaero.fr>
Sat, 12 Dec 2015 01:37:42 +0000 (02:37 +0100)
committerJorge Bernal <jbernalmartinez@gmail.com>
Sat, 12 Dec 2015 01:46:53 +0000 (02:46 +0100)
This patch improves clock management in BGE, to be able to accelerate /
slow the time, and also to finely synchronize clock with external
engines. Several new python functions have been added and existence ones
have been improved for that purpose. Now we have:

- getClockTime(): Get the current BGE render time, in seconds. The BGE
render time is the simulation time corresponding to the next scene that
will be rendered.

- getFrameTime(): Get the current BGE frame time, in seconds. The BGE
frame time is the simulation time corresponding to the current call of
the logic system. Generally speaking, it is what the user is interested
in.

- getRealTime(): Get the number of real (system-clock) seconds elapsed
since the beginning of the simulation.

- getTimeScale(): Get the time multiplier between real-time and
simulation time. The default value is 1.0. A value greater than 1.0
means that the simulation is going faster than real-time, a value lower
than 1.0 means that the simulation is going slower than real-time.

- setTimeScale(time_scale): Set the time multiplier between real-time
and simulation time. A value greater than 1.0 means that the simulation
is going faster than real-time, a value lower than 1.0 means that the
simulation is going slower than real-time. Note that a too large value
may lead to some physics instabilities.

- getUseExternalClock(): Get if the BGE use the inner BGE clock, or rely
or on an external clock. The default is to use the inner BGE clock.

- setUseExternalClock(use_external_clock): Set if the BGE use the inner
BGE clock, or rely or on an external clock. If the user selects the use
of an external clock, he should call regularly the setClockTime method.

- setClockTime(new_time): Set the next value of the simulation clock. It
is preferable to use this method from a custom main function in python,
as calling it in the logic block can easily lead to a blocked system (if
the time does not advance enough to run at least the next logic step).

Rationale are described more precisely in the thread
http://lists.blender.org/pipermail/bf-gamedev/2013-November/000165.html.

See also T37640

Reviewers: sybren, panzergame, #game_engine, lordloki, moguri

Reviewed By: sybren, panzergame, #game_engine, lordloki, moguri

Subscribers: moguri, hg1, sybren, panzergame, dfelinto, lordloki

Projects: #game_engine

Maniphest Tasks: T37640

Differential Revision: https://developer.blender.org/D728

doc/python_api/rst/bge.logic.rst
source/gameengine/Ketsji/KX_KetsjiEngine.cpp
source/gameengine/Ketsji/KX_KetsjiEngine.h
source/gameengine/Ketsji/KX_PythonInit.cpp

index b119bdd1ba1e9245af1d586d89d38b01ec8185e0..3f35901234aaa0e32dce482fc69883c1a156e742 100644 (file)
@@ -378,6 +378,76 @@ General functions
 
    Render next frame (if Python has control)
 
+**********************
+Time related functions
+**********************
+
+.. function:: getClockTime()
+
+    Get the current BGE render time, in seconds. The BGE render time is the
+    simulation time corresponding to the next scene that will be rendered.
+
+    :rtype: double
+
+.. function:: getFrameTime()
+
+    Get the current BGE frame time, in seconds. The BGE frame time is the
+    simulation time corresponding to the current call of the logic system.
+    Generally speaking, it is what the user is interested in.
+
+    :rtype: double
+
+.. function:: getRealTime()
+
+    Get the number of real (system-clock) seconds elapsed since the beginning
+    of the simulation.
+
+    :rtype: double
+
+.. function:: getTimeScale()
+
+    Get the time multiplier between real-time and simulation time. The default
+    value is 1.0. A value greater than 1.0 means that the simulation is going
+    faster than real-time, a value lower than 1.0 means that the simulation is
+    going slower than real-time.
+
+    :rtype: double
+
+.. function:: setTimeScale(time_scale)
+
+    Set the time multiplier between real-time and simulation time. A value
+    greater than 1.0 means that the simulation is going faster than real-time,
+    a value lower than 1.0 means that the simulation is going slower than
+    real-time. Note that a too large value may lead to some physics
+    instabilities.
+
+    :arg time_scale: The new time multiplier.
+
+.. function:: getUseExternalClock()
+
+    Get if the BGE use the inner BGE clock, or rely or on an external
+    clock. The default is to use the inner BGE clock.
+
+    :rtype: bool
+
+.. function:: setUseExternalClock(use_external_clock)
+
+    Set if the BGE use the inner BGE clock, or rely or on an external
+    clock. If the user selects the use of an external clock, he should call
+    regularly the setClockTime method.
+
+    :arg use_external_clock: the new setting
+
+.. function:: setClockTime(new_time)
+
+    Set the next value of the simulation clock. It is preferable to use this
+    method from a custom main function in python, as calling it in the logic
+    block can easily lead to a blocked system (if the time does not advance
+    enough to run at least the next logic step).
+
+    :arg new_time: the next value of the BGE clock (in second).
+    
+
 *****************
 Utility functions
 *****************
index 5f36a980a53a056230314dad0e9c5c48a4f984dd..fa924f74290694d927a55368a2f62eaaeeb901b2 100644 (file)
@@ -128,6 +128,7 @@ KX_KetsjiEngine::KX_KetsjiEngine(KX_ISystem* system)
        m_bInitialized(false),
        m_activecam(0),
        m_bFixedTime(false),
+       m_useExternalClock(false),
        
        m_firstframe(true),
        
@@ -135,6 +136,8 @@ KX_KetsjiEngine::KX_KetsjiEngine(KX_ISystem* system)
        m_clockTime(0.f),
        m_previousClockTime(0.f),
        m_previousAnimTime(0.f),
+       m_timescale(1.0f),
+       m_previousRealTime(0.0f),
 
 
        m_exitcode(KX_EXIT_REQUEST_NO_REQUEST),
@@ -411,6 +414,7 @@ void KX_KetsjiEngine::StartEngine(bool clearIpo)
        m_clockTime = m_kxsystem->GetTimeInSeconds();
        m_frameTime = m_kxsystem->GetTimeInSeconds();
        m_previousClockTime = m_kxsystem->GetTimeInSeconds();
+       m_previousRealTime = m_kxsystem->GetTimeInSeconds();
 
        m_firstframe = true;
        m_bInitialized = true;
@@ -554,7 +558,7 @@ void KX_KetsjiEngine::EndFrame()
 
 bool KX_KetsjiEngine::NextFrame()
 {
-       double timestep = 1.0/m_ticrate;
+       double timestep =  m_timescale / m_ticrate;
        double framestep = timestep;
        //      static hidden::Clock sClock;
 
@@ -563,12 +567,43 @@ bool KX_KetsjiEngine::NextFrame()
        //float dt = sClock.getTimeMicroseconds() * 0.000001f;
        //sClock.reset();
 
-       if (m_bFixedTime) {
-               m_clockTime += timestep;
-       }
-       else {
-               // m_clockTime += dt;
-               m_clockTime = m_kxsystem->GetTimeInSeconds();
+       /*
+        * Clock advancement. There is basically three case:
+        *   - m_useExternalClock is true, the user is responsible to advance the time
+        *   manually using setClockTime, so here, we do not do anything.
+        *   - m_useExternalClock is false, m_bFixedTime is true, we advance for one
+        *   timestep, which already handle the time scaling parameter
+        *   - m_useExternalClock is false, m_bFixedTime is false, we consider how much
+        *   time has elapsed since last call and we scale this time by the time
+        *   scaling parameter. If m_timescale is 1.0 (default value), the clock
+        *   corresponds to the computer clock.
+        *
+        * Once clockTime has been computed, we will compute how many logic frames
+        * will be executed before the next rendering phase (which will occur at "clockTime").
+        * The game time elapsing between two logic frames (called framestep)
+        * depends on several variables:
+        *   - ticrate 
+        *   - max_physic_frame
+        *   - max_logic_frame
+        * XXX The logic over computation framestep is definitively not clear (and
+        * I'm not even sure it is correct). If needed frame is strictly greater
+        * than max_physics_frame, we are doing a jump in game time, but keeping
+        * framestep = 1 / ticrate, while if frames is greater than
+        * max_logic_frame, we increase framestep.
+        *
+        * XXX render.fps is not considred anywhere.
+        */
+       if (!m_useExternalClock) {
+               if (m_bFixedTime) {
+                       m_clockTime += timestep;
+               }
+               else {
+                       double current_time = m_kxsystem->GetTimeInSeconds();
+                       double dt = current_time - m_previousRealTime;
+                       m_previousRealTime = current_time;
+                       // m_clockTime += dt;
+                       m_clockTime += dt * m_timescale;
+               }
        }
        
        double deltatime = m_clockTime - m_frameTime;
@@ -579,16 +614,14 @@ bool KX_KetsjiEngine::NextFrame()
                return false;
        }
 
-
        // Compute the number of logic frames to do each update (fixed tic bricks)
-       int frames =int(deltatime*m_ticrate+1e-6);
+       int frames = int(deltatime * m_ticrate / m_timescale + 1e-6);
 //     if (frames>1)
 //             printf("****************************************");
 //     printf("dt = %f, deltatime = %f, frames = %d\n",dt, deltatime,frames);
        
 //     if (!frames)
 //             PIL_sleep_ms(1);
-       
        KX_SceneList::iterator sceneit;
        
        if (frames>m_maxPhysicsFrame)
@@ -1756,6 +1789,10 @@ void KX_KetsjiEngine::SetUseFixedTime(bool bUseFixedTime)
        m_bFixedTime = bUseFixedTime;
 }
 
+void KX_KetsjiEngine::SetUseExternalClock(bool useExternalClock)
+{
+       m_useExternalClock = useExternalClock;
+}
 
 void   KX_KetsjiEngine::SetAnimRecordMode(bool animation_record, int startFrame)
 {
@@ -1783,6 +1820,11 @@ bool KX_KetsjiEngine::GetUseFixedTime(void) const
        return m_bFixedTime;
 }
 
+bool KX_KetsjiEngine::GetUseExternalClock(void) const
+{
+       return m_useExternalClock;
+}
+
 double KX_KetsjiEngine::GetSuspendedDelta()
 {
        return m_suspendeddelta;
@@ -1798,6 +1840,16 @@ void KX_KetsjiEngine::SetTicRate(double ticrate)
        m_ticrate = ticrate;
 }
 
+double KX_KetsjiEngine::GetTimeScale() const
+{
+       return m_timescale;
+}
+
+void KX_KetsjiEngine::SetTimeScale(double timescale)
+{
+       m_timescale = timescale;
+}
+
 int KX_KetsjiEngine::GetMaxLogicFrame()
 {
        return m_maxLogicFrame;
@@ -1838,6 +1890,11 @@ double KX_KetsjiEngine::GetClockTime(void) const
        return m_clockTime;
 }
 
+void KX_KetsjiEngine::SetClockTime(double externalClockTime)
+{
+       m_clockTime = externalClockTime;
+}
+
 double KX_KetsjiEngine::GetFrameTime(void) const
 {
        return m_frameTime;
index ec855be3212c5677fb2d53597c3da0b37d9bc698..3b8cec2ac824ee3b0497c41ac360bde04dc5b6bc 100644 (file)
@@ -103,16 +103,19 @@ private:
        bool                            m_bInitialized;
        int                                     m_activecam;
        bool                            m_bFixedTime;
+       bool                            m_useExternalClock;
        
        
        bool                            m_firstframe;
        int                                     m_currentFrame;
 
-       double                          m_frameTime;//discrete timestamp of the 'game logic frame'
-       double                          m_clockTime;//current time
-       double                          m_previousClockTime;//previous clock time
-       double                          m_previousAnimTime; //the last time animations were updated
+       double                          m_frameTime; // current logic game time
+       double                          m_clockTime; // game time for the next rendering step
+       double                          m_previousClockTime; // game time of the previous rendering step
+       double                          m_previousAnimTime; //game time when the animations were last updated
        double                          m_remainingTime;
+       double                          m_timescale; // time scaling parameter. if > 1.0, time goes faster than real-time. If < 1.0, times goes slower than real-time.
+       double                          m_previousRealTime;
 
        static int                              m_maxLogicFrame;        /* maximum number of consecutive logic frame */
        static int                              m_maxPhysicsFrame;      /* maximum number of consecutive physics frame */
@@ -297,15 +300,37 @@ public:
        bool GetUseFixedTime(void) const;
 
        /**
-        * Returns current render frame clock time
+        * Sets if the BGE relies on a external clock or its own internal clock
+        */
+       void SetUseExternalClock(bool bUseExternalClock);
+
+       /**
+        * Returns if we rely on an external clock
+        * \return Current setting
+        */
+       bool GetUseExternalClock(void) const;
+
+       /**
+        * Returns next render frame game time
         */
        double GetClockTime(void) const;
+
+       /**
+        * Set the next render frame game time. It will impact also frame time, as
+        * this one is derived from clocktime
+        */
+       void SetClockTime(double externalClockTime);
+
        /**
-        * Returns current logic frame clock time
+        * Returns current logic frame game time
         */
        double GetFrameTime(void) const;
 
+       /**
+        * Returns the real (system) time
+        */
        double GetRealTime(void) const;
+
        /**
         * Returns the difference between the local time of the scene (when it
         * was running and not suspended) and the "curtime"
@@ -361,6 +386,16 @@ public:
         */
        static double GetAverageFrameRate();
 
+       /**
+        * Gets the time scale multiplier 
+        */
+       double GetTimeScale() const;
+
+       /**
+        * Sets the time scale multiplier
+        */
+       void SetTimeScale(double scale);
+
        static void SetExitKey(short key);
 
        static short GetExitKey();
index d35f09dd820cef927143c0550af58df831d94d78..8a010726bbada7f99bd0a562cd26205490068b22 100644 (file)
@@ -548,6 +548,64 @@ static PyObject *gPyGetAverageFrameRate(PyObject *)
        return PyFloat_FromDouble(KX_KetsjiEngine::GetAverageFrameRate());
 }
 
+static PyObject *gPyGetUseExternalClock(PyObject *)
+{
+       return PyBool_FromLong(gp_KetsjiEngine->GetUseExternalClock());
+}
+
+static PyObject *gPySetUseExternalClock(PyObject *, PyObject *args)
+{
+       bool bUseExternalClock;
+
+       if (!PyArg_ParseTuple(args, "p:setUseExternalClock", &bUseExternalClock))
+               return NULL;
+
+       gp_KetsjiEngine->SetUseExternalClock(bUseExternalClock);
+       Py_RETURN_NONE;
+}
+
+static PyObject *gPyGetClockTime(PyObject *)
+{
+       return PyFloat_FromDouble(gp_KetsjiEngine->GetClockTime());
+}
+
+static PyObject *gPySetClockTime(PyObject *, PyObject *args)
+{
+       double externalClockTime;
+
+       if (!PyArg_ParseTuple(args, "d:setClockTime", &externalClockTime))
+               return NULL;
+
+       gp_KetsjiEngine->SetClockTime(externalClockTime);
+       Py_RETURN_NONE;
+}
+
+static PyObject *gPyGetFrameTime(PyObject *)
+{
+       return PyFloat_FromDouble(gp_KetsjiEngine->GetFrameTime());
+}
+
+static PyObject *gPyGetRealTime(PyObject *)
+{
+       return PyFloat_FromDouble(gp_KetsjiEngine->GetRealTime());
+}
+
+static PyObject *gPyGetTimeScale(PyObject *)
+{
+       return PyFloat_FromDouble(gp_KetsjiEngine->GetTimeScale());
+}
+
+static PyObject *gPySetTimeScale(PyObject *, PyObject *args)
+{
+       double time_scale;
+
+       if (!PyArg_ParseTuple(args, "d:setTimeScale", &time_scale))
+                       return NULL;
+
+       gp_KetsjiEngine->SetTimeScale(time_scale);
+       Py_RETURN_NONE;
+}
+
 static PyObject *gPyGetBlendFileList(PyObject *, PyObject *args)
 {
        char cpath[sizeof(gp_GamePythonPath)];
@@ -847,7 +905,19 @@ static struct PyMethodDef game_methods[] = {
        {"setAnimRecordFrame", (PyCFunction) gPySetAnimRecordFrame, METH_VARARGS, (const char *)"Sets the current frame number used for animation recording"},
        {"getExitKey", (PyCFunction) gPyGetExitKey, METH_NOARGS, (const char *)"Gets the key used to exit the game engine"},
        {"setExitKey", (PyCFunction) gPySetExitKey, METH_VARARGS, (const char *)"Sets the key used to exit the game engine"},
+       {"getUseExternalClock", (PyCFunction) gPyGetUseExternalClock, METH_NOARGS, (const char *)"Get if we use the time provided by an external clock"},
+       {"setUseExternalClock", (PyCFunction) gPySetUseExternalClock, METH_VARARGS, (const char *)"Set if we use the time provided by an external clock"},
+       {"getClockTime", (PyCFunction) gPyGetClockTime, METH_NOARGS, (const char *)"Get the last BGE render time. "
+       "The BGE render time is the simulated time corresponding to the next scene that will be renderered"},
+       {"setClockTime", (PyCFunction) gPySetClockTime, METH_VARARGS, (const char *)"Set the BGE render time. "
+       "The BGE render time is the simulated time corresponding to the next scene that will be rendered"},
+       {"getFrameTime", (PyCFunction) gPyGetFrameTime, METH_NOARGS, (const char *)"Get the BGE last frametime. "
+       "The BGE frame time is the simulated time corresponding to the last call of the logic system"},
+       {"getRealTime", (PyCFunction) gPyGetRealTime, METH_NOARGS, (const char *)"Get the real system time. "
+       "The real-time corresponds to the system time" },
        {"getAverageFrameRate", (PyCFunction) gPyGetAverageFrameRate, METH_NOARGS, (const char *)"Gets the estimated average frame rate"},
+       {"getTimeScale", (PyCFunction) gPyGetTimeScale, METH_NOARGS, (const char *)"Get the time multiplier"},
+       {"setTimeScale", (PyCFunction) gPySetTimeScale, METH_VARARGS, (const char *)"Set the time multiplier"},
        {"getBlendFileList", (PyCFunction)gPyGetBlendFileList, METH_VARARGS, (const char *)"Gets a list of blend files in the same directory as the current blend file"},
        {"PrintGLInfo", (PyCFunction)pyPrintExt, METH_NOARGS, (const char *)"Prints GL Extension Info"},
        {"PrintMemInfo", (PyCFunction)pyPrintStats, METH_NOARGS, (const char *)"Print engine statistics"},