Throw exceptions for Resets and Exits instead of directly raising.

While this does close small memory leaks, this is mostly for threading reasons. We're not supposed to call rb_raise with the gvl released, and calling rb_raise prevents GFX_UNLOCK from being called, which would cause problems for any games that want to call graphical operations in multiple threads should the user reset.

We're also now calling Graphics.__reset__ and Audio.__reset__ via eval instead of directly calling the functions, in case a game wants to hook them.
This commit is contained in:
Wayward Heart 2024-06-12 04:17:31 -05:00
parent 2622a84c53
commit 99ad4fa636
10 changed files with 172 additions and 86 deletions

View file

@ -315,21 +315,29 @@ static void printP(int argc, VALUE *argv, const char *convMethod,
}
RB_METHOD(mriPrint) {
RB_METHOD_GUARD(mriPrint) {
RB_UNUSED_PARAM;
printP(argc, argv, "to_s", "");
shState->checkShutdown();
shState->checkReset();
return Qnil;
}
RB_METHOD_GUARD_END
RB_METHOD(mriP) {
RB_METHOD_GUARD(mriP) {
RB_UNUSED_PARAM;
printP(argc, argv, "inspect", "\n");
shState->checkShutdown();
shState->checkReset();
return Qnil;
}
RB_METHOD_GUARD_END
RB_METHOD(mkxpDelta) {
RB_UNUSED_PARAM;
@ -757,15 +765,21 @@ static VALUE rgssMainRescue(VALUE arg, VALUE exc) {
return Qnil;
}
static void processReset() {
shState->graphics().reset();
shState->audio().reset();
shState->rtData().rqReset.clear();
shState->graphics().repaintWait(shState->rtData().rqResetFinish, false);
static bool processReset(bool rubyExc) {
const char *str = "Audio.__reset__; Graphics.__reset__;";
if (rubyExc) {
rb_eval_string(str);
} else {
int state;
rb_eval_string_protect(str, &state);
return state;
}
return 0;
}
RB_METHOD(mriRgssMain) {
RB_METHOD_GUARD(mriRgssMain) {
RB_UNUSED_PARAM;
while (true) {
@ -783,15 +797,16 @@ RB_METHOD(mriRgssMain) {
break;
if (rb_obj_class(exc) == getRbData()->exc[Reset])
processReset();
processReset(true);
else
rb_exc_raise(exc);
}
return Qnil;
}
RB_METHOD_GUARD_END
RB_METHOD(mriRgssStop) {
RB_METHOD_GUARD(mriRgssStop) {
RB_UNUSED_PARAM;
while (true)
@ -799,6 +814,7 @@ RB_METHOD(mriRgssStop) {
return Qnil;
}
RB_METHOD_GUARD_END
RB_METHOD(_kernelCaller) {
RB_UNUSED_PARAM;
@ -1038,7 +1054,8 @@ static void runRMXPScripts(BacktraceData &btData) {
if (rb_obj_class(exc) != getRbData()->exc[Reset])
break;
processReset();
if (processReset(false))
break;
}
}
@ -1247,6 +1264,6 @@ static void mriBindingExecute() {
shState->rtData().rqTermAck.set();
}
static void mriBindingTerminate() { rb_raise(rb_eSystemExit, " "); }
static void mriBindingTerminate() { throw Exception(Exception::SystemExit, " "); }
static void mriBindingReset() { rb_raise(getRbData()->exc[Reset], " "); }
static void mriBindingReset() { throw Exception(Exception::Reset, " "); }

View file

@ -58,8 +58,9 @@ RbData::~RbData() {}
/* Indexed with Exception::Type */
const RbException excToRbExc[] = {
RGSS, /* RGSSError */
ErrnoENOENT, /* NoFileError */
RGSS, /* RGSSError */
Reset, /* Reset/RGSSReset */
ErrnoENOENT, /* NoFileError */
IOError,
TypeError, ArgumentError, SystemExit, RuntimeError,
@ -317,3 +318,39 @@ int rb_get_args(int argc, VALUE *argv, const char *format, ...) {
return 0;
}
#if RAPI_MAJOR >= 2
#include <ruby/thread.h>
typedef struct gvl_guard_args {
Exception *exc;
void *(*func)(void *);
void *args;
} gvl_guard_args;
static void *gvl_guard(void *args) {
gvl_guard_args *gvl_args = (gvl_guard_args*)args;
try{
return gvl_args->func(gvl_args->args);
} catch (const Exception &e) {
gvl_args->exc = new Exception(e.type, e.msg);
}
return 0;
}
void *drop_gvl_guard(void *(*func)(void *), void *args,
rb_unblock_function_t *ubf, void *data2) {
gvl_guard_args gvl_args = {0, func, args};
void *ret = rb_thread_call_without_gvl(&gvl_guard, &gvl_args, ubf, data2);
Exception *&exc = gvl_args.exc;
if (exc){
Exception e(exc->type, exc->msg);
delete exc;
throw e;
}
return ret;
}
#endif

View file

@ -78,6 +78,11 @@ struct Exception;
VALUE excToRbClass(const Exception &exc);
void raiseRbExc(Exception exc);
#if RAPI_MAJOR >= 2
void *drop_gvl_guard(void *(*func)(void *), void *args,
rb_unblock_function_t *ubf, void *data2);
#endif
#if RAPI_FULL > 187
#define DECL_TYPE(Klass) extern rb_data_type_t Klass##Type
@ -309,15 +314,6 @@ static inline void _rb_define_module_function(VALUE module, const char *name,
rb_define_module_function(module, name, RUBY_METHOD_FUNC(func), -1);
}
#define GUARD_EXC(exp) \
{ \
try { \
exp \
} catch (const Exception &exc) { \
raiseRbExc(exc); \
} \
}
#define GFX_GUARD_EXC(exp) \
{ \
GFX_LOCK; \

View file

@ -26,10 +26,6 @@
#include "binding-types.h"
#include "exception.h"
#if RAPI_MAJOR >= 2
#include <ruby/thread.h>
#endif
RB_METHOD(graphicsDelta) {
RB_UNUSED_PARAM;
GFX_LOCK;
@ -38,14 +34,12 @@ RB_METHOD(graphicsDelta) {
return ret;
}
RB_METHOD(graphicsUpdate)
RB_METHOD_GUARD(graphicsUpdate)
{
RB_UNUSED_PARAM;
#if RAPI_MAJOR >= 2
rb_thread_call_without_gvl([](void*) -> void* {
GFX_LOCK;
shState->graphics().update();
GFX_UNLOCK;
drop_gvl_guard([](void*) -> void* {
GFX_GUARD_EXC( shState->graphics().update(); );
return 0;
}, 0, 0, 0);
#else
@ -53,6 +47,7 @@ RB_METHOD(graphicsUpdate)
#endif
return Qnil;
}
RB_METHOD_GUARD_END
RB_METHOD(graphicsAverageFrameRate)
{
@ -63,16 +58,28 @@ RB_METHOD(graphicsAverageFrameRate)
return ret;
}
RB_METHOD(graphicsFreeze)
RB_METHOD_GUARD(graphicsFreeze)
{
RB_UNUSED_PARAM;
GFX_LOCK;
#if RAPI_MAJOR >= 2
drop_gvl_guard([](void*) -> void* {
GFX_GUARD_EXC( shState->graphics().freeze(); );
return 0;
}, 0, 0, 0);
#else
shState->graphics().freeze();
GFX_UNLOCK;
#endif
return Qnil;
}
RB_METHOD_GUARD_END
typedef struct {
int duration;
const char *filename;
int vague;
} TransitionArgs;
RB_METHOD_GUARD(graphicsTransition)
{
@ -84,7 +91,20 @@ RB_METHOD_GUARD(graphicsTransition)
rb_get_args(argc, argv, "|izi", &duration, &filename, &vague RB_ARG_END);
TransitionArgs args = {duration, filename, vague};
#if RAPI_MAJOR >= 2
drop_gvl_guard([](void *args) -> void* {
TransitionArgs &a = *((TransitionArgs*)args);
GFX_GUARD_EXC( shState->graphics().transition(a.duration,
a.filename,
a.vague
); );
return 0;
}, &args, 0, 0);
#else
GFX_GUARD_EXC( shState->graphics().transition(duration, filename, vague); )
#endif
return Qnil;
}
@ -180,17 +200,15 @@ RB_METHOD(graphicsDisplayHeight)
return rb_fix_new(shState->graphics().displayHeight());
}
RB_METHOD(graphicsWait)
RB_METHOD_GUARD(graphicsWait)
{
RB_UNUSED_PARAM;
int duration;
rb_get_args(argc, argv, "i", &duration RB_ARG_END);
#if RAPI_MAJOR >= 2
rb_thread_call_without_gvl([](void* d) -> void* {
GFX_LOCK;
shState->graphics().wait(*(int*)d);
GFX_UNLOCK;
drop_gvl_guard([](void* d) -> void* {
GFX_GUARD_EXC( shState->graphics().wait(*(int*)d); );
return 0;
}, (int*)&duration, 0, 0);
#else
@ -198,34 +216,47 @@ RB_METHOD(graphicsWait)
#endif
return Qnil;
}
RB_METHOD_GUARD_END
RB_METHOD(graphicsFadeout)
RB_METHOD_GUARD(graphicsFadeout)
{
RB_UNUSED_PARAM;
int duration;
rb_get_args(argc, argv, "i", &duration RB_ARG_END);
GFX_LOCK;
#if RAPI_MAJOR >= 2
drop_gvl_guard([](void* d) -> void* {
GFX_GUARD_EXC( shState->graphics().fadeout(*(int*)d); );
return 0;
}, (int*)&duration, 0, 0);
#else
shState->graphics().fadeout(duration);
GFX_UNLOCK;
#endif
return Qnil;
}
RB_METHOD_GUARD_END
RB_METHOD(graphicsFadein)
RB_METHOD_GUARD(graphicsFadein)
{
RB_UNUSED_PARAM;
int duration;
rb_get_args(argc, argv, "i", &duration RB_ARG_END);
GFX_LOCK;
#if RAPI_MAJOR >= 2
drop_gvl_guard([](void* d) -> void* {
GFX_GUARD_EXC( shState->graphics().fadein(*(int*)d); );
return 0;
}, (int*)&duration, 0, 0);
#else
shState->graphics().fadein(duration);
GFX_UNLOCK;
#endif
return Qnil;
}
RB_METHOD_GUARD_END
void bitmapInitProps(Bitmap *b, VALUE self);
@ -274,16 +305,15 @@ RB_METHOD(graphicsResizeWindow)
return Qnil;
}
RB_METHOD(graphicsReset)
RB_METHOD_GUARD(graphicsReset)
{
RB_UNUSED_PARAM;
GFX_LOCK;
shState->graphics().reset();
GFX_UNLOCK;
GFX_GUARD_EXC( shState->graphics().reset(); );
return Qnil;
}
RB_METHOD_GUARD_END
RB_METHOD(graphicsCenter)
{
@ -301,17 +331,17 @@ typedef struct {
void *playMovieInternal(void *args) {
PlayMovieArgs *a = (PlayMovieArgs*)args;
GFX_GUARD_EXC(
shState->graphics().playMovie(a->filename, a->volume, a->skippable);
// Signals for shutdown or reset only make playMovie quit early,
// so check again
shState->graphics().update();
);
GFX_GUARD_EXC( shState->graphics().playMovie(a->filename, a->volume, a->skippable); );
// Signals for shutdown or reset only make playMovie quit early,
// so check again
shState->checkShutdown();
shState->checkReset();
return 0;
}
RB_METHOD(graphicsPlayMovie)
RB_METHOD_GUARD(graphicsPlayMovie)
{
RB_UNUSED_PARAM;
@ -329,20 +359,21 @@ RB_METHOD(graphicsPlayMovie)
args.volume = (volumeArg == Qnil) ? 100 : NUM2INT(volumeArg);;
args.skippable = skip;
#if RAPI_MAJOR >= 2
rb_thread_call_without_gvl(playMovieInternal, &args, 0, 0);
drop_gvl_guard(playMovieInternal, &args, 0, 0);
#else
playMovieInternal(&args);
#endif
return Qnil;
}
RB_METHOD_GUARD_END
void graphicsScreenshotInternal(const char *filename)
{
GFX_GUARD_EXC(shState->graphics().screenshot(filename););
}
RB_METHOD(graphicsScreenshot)
RB_METHOD_GUARD(graphicsScreenshot)
{
RB_UNUSED_PARAM;
@ -351,7 +382,7 @@ RB_METHOD(graphicsScreenshot)
SafeStringValue(filename);
#if RAPI_MAJOR >= 2
rb_thread_call_without_gvl([](void* fn) -> void* {
drop_gvl_guard([](void* fn) -> void* {
graphicsScreenshotInternal((const char*)fn);
return 0;
}, (void*)RSTRING_PTR(filename), 0, 0);
@ -360,6 +391,7 @@ RB_METHOD(graphicsScreenshot)
#endif
return Qnil;
}
RB_METHOD_GUARD_END
DEF_GRA_PROP_I(FrameRate)
DEF_GRA_PROP_I(FrameCount)

View file

@ -10,10 +10,6 @@
#include "util/json5pp.hpp"
#include "binding-util.h"
#if RAPI_MAJOR >= 2
#include <ruby/thread.h>
#endif
#include "net/net.h"
VALUE stringMap2hash(mkxp_net::StringMap &map) {
@ -78,10 +74,8 @@ VALUE formResponse(mkxp_net::HTTPResponse &res) {
void* httpGetInternal(void *req) {
VALUE ret;
GUARD_EXC(
mkxp_net::HTTPResponse res = ((mkxp_net::HTTPRequest*)req)->get();
ret = formResponse(res);
);
mkxp_net::HTTPResponse res = ((mkxp_net::HTTPRequest*)req)->get();
ret = formResponse(res);
return (void*)ret;
}
@ -102,7 +96,7 @@ RB_METHOD_GUARD(httpGet) {
req.headers().insert(headers.begin(), headers.end());
}
#if RAPI_MAJOR >= 2
return (VALUE)rb_thread_call_without_gvl(httpGetInternal, &req, 0, 0);
return (VALUE)drop_gvl_guard(httpGetInternal, &req, 0, 0);
#else
return (VALUE)httpGetInternal(&req);
#endif
@ -122,10 +116,8 @@ void* httpPostInternal(void *args) {
mkxp_net::HTTPRequest *req = ((httpPostInternalArgs*)args)->req;
mkxp_net::StringMap *postData = ((httpPostInternalArgs*)args)->postData;
GUARD_EXC(
mkxp_net::HTTPResponse res = req->post(*postData);
ret = formResponse(res);
);
mkxp_net::HTTPResponse res = req->post(*postData);
ret = formResponse(res);
return (void*)ret;
}
@ -149,7 +141,7 @@ RB_METHOD_GUARD(httpPost) {
mkxp_net::StringMap postData = hash2StringMap(postDataHash);
httpPostInternalArgs args {&req, &postData};
#if RAPI_MAJOR >= 2
return (VALUE)rb_thread_call_without_gvl(httpPostInternal, &args, 0, 0);
return (VALUE)drop_gvl_guard(httpPostInternal, &args, 0, 0);
#else
return httpPostInternal(&args);
#endif
@ -170,16 +162,14 @@ void* httpPostBodyInternal(void *args) {
const char *reqbody = ((httpPostBodyInternalArgs*)args)->body;
const char *reqctype = ((httpPostBodyInternalArgs*)args)->ctype;
GUARD_EXC(
mkxp_net::HTTPResponse res = req->post(reqbody, reqctype);
ret = formResponse(res);
);
mkxp_net::HTTPResponse res = req->post(reqbody, reqctype);
ret = formResponse(res);
return (void*)ret;
}
#endif
RB_METHOD(httpPostBody) {
RB_METHOD_GUARD(httpPostBody) {
RB_UNUSED_PARAM;
VALUE path, body, ctype, rheaders;
@ -197,11 +187,12 @@ RB_METHOD(httpPostBody) {
httpPostBodyInternalArgs args {&req, RSTRING_PTR(body), RSTRING_PTR(ctype)};
#if RAPI_MAJOR >= 2
return (VALUE)rb_thread_call_without_gvl(httpPostBodyInternal, &args, 0, 0);
return (VALUE)drop_gvl_guard(httpPostBodyInternal, &args, 0, 0);
#else
return httpPostBodyInternal(&args);
#endif
}
RB_METHOD_GUARD_END
VALUE json2rb(json5pp::value const &v) {
if (v.is_null())

View file

@ -37,13 +37,14 @@ RB_METHOD(inputDelta) {
return rb_float_new(shState->input().getDelta());
}
RB_METHOD(inputUpdate) {
RB_METHOD_GUARD(inputUpdate) {
RB_UNUSED_PARAM;
shState->input().update();
return Qnil;
}
RB_METHOD_GUARD_END
static int getButtonArg(VALUE *argv) {
int num;

View file

@ -1609,6 +1609,13 @@ void Graphics::reset() {
setFrameRate(DEF_FRAMERATE);
setBrightness(255);
// Always update at least once to clear the screen
if (p->threadData->rqResetFinish)
update();
else
repaintWait(p->threadData->rqResetFinish, false);
p->threadData->rqReset.clear();
}
void Graphics::center() {

View file

@ -800,7 +800,9 @@ void EventThread::showMessageBox(const char *body, int flags)
SDL_PushEvent(&event);
/* Keep repainting screen while box is open */
shState->graphics().repaintWait(msgBoxDone);
try{
shState->graphics().repaintWait(msgBoxDone);
}catch(...){}
/* Prevent endless loops */
resetInputStates();
}

View file

@ -286,7 +286,9 @@ struct RGSSThreadData
scale(scalingFactor),
config(newconf),
glContext(ctx)
{}
{
rqResetFinish.set();
}
};
#endif // EVENTTHREAD_H

View file

@ -31,6 +31,7 @@ struct Exception
enum Type
{
RGSSError,
Reset,
NoFileError,
IOError,