Implement F12 game reset (MRI only)

Can be disabled with "enableReset=false".

While at it, also replace the flakey volatile bool flags
with proper atomics.
This commit is contained in:
Jonas Kulla 2014-08-24 07:36:19 +02:00
parent 3a2e560139
commit d223d83cbf
23 changed files with 343 additions and 60 deletions

View File

@ -15,7 +15,6 @@ Missing RGSS3 functionality:
* Text outline
* Movie playback
* F12 reset
* Audio fade-in
Some other things might be implemented, but simply not bound yet.
@ -116,7 +115,6 @@ If a requested font is not found, no error is generated. Instead, a built-in fon
* wma audio files
* The Win32API ruby class (for obvious reasons)
* Restarting the game with F12
* Creating Bitmaps with sizes greater than the OpenGL texture size limit (around 8192 on modern cards)*
\* There is an exception to this, called *mega surface*. When a Bitmap bigger than the texture limit is created from a file, it is not stored in VRAM, but regular RAM. Its sole purpose is to be used as a tileset bitmap. Any other operation to it (besides blitting to a regular Bitmap) will result in an error.

View File

@ -97,6 +97,15 @@ DEF_FADE( me )
DEF_PLAY_STOP( se )
RB_METHOD(audioReset)
{
RB_UNUSED_PARAM;
shState->audio().reset();
return Qnil;
}
#define BIND_PLAY_STOP(entity) \
_rb_define_module_function(module, #entity "_play", audio_##entity##Play); \
@ -129,4 +138,6 @@ audioBindingInit()
}
BIND_PLAY_STOP( se )
_rb_define_module_function(module, "__reset__", audioReset);
}

View File

@ -27,6 +27,7 @@
#include "util.h"
#include "debugwriter.h"
#include "graphics.h"
#include "audio.h"
#include "boost-hash.h"
#include <ruby.h>
@ -44,11 +45,13 @@ extern const char module_rpg3[];
static void mriBindingExecute();
static void mriBindingTerminate();
static void mriBindingReset();
ScriptBinding scriptBindingImpl =
{
mriBindingExecute,
mriBindingTerminate
mriBindingTerminate,
mriBindingReset
};
ScriptBinding *scriptBinding = &scriptBindingImpl;
@ -213,12 +216,51 @@ RB_METHOD(mriDataDirectory)
return pathStr;
}
static VALUE rgssMainCb(VALUE block)
{
rb_funcall2(block, rb_intern("call"), 0, 0);
return Qnil;
}
static VALUE rgssMainRescue(VALUE arg, VALUE exc)
{
VALUE *excRet = (VALUE*) arg;
*excRet = exc;
return Qnil;
}
static void processReset()
{
shState->graphics().reset();
shState->audio().reset();
shState->rtData().rqReset.clear();
shState->graphics().repaintWait(shState->rtData().rqResetFinish,
false);
}
RB_METHOD(mriRgssMain)
{
RB_UNUSED_PARAM;
// TODO: Implement F12 reset
rb_yield(Qnil);
while (true)
{
VALUE exc = Qnil;
rb_rescue2((VALUE(*)(ANYARGS)) rgssMainCb, rb_block_proc(),
(VALUE(*)(ANYARGS)) rgssMainRescue, (VALUE) &exc,
rb_eException, (VALUE) 0);
if (NIL_P(exc))
break;
if (rb_obj_class(exc) == getRbData()->exc[Reset])
processReset();
else
rb_exc_raise(exc);
}
return Qnil;
}
@ -395,30 +437,39 @@ static void runRMXPScripts(BacktraceData &btData)
for (size_t i = 0; i < conf.preloadScripts.size(); ++i)
runCustomScript(conf.preloadScripts[i]);
for (long i = 0; i < scriptCount; ++i)
while (true)
{
VALUE script = rb_ary_entry(scriptArray, i);
VALUE scriptDecoded = rb_ary_entry(script, 3);
VALUE string = newStringUTF8(RSTRING_PTR(scriptDecoded),
RSTRING_LEN(scriptDecoded));
for (long i = 0; i < scriptCount; ++i)
{
VALUE script = rb_ary_entry(scriptArray, i);
VALUE scriptDecoded = rb_ary_entry(script, 3);
VALUE string = newStringUTF8(RSTRING_PTR(scriptDecoded),
RSTRING_LEN(scriptDecoded));
VALUE fname;
const char *scriptName = RSTRING_PTR(rb_ary_entry(script, 1));
char buf[512];
int len;
VALUE fname;
const char *scriptName = RSTRING_PTR(rb_ary_entry(script, 1));
char buf[512];
int len;
if (conf.useScriptNames)
len = snprintf(buf, sizeof(buf), "%03ld:%s", i, scriptName);
else
len = snprintf(buf, sizeof(buf), SCRIPT_SECTION_FMT, i);
if (conf.useScriptNames)
len = snprintf(buf, sizeof(buf), "%03ld:%s", i, scriptName);
else
len = snprintf(buf, sizeof(buf), SCRIPT_SECTION_FMT, i);
fname = newStringUTF8(buf, len);
btData.scriptNames.insert(buf, scriptName);
fname = newStringUTF8(buf, len);
btData.scriptNames.insert(buf, scriptName);
int state;
evalString(string, fname, &state);
if (state)
int state;
evalString(string, fname, &state);
if (state)
break;
}
VALUE exc = rb_gv_get("$!");
if (rb_obj_class(exc) != getRbData()->exc[Reset])
break;
processReset();
}
}
@ -520,10 +571,15 @@ static void mriBindingExecute()
ruby_cleanup(0);
shState->rtData().rqTermAck = true;
shState->rtData().rqTermAck.set();
}
static void mriBindingTerminate()
{
rb_raise(rb_eSystemExit, " ");
}
static void mriBindingReset()
{
rb_raise(getRbData()->exc[Reset], " ");
}

