Pause RGSS execution when moving into background on Android

Assuming that there is enough memory for mkxp to stay in the
background and that the OS doesn't kill the process, this should
allow smooth resuming after moving back into the foreground.

For now, EGL context loss is not handled.
This commit is contained in:
Jonas Kulla 2015-01-15 08:02:21 +01:00
parent 012d87d05a
commit 7c6a2b2c62
7 changed files with 212 additions and 7 deletions

View File

@ -23,6 +23,7 @@
#include "sharedstate.h"
#include "sharedmidistate.h"
#include "eventthread.h"
#include "filesystem.h"
#include "aldatasource.h"
#include "fluid-fun.h"
@ -361,6 +362,8 @@ void ALStream::streamData()
* refill and queue them up again */
while (true)
{
shState->rtData().syncPoint.passSecondarySync();
ALint procBufs = AL::Source::getProcBufferCount(alSrc);
while (procBufs--)

View File

@ -25,6 +25,7 @@
#include "soundemitter.h"
#include "sharedstate.h"
#include "sharedmidistate.h"
#include "eventthread.h"
#include "sdl-util.h"
#include <string>
@ -40,6 +41,8 @@ struct AudioPrivate
SoundEmitter se;
SyncPoint &syncPoint;
/* The 'MeWatch' is responsible for detecting
* a playing ME, quickly fading out the BGM and
* keeping it paused/stopped while the ME plays,
@ -60,11 +63,12 @@ struct AudioPrivate
MeWatchState state;
} meWatch;
AudioPrivate(const Config &conf)
AudioPrivate(RGSSThreadData &rtData)
: bgm(ALStream::Looped, "bgm"),
bgs(ALStream::Looped, "bgs"),
me(ALStream::NotLooped, "me"),
se(conf)
se(rtData.config),
syncPoint(rtData.syncPoint)
{
meWatch.state = MeNotPlaying;
meWatch.thread = createSDLThread
@ -84,6 +88,8 @@ struct AudioPrivate
while (true)
{
syncPoint.passSecondarySync();
if (meWatch.termReq)
return;
@ -231,8 +237,8 @@ struct AudioPrivate
}
};
Audio::Audio(const Config &conf)
: p(new AudioPrivate(conf))
Audio::Audio(RGSSThreadData &rtData)
: p(new AudioPrivate(rtData))
{}

View File

@ -33,7 +33,7 @@
* quite make out their meaning yet) */
struct AudioPrivate;
struct Config;
struct RGSSThreadData;
class Audio
{
@ -70,7 +70,7 @@ public:
void reset();
private:
Audio(const Config &conf);
Audio(RGSSThreadData &rtData);
~Audio();
friend struct SharedStatePrivate;

View File

@ -76,6 +76,8 @@ void EventThread::process(RGSSThreadData &rtData)
SDL_Window *win = rtData.window;
UnidirMessage<Vec2i> &windowSizeMsg = rtData.windowSizeMsg;
SDL_SetEventFilter(eventFilter, &rtData);
fullscreen = rtData.config.fullscreen;
int toggleFSMod = rtData.config.anyAltToggleFS ? KMOD_ALT : KMOD_LALT;
@ -402,12 +404,63 @@ void EventThread::process(RGSSThreadData &rtData)
break;
}
/* Just in case */
rtData.syncPoint.resumeThreads();
if (SDL_JoystickGetAttached(js))
SDL_JoystickClose(js);
delete sMenu;
}
int EventThread::eventFilter(void *data, SDL_Event *event)
{
RGSSThreadData &rtData = *static_cast<RGSSThreadData*>(data);
switch (event->type)
{
case SDL_APP_WILLENTERBACKGROUND :
Debug() << "SDL_APP_WILLENTERBACKGROUND";
rtData.syncPoint.haltThreads();
return 0;
case SDL_APP_DIDENTERBACKGROUND :
Debug() << "SDL_APP_DIDENTERBACKGROUND";
return 0;
case SDL_APP_WILLENTERFOREGROUND :
Debug() << "SDL_APP_WILLENTERFOREGROUND";
return 0;
case SDL_APP_DIDENTERFOREGROUND :
Debug() << "SDL_APP_DIDENTERFOREGROUND";
rtData.syncPoint.resumeThreads();
return 0;
case SDL_APP_TERMINATING :
Debug() << "SDL_APP_TERMINATING";
return 0;
case SDL_APP_LOWMEMORY :
Debug() << "SDL_APP_LOWMEMORY";
return 0;
case SDL_RENDER_TARGETS_RESET :
Debug() << "****** SDL_RENDER_TARGETS_RESET";
return 0;
case SDL_RENDER_DEVICE_RESET :
Debug() << "****** SDL_RENDER_DEVICE_RESET";
return 0;
}
return 1;
}
void EventThread::cleanup()
{
SDL_Event event;
@ -539,3 +592,87 @@ void EventThread::notifyFrame()
event.user.type = usrIdStart + UPDATE_FPS;
SDL_PushEvent(&event);
}
void SyncPoint::haltThreads()
{
if (mainSync.locked)
return;
/* Lock the reply sync first to avoid races */
reply.lock();
/* Lock main sync and sleep until RGSS thread
* reports back */
mainSync.lock();
reply.waitForUnlock();
/* Now that the RGSS thread is asleep, we can
* safely put the other threads to sleep as well
* without causing deadlocks */
secondSync.lock();
}
void SyncPoint::resumeThreads()
{
if (!mainSync.locked)
return;
mainSync.unlock(false);
secondSync.unlock(true);
}
bool SyncPoint::mainSyncLocked()
{
return mainSync.locked;
}
void SyncPoint::waitMainSync()
{
reply.unlock(false);
mainSync.waitForUnlock();
}
void SyncPoint::passSecondarySync()
{
if (!secondSync.locked)
return;
secondSync.waitForUnlock();
}
SyncPoint::Util::Util()
{
mut = SDL_CreateMutex();
cond = SDL_CreateCond();
}
SyncPoint::Util::~Util()
{
SDL_DestroyCond(cond);
SDL_DestroyMutex(mut);
}
void SyncPoint::Util::lock()
{
locked.set();
}
void SyncPoint::Util::unlock(bool multi)
{
locked.clear();
if (multi)
SDL_CondBroadcast(cond);
else
SDL_CondSignal(cond);
}
void SyncPoint::Util::waitForUnlock()
{
SDL_LockMutex(mut);
while (locked)
SDL_CondWait(cond, mut);
SDL_UnlockMutex(mut);
}

