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; RB_UNUSED_PARAM;
printP(argc, argv, "to_s", ""); printP(argc, argv, "to_s", "");
shState->checkShutdown();
shState->checkReset();
return Qnil; return Qnil;
} }
RB_METHOD_GUARD_END
RB_METHOD(mriP) { RB_METHOD_GUARD(mriP) {
RB_UNUSED_PARAM; RB_UNUSED_PARAM;
printP(argc, argv, "inspect", "\n"); printP(argc, argv, "inspect", "\n");
shState->checkShutdown();
shState->checkReset();
return Qnil; return Qnil;
} }
RB_METHOD_GUARD_END
RB_METHOD(mkxpDelta) { RB_METHOD(mkxpDelta) {
RB_UNUSED_PARAM; RB_UNUSED_PARAM;
@ -757,15 +765,21 @@ static VALUE rgssMainRescue(VALUE arg, VALUE exc) {
return Qnil; return Qnil;
} }
static void processReset() { static bool processReset(bool rubyExc) {
shState->graphics().reset(); const char *str = "Audio.__reset__; Graphics.__reset__;";
shState->audio().reset();
if (rubyExc) {
shState->rtData().rqReset.clear(); rb_eval_string(str);
shState->graphics().repaintWait(shState->rtData().rqResetFinish, false); } else {
int state;
rb_eval_string_protect(str, &state);
return state;
}
return 0;
} }
RB_METHOD(mriRgssMain) { RB_METHOD_GUARD(mriRgssMain) {
RB_UNUSED_PARAM; RB_UNUSED_PARAM;
while (true) { while (true) {
@ -783,15 +797,16 @@ RB_METHOD(mriRgssMain) {
break; break;
if (rb_obj_class(exc) == getRbData()->exc[Reset]) if (rb_obj_class(exc) == getRbData()->exc[Reset])
processReset(); processReset(true);
else else
rb_exc_raise(exc); rb_exc_raise(exc);
} }
return Qnil; return Qnil;
} }
RB_METHOD_GUARD_END
RB_METHOD(mriRgssStop) { RB_METHOD_GUARD(mriRgssStop) {
RB_UNUSED_PARAM; RB_UNUSED_PARAM;
while (true) while (true)
@ -799,6 +814,7 @@ RB_METHOD(mriRgssStop) {
return Qnil; return Qnil;
} }
RB_METHOD_GUARD_END
RB_METHOD(_kernelCaller) { RB_METHOD(_kernelCaller) {
RB_UNUSED_PARAM; RB_UNUSED_PARAM;
@ -1038,7 +1054,8 @@ static void runRMXPScripts(BacktraceData &btData) {
if (rb_obj_class(exc) != getRbData()->exc[Reset]) if (rb_obj_class(exc) != getRbData()->exc[Reset])
break; break;
processReset(); if (processReset(false))
break;
} }
} }
@ -1247,6 +1264,6 @@ static void mriBindingExecute() {
shState->rtData().rqTermAck.set(); 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 */ /* Indexed with Exception::Type */
const RbException excToRbExc[] = { const RbException excToRbExc[] = {
RGSS, /* RGSSError */ RGSS, /* RGSSError */
ErrnoENOENT, /* NoFileError */ Reset, /* Reset/RGSSReset */
ErrnoENOENT, /* NoFileError */
IOError, IOError,
TypeError, ArgumentError, SystemExit, RuntimeError, TypeError, ArgumentError, SystemExit, RuntimeError,
@ -317,3 +318,39 @@ int rb_get_args(int argc, VALUE *argv, const char *format, ...) {
return 0; 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); VALUE excToRbClass(const Exception &exc);
void raiseRbExc(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 #if RAPI_FULL > 187
#define DECL_TYPE(Klass) extern rb_data_type_t Klass##Type #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); 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) \ #define GFX_GUARD_EXC(exp) \
{ \ { \
GFX_LOCK; \ GFX_LOCK; \

View file

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

View file

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

View file

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

View file

@ -1609,6 +1609,13 @@ void Graphics::reset() {
setFrameRate(DEF_FRAMERATE); setFrameRate(DEF_FRAMERATE);
setBrightness(255); 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() { void Graphics::center() {

View file

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

View file

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

View file

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