View File

@ -51,6 +51,8 @@ RbData::RbData()
for (size_t i = 0; i < ARRAY_SIZE(customExc); ++i)
exc[customExc[i].id] = rb_define_class(customExc[i].name, rb_eException);
exc[Reset] = rb_define_class(rgssVer >= 3 ? "RGSSReset" : "Reset", rb_eException);
exc[ErrnoENOENT] = rb_const_get(rb_const_get(rb_cObject, rb_intern("Errno")), rb_intern("ENOENT"));
exc[IOError] = rb_eIOError;
exc[TypeError] = rb_eTypeError;

View File

@ -29,6 +29,7 @@
enum RbException
{
RGSS = 0,
Reset,
PHYSFS,
SDL,
MKXP,

View File

@ -174,6 +174,15 @@ RB_METHOD(graphicsResizeScreen)
return Qnil;
}
RB_METHOD(graphicsReset)
{
RB_UNUSED_PARAM;
shState->graphics().reset();
return Qnil;
}
DEF_GRA_PROP_I(FrameRate)
DEF_GRA_PROP_I(FrameCount)
DEF_GRA_PROP_I(Brightness)
@ -196,6 +205,8 @@ void graphicsBindingInit()
_rb_define_module_function(module, "transition", graphicsTransition);
_rb_define_module_function(module, "frame_reset", graphicsFrameReset);
_rb_define_module_function(module, "__reset__", graphicsReset);
INIT_GRA_PROP_BIND( FrameRate, "frame_rate" );
INIT_GRA_PROP_BIND( FrameCount, "frame_count" );

View File

@ -51,11 +51,13 @@
static void mrbBindingExecute();
static void mrbBindingTerminate();
static void mrbBindingReset();
ScriptBinding scriptBindingImpl =
{
mrbBindingExecute,
mrbBindingTerminate
mrbBindingTerminate,
mrbBindingReset
};
ScriptBinding *scriptBinding = &scriptBindingImpl;
@ -384,7 +386,7 @@ static void mrbBindingExecute()
checkException(mrb);
shState->rtData().rqTermAck = true;
shState->rtData().rqTermAck.set();
shState->texPool().disable();
mrbc_context_free(mrb, ctx);
@ -398,3 +400,8 @@ static void mrbBindingTerminate()
mrb_raise(mrb, data->exc[Shutdown], "");
}
static void mrbBindingReset()
{
// No idea how to do this with mruby yet
}

View File

@ -27,7 +27,7 @@
static void nullBindingExecute()
{
Debug() << "The null binding doesn't do anything, so we're done!";
shState->rtData().rqTermAck = true;
shState->rtData().rqTermAck.set();
}
static void nullBindingTerminate()
@ -35,10 +35,16 @@ static void nullBindingTerminate()
}
static void nullBindingReset()
{
}
ScriptBinding scriptBindingImpl =
{
nullBindingExecute,
nullBindingTerminate
nullBindingTerminate,
nullBindingReset
};
ScriptBinding *scriptBinding = &scriptBindingImpl;

View File

@ -99,6 +99,12 @@
# anyAltToggleFS=false
# Enable F12 game reset
# (default: enabled)
#
# enableReset=true
# Allow symlinks for game assets to be followed
# (default: disabled)
#

View File

