BGE: Improve clock management

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
This commit is contained in:
Arnaud Degroote 2015-12-12 02:37:42 +01:00 committed by Jorge Bernal
parent e089b1f08b
commit aae93ae4c6
4 changed files with 248 additions and 16 deletions

View 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
*****************

View 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;

View 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;
/**
* Returns current logic frame clock time
* 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 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();

View 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"},