View File

@ -39,6 +39,7 @@
struct RGSSThreadData;
typedef struct ALCdevice_struct ALCdevice;
struct SDL_Window;
union SDL_Event;
#define MAX_FINGERS 4
@ -98,6 +99,8 @@ public:
void notifyFrame();
private:
static int eventFilter(void *, SDL_Event*);
void resetInputStates();
void setFullscreen(SDL_Window *, bool mode);
void updateCursorState(bool inWindow);
@ -174,6 +177,39 @@ private:
T current;
};
struct SyncPoint
{
/* Used by eventFilter to control sleep/wakeup */
void haltThreads();
void resumeThreads();
/* Used by RGSS thread */
bool mainSyncLocked();
void waitMainSync();
/* Used by secondary (audio) threads */
void passSecondarySync();
private:
struct Util
{
Util();
~Util();
void lock();
void unlock(bool multi);
void waitForUnlock();
AtomicFlag locked;
SDL_mutex *mut;
SDL_cond *cond;
};
Util mainSync;
Util reply;
Util secondSync;
};
struct RGSSThreadData
{
/* Main thread sets this to request RGSS thread to terminate */
@ -191,6 +227,7 @@ struct RGSSThreadData
EventThread *ethread;
UnidirMessage<Vec2i> windowSizeMsg;
UnidirMessage<BDescVec> bindingUpdateMsg;
SyncPoint syncPoint;
const char *argv0;

View File

@ -466,6 +466,7 @@ struct GraphicsPrivate
ScreenScene screen;
RGSSThreadData *threadData;
SDL_GLContext glCtx;
int frameRate;
int frameCount;
@ -489,6 +490,7 @@ struct GraphicsPrivate
winSize(rtData->config.defScreenW, rtData->config.defScreenH),
screen(scRes.x, scRes.y),
threadData(rtData),
glCtx(SDL_GL_GetCurrentContext()),
frameRate(DEF_FRAMERATE),
frameCount(0),
brightness(255),
@ -622,6 +624,21 @@ struct GraphicsPrivate
swapGLBuffer();
}
void checkSyncLock()
{
if (!threadData->syncPoint.mainSyncLocked())
return;
/* Releasing the GL context before sleeping and making it
* current again on wakeup seems to avoid the context loss
* when the app moves into the background on Android */
SDL_GL_MakeCurrent(threadData->window, 0);
threadData->syncPoint.waitMainSync();
SDL_GL_MakeCurrent(threadData->window, glCtx);
fpsLimiter.resetFrameAdjust();
}
};
Graphics::Graphics(RGSSThreadData *data)
@ -651,6 +668,7 @@ Graphics::~Graphics()
void Graphics::update()
{
p->checkShutDownReset();
p->checkSyncLock();
if (p->frozen)
return;
@ -692,6 +710,8 @@ void Graphics::transition(int duration,
const char *filename,
int vague)
{
p->checkSyncLock();
if (!p->frozen)
return;
@ -752,6 +772,8 @@ void Graphics::transition(int duration,
return;
}
p->checkSyncLock();
const float prog = i * (1.0 / duration);
if (transMap)

View File

@ -108,7 +108,7 @@ struct SharedStatePrivate
midiState(threadData->config),
graphics(threadData),
input(*threadData),
audio(threadData->config),
audio(*threadData),
fontState(threadData->config),
stampCounter(0)
{