@ -327,4 +327,12 @@ float Audio::bgsPos()
return p->bgs.playingOffset();
}
void Audio::reset()
{
p->bgm.stop();
p->bgs.stop();
p->me.stop();
p->se.stop();
}
Audio::~Audio() { delete p; }

View File

@ -67,6 +67,8 @@ public:
float bgmPos();
float bgsPos();
void reset();
private:
Audio(const Config &conf);
~Audio();

View File

@ -35,6 +35,10 @@ struct ScriptBinding
* function will perform a longjmp instead of returning,
* so be careful about any variables with local storage */
void (*terminate) (void);
/* Instructs the binding to issue a game reset.
* Same conditions as for terminate apply */
void (*reset) (void);
};
/* VTable defined in the binding source */

View File

@ -138,6 +138,7 @@ Config::Config()
solidFonts(false),
gameFolder("."),
anyAltToggleFS(false),
enableReset(true),
allowSymlinks(false),
pathCache(true),
useScriptNames(false)
@ -164,6 +165,7 @@ void Config::read(int argc, char *argv[])
PO_DESC(solidFonts, bool) \
PO_DESC(gameFolder, std::string) \
PO_DESC(anyAltToggleFS, bool) \
PO_DESC(enableReset, bool) \
PO_DESC(allowSymlinks, bool) \
PO_DESC(iconPath, std::string) \
PO_DESC(titleLanguage, std::string) \

View File

@ -47,6 +47,7 @@ struct Config
std::string gameFolder;
bool anyAltToggleFS;
bool enableReset;
bool allowSymlinks;
bool pathCache;

View File

@ -22,7 +22,10 @@
#ifndef DISPOSABLE_H
#define DISPOSABLE_H
#include "intrulist.h"
#include "exception.h"
#include "sharedstate.h"
#include "graphics.h"
#include <assert.h>
#include <sigc++/signal.h>
@ -32,12 +35,15 @@ class Disposable
{
public:
Disposable()
: disposed(false)
{}
: disposed(false),
link(this)
{
shState->graphics().addDisposable(this);
}
virtual ~Disposable()
{
assert(disposed);
shState->graphics().remDisposable(this);
}
void dispose()
@ -69,7 +75,10 @@ private:
virtual void releaseResources() = 0;
virtual const char *klassName() const = 0;
friend class Graphics;
bool disposed;
IntruListLink<Disposable> link;
};
template<class C>

View File

@ -106,6 +106,8 @@ void EventThread::process(RGSSThreadData &rtData)
char pendingTitle[128];
bool havePendingTitle = false;
bool resetting = false;
while (true)
{
if (!SDL_WaitEvent(&event))
@ -206,10 +208,34 @@ void EventThread::process(RGSSThreadData &rtData)
break;
}
if (event.key.keysym.scancode == SDL_SCANCODE_F12)
{
if (!rtData.config.enableReset)
break;
if (resetting)
break;
resetting = true;
rtData.rqResetFinish.clear();
rtData.rqReset.set();
break;
}
keyStates[event.key.keysym.scancode] = true;
break;
case SDL_KEYUP :
if (event.key.keysym.scancode == SDL_SCANCODE_F12)
{
if (!rtData.config.enableReset)
break;
resetting = false;
rtData.rqResetFinish.set();
break;
}
keyStates[event.key.keysym.scancode] = false;
break;
@ -271,7 +297,7 @@ void EventThread::process(RGSSThreadData &rtData)
rtData.config.game.title.c_str(),
(const char*) event.user.data1, win);
free(event.user.data1);
msgBoxDone = true;
msgBoxDone.set();
break;
case REQUEST_SETCURSORVISIBLE :
@ -377,7 +403,7 @@ void EventThread::requestShowCursor(bool mode)
void EventThread::showMessageBox(const char *body, int flags)
{
msgBoxDone = false;
msgBoxDone.clear();
SDL_Event event;
event.user.code = flags;
@ -386,7 +412,7 @@ void EventThread::showMessageBox(const char *body, int flags)
SDL_PushEvent(&event);
/* Keep repainting screen while box is open */
shState->graphics().repaintWait(&msgBoxDone);
shState->graphics().repaintWait(msgBoxDone);
/* Prevent endless loops */
resetInputStates();
}

View File

