/* ** binding-mruby.cpp ** ** This file is part of mkxp. ** ** Copyright (C) 2013 Jonas Kulla ** ** mkxp is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 2 of the License, or ** (at your option) any later version. ** ** mkxp is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with mkxp. If not, see . */ #include "binding.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sharedstate.h" #include "texpool.h" #include "eventthread.h" #include "filesystem.h" #include "exception.h" #include "binding-util.h" #include "binding-types.h" #include "mrb-ext/marshal.h" #include "mrb-ext/file-helper.h" #ifdef __EMSCRIPTEN__ #include #include #include #endif static void mrbBindingExecute(); static void mrbBindingTerminate(); static void mrbBindingReset(); ScriptBinding scriptBindingImpl = { mrbBindingExecute, mrbBindingTerminate, mrbBindingReset }; ScriptBinding *scriptBinding = &scriptBindingImpl; void fileBindingInit(mrb_state *); void timeBindingInit(mrb_state *); void kernelBindingInit(mrb_state *); void tableBindingInit(mrb_state *); void etcBindingInit(mrb_state *); void fontBindingInit(mrb_state *); void bitmapBindingInit(mrb_state *); void spriteBindingInit(mrb_state *); void planeBindingInit(mrb_state *); void viewportBindingInit(mrb_state *); void windowBindingInit(mrb_state *); void tilemapBindingInit(mrb_state *); void inputBindingInit(mrb_state *); void audioBindingInit(mrb_state *); void graphicsBindingInit(mrb_state *); /* From module_rpg.c */ extern const uint8_t rpg_mrb[]; static void __attribute__ ((optnone)) mrbBindingInit(mrb_state *mrb) { int arena = mrb_gc_arena_save(mrb); /* Init standard classes */ fileBindingInit(mrb); timeBindingInit(mrb); kernelBindingInit(mrb); /* Init RGSS classes */ tableBindingInit(mrb); etcBindingInit(mrb); fontBindingInit(mrb); bitmapBindingInit(mrb); spriteBindingInit(mrb); planeBindingInit(mrb); viewportBindingInit(mrb); windowBindingInit(mrb); tilemapBindingInit(mrb); /* Init RGSS modules */ inputBindingInit(mrb); audioBindingInit(mrb); graphicsBindingInit(mrb); /* Load RPG module */ mrb_load_irep(mrb, rpg_mrb); /* Load global constants */ mrb_define_global_const(mrb, "MKXP", mrb_true_value()); mrb_value debug = mrb_bool_value(shState->config().editor.debug); if (rgssVer == 1) mrb_define_global_const(mrb, "DEBUG", debug); else if (rgssVer >= 2) mrb_define_global_const(mrb, "TEST", debug); mrb_define_global_const(mrb, "BTEST", mrb_bool_value(shState->config().editor.battleTest)); mrb_gc_arena_restore(mrb, arena); } static mrb_value mkxpTimeOp(mrb_state *mrb, mrb_value) { mrb_int iterations = 1; const char *opName = ""; mrb_value block; mrb_get_args(mrb, "|iz&", &iterations, &opName, &block); Uint64 start = SDL_GetPerformanceCounter(); for (int i = 0; i < iterations; ++i) mrb_yield(mrb, block, mrb_nil_value()); Uint64 diff = SDL_GetPerformanceCounter() - start; double avg = (double) diff / iterations; double sec = avg / SDL_GetPerformanceFrequency(); float ms = sec * 1000; printf("<%s> [%f ms]\n", opName, ms); fflush(stdout); return mrb_float_value(mrb, ms); } static const char * mrbValueString(mrb_value value) { return mrb_string_p(value) ? RSTRING_PTR(value) : 0; } static void showExcMessageBox(mrb_state *mrb, mrb_value exc) { /* Display actual exception in a message box */ mrb_value mesg = mrb_funcall(mrb, exc, "message", 0); mrb_value line = mrb_attr_get(mrb, exc, mrb_intern_lit(mrb, "line")); mrb_value file = mrb_attr_get(mrb, exc, mrb_intern_lit(mrb, "file")); const char *excClass = mrb_class_name(mrb, mrb_class(mrb, exc)); char msgBoxText[512]; snprintf(msgBoxText, sizeof(msgBoxText), "Script '%s' line %d: %s occured.\n\n%s", mrbValueString(file), mrb_fixnum(line), excClass, mrbValueString(mesg)); printf("Alert - %s\n", msgBoxText); } static void checkException(mrb_state *mrb) { if (!mrb->exc) return; mrb_value exc = mrb_obj_value(mrb->exc); MrbData &mrbData = *getMrbData(mrb); /* Check if an actual exception occured, or just a shutdown was called */ if (mrb_obj_class(mrb, exc) != mrbData.exc[Shutdown]) showExcMessageBox(mrb, exc); } static void showError(const std::string &msg) { shState->eThread().showMessageBox(msg.c_str()); } static void runCustomScript(mrb_state *mrb, mrbc_context *ctx, const char *filename) { /* Execute custom script */ FILE *f = fopen(filename, "rb"); if (!f) { static char buffer[256]; snprintf(buffer, sizeof(buffer), "Unable to open script '%s'", filename); showError(buffer); return; } ctx->filename = strdup(filename); ctx->lineno = 1; /* Run code */ mrb_load_file_cxt(mrb, f, ctx); free(ctx->filename); fclose(f); } static void runMrbFile(mrb_state *mrb, const char *filename) { /* Execute compiled script */ FILE *f = fopen(filename, "rb"); if (!f) { static char buffer[256]; snprintf(buffer, sizeof(buffer), "Unable to open compiled script '%s'", filename); showError(buffer); return; } mrb_irep *irep = mrb_read_irep_file(mrb, f); if (!irep) { static char buffer[256]; snprintf(buffer, sizeof(buffer), "Unable to read compiled script '%s'", filename); showError(buffer); return; } RProc *proc = mrb_proc_new(mrb, irep); mrb_run(mrb, proc, mrb_top_self(mrb)); fclose(f); } static MrbData * mrbData; static mrb_state * static_scriptmrb; static void __attribute__ ((optnone)) main_update_loop() { mrb_state * mrb = (mrb_state *) static_cast(shState->bindingData()); mrb_load_nstring_cxt(mrb, "main_update_loop", 16, NULL); #ifdef __EMSCRIPTEN__ if (mrb->exc) { printf("Execution Errored\n"); mrb_value s = mrb_funcall(mrb, mrb_obj_value(mrb->exc), "inspect", 0); if (mrb_string_p(s)) { printf("%s", mrb_str_to_cstr(mrb, s)); printf("\n"); } emscripten_cancel_main_loop(); //mrb_close(static_scriptmrb); //shState->texPool().disable(); //mrb_close(static_mrb); } #endif } static bool initialized = false; static emscripten_fetch_t *fetchScripts; static std::atomic assetQ; static void runRMXPScripts(mrb_state *mrb, mrbc_context *ctx) { const std::string &scriptPack = shState->rtData().config.game.scripts; if (scriptPack.empty()) { showError("No game scripts specified (missing Game.ini?)"); return; } if (!shState->fileSystem().exists(scriptPack.c_str())) { showError(std::string("Unable to open '") + scriptPack + "'"); return; } /* We use a secondary util state to unmarshal the scripts */ mrb_state *scriptMrb = mrb_open(); mrb_value scriptArray = mrb_nil_value(); std::string readError; try { const char * contents = fetchScripts->data; mrb_value rawdata = mrb_str_new_static(mrb, contents, fetchScripts->numBytes); scriptArray = mrb_marshal_load(mrb, rawdata); } catch (const Exception &e) { readError = std::string(": ") + e.msg; } emscripten_fetch_close(fetchScripts); fetchScripts = NULL; if (!mrb_array_p(scriptArray)) { showError(std::string("Failed to read script data") + readError); mrb_close(scriptMrb); return; } int scriptCount = RARRAY_LEN(scriptArray); std::string decodeBuffer; decodeBuffer.resize(0x1000); for (int i = 0; i < scriptCount; ++i) { mrb_value script = mrb_ary_entry(scriptArray, i); mrb_value scriptChksum = mrb_ary_entry(script, 0); mrb_value scriptName = mrb_ary_entry(script, 1); mrb_value scriptString = mrb_ary_entry(script, 2); (void) scriptChksum; int result = Z_OK; unsigned long bufferLen; while (true) { unsigned char *bufferPtr = reinterpret_cast(const_cast(decodeBuffer.c_str())); unsigned char *sourcePtr = reinterpret_cast(RSTRING_PTR(scriptString)); bufferLen = decodeBuffer.length(); result = uncompress(bufferPtr, &bufferLen, sourcePtr, RSTRING_LEN(scriptString)); bufferPtr[bufferLen] = '\0'; if (result != Z_BUF_ERROR) break; decodeBuffer.resize(decodeBuffer.size()*2); } if (result != Z_OK) { static char buffer[256]; snprintf(buffer, sizeof(buffer), "Error decoding script %d: '%s'", i, RSTRING_PTR(scriptName)); showError(buffer); break; } ctx->filename = RSTRING_PTR(scriptName); ctx->lineno = 1; int ai = mrb_gc_arena_save(mrb); /* Execute code */ mrb_load_nstring_cxt(mrb, decodeBuffer.c_str(), bufferLen, ctx); mrb_gc_arena_restore(mrb, ai); if (mrb->exc) break; } static_scriptmrb = scriptMrb; #ifdef __EMSCRIPTEN__ return; #else while (true) { main_update_loop(); SDL_Delay(3); if (mrb->exc) break; } mrb_close(scriptMrb); #endif } static void __attribute__ ((optnone)) mrbBindingMain() { mrb_state *mrb = mrb_open(); shState->setBindingData(mrb); mrbData = new MrbData(mrb); mrb->ud = mrbData; mrb_define_module_function(mrb, mrb->kernel_module, "time_op", mkxpTimeOp, MRB_ARGS_OPT(2) | MRB_ARGS_BLOCK()); mrbBindingInit(mrb); const Config &conf = shState->rtData().config; const std::string &customScript = conf.customScript; (void) runMrbFile; // FIXME mrbFile support on ice for now mrbc_context *ctx = mrbc_context_new(mrb); ctx->capture_errors = 1; if (!customScript.empty()) { runCustomScript(mrb, ctx, customScript.c_str()); } else runRMXPScripts(mrb, ctx); #ifndef __EMSCRIPTEN__ checkException(mrb); shState->rtData().rqTermAck.set(); shState->texPool().disable(); mrbc_context_free(mrb, ctx); mrb_close(mrb); #endif } void downloadSucceeded(emscripten_fetch_t *fetch) { printf("Finished downloading %llu bytes from URL %s.\n", fetch->numBytes, fetch->url); fetchScripts = fetch; assetQ--; } void downloadFailed(emscripten_fetch_t *fetch) { printf("Downloading %s failed, HTTP failure status code: %d.\n", fetch->url, fetch->status); fetchScripts = fetch; assetQ--; emscripten_fetch_close(fetch); } void __attribute__ ((optnone)) supermainloop() { if (assetQ > 0) return; if (initialized) { main_update_loop(); } else { mrbBindingMain(); initialized = true; } } static void __attribute__ ((optnone)) mrbBindingExecute() { assetQ = 1; emscripten_fetch_attr_t attr; emscripten_fetch_attr_init(&attr); strcpy(attr.requestMethod, "GET"); attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; attr.onsuccess = downloadSucceeded; attr.onerror = downloadFailed; emscripten_fetch(&attr, "async/Data/Scripts.rxdata"); emscripten_set_main_loop(supermainloop, 0, 0); } void addAssetDownloadMap(char * path) { } static void mrbBindingTerminate() { mrb_state *mrb = static_cast(shState->bindingData()); MrbData *data = static_cast(mrb->ud); mrb_raise(mrb, data->exc[Shutdown], ""); } static void mrbBindingReset() { // No idea how to do this with mruby yet }