@ -29,6 +29,7 @@
#include <SDL_joystick.h>
#include <SDL_mouse.h>
#include <SDL_mutex.h>
#include <SDL_atomic.h>
#include <string>
@ -38,6 +39,32 @@ struct RGSSThreadData;
struct SDL_Thread;
struct SDL_Window;
struct AtomicFlag
{
AtomicFlag()
{
clear();
}
void set()
{
SDL_AtomicSet(&atom, 1);
}
void clear()
{
SDL_AtomicSet(&atom, 0);
}
operator bool() const
{
return SDL_AtomicGet(&atom);
}
private:
mutable SDL_atomic_t atom;
};
class EventThread
{
public:
@ -91,7 +118,7 @@ private:
bool fullscreen;
bool showCursor;
volatile bool msgBoxDone;
AtomicFlag msgBoxDone;
struct
{
@ -111,13 +138,12 @@ struct WindowSizeNotify
{
SDL_mutex *mutex;
volatile bool changedFlag;
volatile int w, h;
AtomicFlag changed;
int w, h;
WindowSizeNotify()
{
mutex = SDL_CreateMutex();
changedFlag = false;
w = h = 0;
}
@ -133,7 +159,7 @@ struct WindowSizeNotify
this->w = w;
this->h = h;
changedFlag = true;
changed.set();
SDL_UnlockMutex(mutex);
}
@ -141,14 +167,14 @@ struct WindowSizeNotify
/* Done from the receiving side */
bool pollChange(int *w, int *h)
{
if (!changedFlag)
if (!changed)
return false;
SDL_LockMutex(mutex);
*w = this->w;
*h = this->h;
changedFlag = false;
changed.clear();
SDL_UnlockMutex(mutex);
@ -159,10 +185,16 @@ struct WindowSizeNotify
struct RGSSThreadData
{
/* Main thread sets this to request RGSS thread to terminate */
volatile bool rqTerm;
AtomicFlag rqTerm;
/* In response, RGSS thread sets this to confirm
* that it received the request and isn't stuck */
volatile bool rqTermAck;
AtomicFlag rqTermAck;
/* Set when F12 is pressed */
AtomicFlag rqReset;
/* Set when F12 is released */
AtomicFlag rqResetFinish;
EventThread *ethread;
WindowSizeNotify windowSizeMsg;
@ -182,9 +214,7 @@ struct RGSSThreadData
const char *argv0,
SDL_Window *window,
const Config& newconf)
: rqTerm(false),
rqTermAck(false),
ethread(ethread),
: ethread(ethread),
argv0(argv0),
window(window),
sizeResoRatio(1, 1),

View File

@ -33,6 +33,8 @@
#include "texpool.h"
#include "bitmap.h"
#include "etc-internal.h"
#include "disposable.h"
#include "intrulist.h"
#include "binding.h"
#include "debugwriter.h"
@ -109,6 +111,19 @@ struct PingPong
bind();
}
void clearBuffers()
{
glState.clearColor.pushSet(Vec4(0, 0, 0, 1));
for (int i = 0; i < 2; ++i)
{
FBO::bind(rt[i].fbo);
FBO::clear();
}
glState.clearColor.pop();
}
private:
void bind()
{
@ -388,6 +403,10 @@ struct GraphicsPrivate
Quad screenQuad;
TEXFBO transBuffer;
/* Global list of all live Disposables
* (disposed on reset) */
IntruList<Disposable> dispList;
GraphicsPrivate(RGSSThreadData *rtData)
: scRes(DEF_SCREEN_W, DEF_SCREEN_H),
scSize(scRes),
@ -472,9 +491,15 @@ struct GraphicsPrivate
}
}
void checkShutDownReset()
{
shState->checkShutdown();
shState->checkReset();
}
void shutdown()
{
threadData->rqTermAck = true;
threadData->rqTermAck.set();
shState->texPool().disable();
scriptBinding->terminate();
@ -540,7 +565,7 @@ Graphics::~Graphics()
void Graphics::update()
{
shState->checkShutdown();
p->checkShutDownReset();
if (p->frozen)
return;
@ -571,7 +596,7 @@ void Graphics::freeze()
{
p->frozen = true;
shState->checkShutdown();
p->checkShutDownReset();
p->checkResize();
/* Capture scene into frozen buffer */
@ -623,10 +648,23 @@ void Graphics::transition(int duration,
for (int i = 0; i < duration; ++i)
{
/* We need to clean up transMap properly before
* a possible longjmp, so we manually test for
* shutdown/reset here */
if (p->threadData->rqTerm)
{
glState.blend.pop();
delete transMap;
p->shutdown();
return;
}
if (p->threadData->rqReset)
{
glState.blend.pop();
delete transMap;
scriptBinding->reset();
return;
}
const float prog = i * (1.0 / duration);
@ -694,8 +732,7 @@ void Graphics::wait(int duration)
{
for (int i = 0; i < duration; ++i)
{
shState->checkShutdown();
p->checkResize();
p->checkShutDownReset();
p->redrawScreen();
}
}
@ -823,6 +860,29 @@ void Graphics::setBrightness(int value)
p->screen.setBrightness(value / 255.0);
}
void Graphics::reset()
{
/* Dispose all live Disposables */
IntruListLink<Disposable> *iter;
for (iter = p->dispList.begin();
iter != p->dispList.end();
iter = iter->next)
{
iter->data->dispose();
}
p->dispList.clear();
/* Reset attributes (frame count not included) */
p->fpsLimiter.resetFrameAdjust();
p->frozen = false;
p->screen.getPP().clearBuffers();
setFrameRate(DEF_FRAMERATE);
setBrightness(255);
}
bool Graphics::getFullscreen() const
{
return p->threadData->ethread->getFullscreen();
@ -848,9 +908,9 @@ Scene *Graphics::getScreen() const
return &p->screen;
}
void Graphics::repaintWait(volatile bool *exitCond)
void Graphics::repaintWait(const AtomicFlag &exitCond, bool checkReset)
{
if (*exitCond)
if (exitCond)
return;
/* Repaint the screen with the last good frame we drew */
@ -858,10 +918,13 @@ void Graphics::repaintWait(volatile bool *exitCond)
GLMeta::blitBeginScreen(p->winSize);
GLMeta::blitSource(lastFrame);
while (!*exitCond)
while (!exitCond)
{
shState->checkShutdown();
if (checkReset)
shState->checkReset();
FBO::clear();
p->metaBlitBufferFlippedScaled();
SDL_GL_SwapWindow(p->threadData->window);
@ -872,3 +935,13 @@ void Graphics::repaintWait(volatile bool *exitCond)
GLMeta::blitEnd();
}
void Graphics::addDisposable(Disposable *d)
{
p->dispList.append(d->link);
}
void Graphics::remDisposable(Disposable *d)
{
p->dispList.remove(d->link);
}

View File

@ -26,8 +26,10 @@
class Scene;
class Bitmap;
class Disposable;
struct RGSSThreadData;
struct GraphicsPrivate;
struct AtomicFlag;
class Graphics
{
@ -53,6 +55,8 @@ public:
int height() const;
void resizeScreen(int width, int height);
void reset();
/* Non-standard extension */
DECL_ATTR( Fullscreen, bool )
DECL_ATTR( ShowCursor, bool )
@ -60,14 +64,20 @@ public:
/* <internal> */
Scene *getScreen() const;
/* Repaint screen with static image until exitCond
* turns true. Used in EThread::showMessageBox() */
void repaintWait(volatile bool *exitCond);
* is set. Observes reset flag on top of shutdown
* if "checkReset" */
void repaintWait(const AtomicFlag &exitCond,
bool checkReset = true);
private:
Graphics(RGSSThreadData *data);
~Graphics();
void addDisposable(Disposable *);
void remDisposable(Disposable *);
friend struct SharedStatePrivate;
friend class Disposable;
GraphicsPrivate *p;
};

View File

@ -105,6 +105,15 @@ public:
size--;
}
void clear()
{
remove(root);
root.prev = &root;
root.next = &root;
size = 0;
}
T *tail() const
{
IntruListLink<T> *node = root.prev;

View File

@ -46,7 +46,7 @@ rgssThreadError(RGSSThreadData *rtData, const std::string &msg)
{
rtData->rgssErrorMsg = msg;
rtData->ethread->requestTerminate();
rtData->rqTermAck = true;
rtData->rqTermAck.set();
}
static inline const char*
@ -147,7 +147,7 @@ int rgssThreadFun(void *userdata)
/* Start script execution */
scriptBinding->execute();
threadData->rqTermAck = true;
threadData->rqTermAck.set();
threadData->ethread->requestTerminate();
SharedState::finiInstance();
@ -276,7 +276,7 @@ int main(int argc, char *argv[])
eventThread.process(rtData);
/* Request RGSS thread to stop */
rtData.rqTerm = true;
rtData.rqTerm.set();
/* Wait for RGSS thread response */
for (int i = 0; i < 1000; ++i)

View File

@ -330,11 +330,20 @@ void SharedState::checkShutdown()
if (!p->rtData.rqTerm)
return;
p->rtData.rqTermAck = true;
p->rtData.rqTermAck.set();
p->texPool.disable();
scriptBinding->terminate();
}
void SharedState::checkReset()
{
if (!p->rtData.rqReset)
return;
p->rtData.rqReset.clear();
scriptBinding->reset();
}
Font &SharedState::defaultFont() const
{
return *p->defaultFont;

View File

@ -109,6 +109,8 @@ struct SharedState
* function will most likely not return */
void checkShutdown();
void checkReset();
static SharedState *instance;
static int rgssVersion;