From 9dcae98180f422893c1937a053bf40efd7329b7f Mon Sep 17 00:00:00 2001 From: pomyk Date: Thu, 15 Feb 2007 18:54:46 +0000 Subject: [PATCH] Separate thread for Ruby Originally committed to SVN as r939. --- aegisub/auto4_ruby.cpp | 212 +++++++++++++++++++-------------- aegisub/auto4_ruby.h | 21 ++-- aegisub/auto4_ruby_assfile.cpp | 29 +---- 3 files changed, 137 insertions(+), 125 deletions(-) diff --git a/aegisub/auto4_ruby.cpp b/aegisub/auto4_ruby.cpp index 358dd7dac..d1e477cc0 100644 --- a/aegisub/auto4_ruby.cpp +++ b/aegisub/auto4_ruby.cpp @@ -68,9 +68,12 @@ namespace Automation4 { RubyObjects *RubyObjects::inst = NULL; RubyScript * RubyScript::inst = NULL; // current Ruby Script RubyProgressSink* RubyProgressSink::inst = NULL; - VALUE RubyScript::RubyAegisub = Qfalse; - wxString RubyScript::backtrace = _T(""); - wxString RubyScript::error = _T(""); + RubyThread* ruby_thread = NULL; + wxSemaphore* ruby_thread_sem = NULL; + wxSemaphore* ruby_script_sem = NULL; + VALUE RubyAegisub = Qfalse; + wxString backtrace = _T(""); + wxString error = _T(""); // RubyScript @@ -87,6 +90,19 @@ namespace Automation4 { } } + void RubyThread::CallFunction(RubyCallArguments* arg, VALUE *res) + { + args = arg; + result = res; + action = CALL_FUNCTION; + } + + void RubyThread::LoadFile(const char *f) + { + file = f; + action = LOAD_FILE; + } + RubyScript::~RubyScript() { } @@ -94,53 +110,25 @@ namespace Automation4 { void RubyScript::Create() { Destroy(); + RubyScript::inst = this; try { -#if defined(NT) - int argc = 0; - char **argv = 0; - NtInitialize(&argc, &argv); -#endif - char **opt = new char*[4]; - opt[0] = "-d"; - ruby_init(); - //ruby_options(1, opt); - ruby_init_loadpath(); - RubyScript::inst = this; - error = _T(""); - backtrace = _T(""); - if(!RubyAegisub) { - RubyAegisub = rb_define_module("Aegisub"); - rb_define_module_function(RubyAegisub, "register_macro",reinterpret_cast(&RubyFeatureMacro::RubyRegister), 4); - rb_define_module_function(RubyAegisub, "register_filter",reinterpret_cast(&RubyFeatureFilter::RubyRegister), 5); - rb_define_module_function(RubyAegisub, "text_extents",reinterpret_cast(&RubyTextExtents), 2); - rb_define_module_function(RubyAegisub, "frame_to_time",reinterpret_cast(&RubyFrameToTime), 1); - rb_define_module_function(RubyAegisub, "time_to_frame",reinterpret_cast(&RubyTimeToFrame), 1); - rb_define_module_function(RubyAegisub, "key_frames",reinterpret_cast(&RubyKeyFrames), 0); - rb_define_module_function(rb_eException, "set_backtrace",reinterpret_cast(&backtrace_hook), 1); - rb_define_module_function(RubyScript::RubyAegisub, "progress_set",reinterpret_cast(&RubyProgressSink::RubySetProgress), 1); - rb_define_module_function(RubyScript::RubyAegisub, "progress_task",reinterpret_cast(&RubyProgressSink::RubySetTask), 1); - rb_define_module_function(RubyScript::RubyAegisub, "progress_title",reinterpret_cast(&RubyProgressSink::RubySetTitle), 1); - rb_define_module_function(RubyScript::RubyAegisub, "debug_out",reinterpret_cast(&RubyProgressSink::RubyDebugOut), -1); - rb_define_module_function(RubyScript::RubyAegisub, "get_cancelled",reinterpret_cast(&RubyProgressSink::RubyGetCancelled), 0); - rb_define_module_function(RubyScript::RubyAegisub, "display_dialog",reinterpret_cast(&RubyProgressSink::RubyDisplayDialog), 2); - } - VALUE paths = rb_gv_get("$:"); - for(int i = 0; i < include_path.GetCount(); i++) + if(ruby_thread == NULL) { - rb_ary_push(paths, rb_str_new2(include_path[i].mb_str(wxConvISO8859_1))); + ruby_thread_sem = new wxSemaphore(0, 1); + ruby_script_sem = new wxSemaphore(0, 1); + ruby_thread = new RubyThread(include_path); + ruby_script_sem->Wait(); } int status = 0; wxCharBuffer buf = GetFilename().mb_str(wxConvISO8859_1); const char *t = buf.data(); - - rb_protect(rbLoadWrapper, rb_str_new2(t), &status); - if(status > 0) // something bad happened (probably parsing error) - { - wxString *err = new wxString(_T("An error occurred initialising the script file \"") + GetFilename() + _T("\":\n\n") + GetError()); - throw err->c_str(); - } + ruby_thread->LoadFile(t); + ruby_thread_sem->Post(); + ruby_script_sem->Wait(); + if(ruby_thread->GetStatus()) + RubyScript::RubyError(); VALUE global_var = rb_gv_get("$script_name"); if(TYPE(global_var) == T_STRING) @@ -165,9 +153,6 @@ namespace Automation4 { void RubyScript::Destroy() { - if(loaded) { -// ruby_finalize(); // broken in 1.9 ?_? - } // remove features for (int i = 0; i < (int)features.size(); i++) { @@ -265,6 +250,8 @@ namespace Automation4 { void RubyScript::RubyError() { wxMessageBox(RubyScript::inst->GetError(), _T("Error"),wxICON_ERROR | wxOK); + error = _T(""); + backtrace = _T(""); } @@ -335,13 +322,12 @@ namespace Automation4 { argv[2] = INT2FIX(active); RubyCallArguments arg(rb_mKernel, rb_to_id(validation_fun), 3, argv); VALUE result; - RubyThreadedCall call(&arg, &result); - wxThread::ExitCode code = call.Wait(); - if(code) - { - wxMessageBox(RubyScript::GetError(), _T("Error running validation function"),wxICON_ERROR | wxOK); - - } else if(result != Qnil && result != Qfalse) { + ruby_thread->CallFunction(&arg, &result); + ruby_thread_sem->Post(); + ruby_script_sem->Wait(); + if(ruby_thread->GetStatus()) + RubyScript::RubyError(); + if(result != Qnil && result != Qfalse) { return true; } @@ -350,7 +336,6 @@ namespace Automation4 { void RubyFeatureMacro::Process(AssFile *subs, const std::vector &selected, int active, wxWindow * const progress_parent) { - rb_gc_disable(); delete RubyProgressSink::inst; RubyProgressSink::inst = new RubyProgressSink(progress_parent, false); RubyProgressSink::inst->SetTitle(GetName()); @@ -363,13 +348,17 @@ namespace Automation4 { argv[2] = INT2FIX(active); RubyCallArguments arg(rb_mKernel, rb_to_id(macro_fun), 3, argv); VALUE result; - RubyThreadedCall call(&arg, &result); + ruby_thread->CallFunction(&arg, &result); + ruby_thread_sem->Post(); RubyProgressSink::inst->ShowModal(); - wxThread::ExitCode code = call.Wait(); + ruby_script_sem->Wait(); delete RubyProgressSink::inst; RubyProgressSink::inst = NULL; - if(TYPE(result) == T_ARRAY) + if(ruby_thread->GetStatus()) + RubyScript::RubyError(); + else if(TYPE(result) == T_ARRAY) { + rb_gc_disable(); bool end = false; for(int i = 0; i < RARRAY(result)->len && !end; ++i) { @@ -397,16 +386,51 @@ namespace Automation4 { break; } } + rb_gc_enable(); } delete subsobj; - rb_gc_enable(); - rb_gc_start(); -} + } + // RubyThread + void RubyThread::InitRuby() + { + #if defined(NT) + int argc = 0; + char **argv = 0; + NtInitialize(&argc, &argv); + #endif + ruby_init(); + ruby_init_loadpath(); + error = _T(""); + backtrace = _T(""); + if(!RubyAegisub) { + RubyAegisub = rb_define_module("Aegisub"); + rb_define_module_function(RubyAegisub, "register_macro",reinterpret_cast(&RubyFeatureMacro::RubyRegister), 4); + rb_define_module_function(RubyAegisub, "register_filter",reinterpret_cast(&RubyFeatureFilter::RubyRegister), 5); + rb_define_module_function(RubyAegisub, "text_extents",reinterpret_cast(&RubyScript::RubyTextExtents), 2); + rb_define_module_function(RubyAegisub, "frame_to_time",reinterpret_cast(&RubyScript::RubyFrameToTime), 1); + rb_define_module_function(RubyAegisub, "time_to_frame",reinterpret_cast(&RubyScript::RubyTimeToFrame), 1); + rb_define_module_function(RubyAegisub, "key_frames",reinterpret_cast(&RubyScript::RubyKeyFrames), 0); + rb_define_module_function(rb_eException, "set_backtrace",reinterpret_cast(&RubyScript::backtrace_hook), 1); + rb_define_module_function(RubyAegisub, "progress_set",reinterpret_cast(&RubyProgressSink::RubySetProgress), 1); + rb_define_module_function(RubyAegisub, "progress_task",reinterpret_cast(&RubyProgressSink::RubySetTask), 1); + rb_define_module_function(RubyAegisub, "progress_title",reinterpret_cast(&RubyProgressSink::RubySetTitle), 1); + rb_define_module_function(RubyAegisub, "debug_out",reinterpret_cast(&RubyProgressSink::RubyDebugOut), -1); + rb_define_module_function(RubyAegisub, "get_cancelled",reinterpret_cast(&RubyProgressSink::RubyGetCancelled), 0); + rb_define_module_function(RubyAegisub, "display_dialog",reinterpret_cast(&RubyProgressSink::RubyDisplayDialog), 2); + } + VALUE paths = rb_gv_get("$:"); + for(int i = 0; i < include_path.GetCount(); i++) + { + rb_ary_push(paths, rb_str_new2(include_path[i].mb_str(wxConvISO8859_1))); + } - RubyThreadedCall::RubyThreadedCall(RubyCallArguments *a, VALUE *res) + } + + RubyThread::RubyThread(wxPathList paths) : wxThread(wxTHREAD_JOINABLE) - ,args(a), result(res) + ,include_path(paths) + ,action(NOTHING) { int prio = Options.AsInt(_T("Automation Thread Priority")); if (prio == 0) prio = 50; // normal @@ -418,20 +442,29 @@ namespace Automation4 { Run(); } - wxThread::ExitCode RubyThreadedCall::Entry() + wxThread::ExitCode RubyThread::Entry() { - int error = 0; - *result = rb_protect(rbCallWrapper, reinterpret_cast(args), &error); - if(RubyProgressSink::inst) - { - RubyProgressSink::inst->script_finished = true; - wxWakeUpIdle(); - } - if(error) { - RubyScript::RubyError(); - return (wxThread::ExitCode)1; - } - return (wxThread::ExitCode)0; + InitRuby(); + ruby_script_sem->Post(); + do { + ruby_thread_sem->Wait(); + status = 0; + switch(action) + { + case LOAD_FILE: + rb_protect(rbLoadWrapper, rb_str_new2(file), &status); + break; + case CALL_FUNCTION: + *result = rb_protect(rbCallWrapper, reinterpret_cast(args), &status); + if(RubyProgressSink::inst) + { + RubyProgressSink::inst->script_finished = true; + wxWakeUpIdle(); + } + break; + } + ruby_script_sem->Post(); + }while(1); } // RubyFeatureFilter @@ -466,7 +499,6 @@ namespace Automation4 { { try { - rb_gc_disable(); VALUE cfg; if (has_config && config_dialog) { cfg = config_dialog->RubyReadBack(); @@ -481,28 +513,25 @@ namespace Automation4 { argv[1] = cfg; // config RubyCallArguments arg(rb_mKernel, rb_to_id(filter_fun), 2, argv); VALUE result; - RubyThreadedCall call(&arg, &result); + ruby_thread->CallFunction(&arg, &result); + ruby_thread_sem->Post(); RubyProgressSink::inst->ShowModal(); + ruby_script_sem->Wait(); + if(ruby_thread->GetStatus()) + RubyScript::RubyError(); RubyProgressSink::inst = NULL; - wxThread::ExitCode code = call.Wait(); delete RubyProgressSink::inst; - /*if(code) // error reporting doesn't work in ruby 1.9 - { - if(TYPE(result) == T_STRING) - throw StringValueCStr(result); - else throw "Unknown Error"; - } - else*/ if(TYPE(result) == T_ARRAY) + if(TYPE(result) == T_ARRAY) { + rb_gc_disable(); subsobj->RubyUpdateAssFile(result); + rb_gc_enable(); } delete subsobj; } catch (const char* e) { wxString *err = new wxString(e, wxConvUTF8); wxMessageBox(*err, _T("Error running filter"),wxICON_ERROR | wxOK); } - rb_gc_enable(); - rb_gc_start(); } ScriptConfigDialog* RubyFeatureFilter::GenerateConfigDialog(wxWindow *parent) @@ -523,9 +552,12 @@ namespace Automation4 { argv[1] = Qnil; // TODO: stored options RubyCallArguments arg(rb_mKernel, rb_to_id(dialog_fun), 2, argv); VALUE dialog_data; - RubyThreadedCall call(&arg, &dialog_data); + ruby_thread->CallFunction(&arg, &dialog_data); + ruby_thread_sem->Post(); RubyProgressSink::inst->ShowModal(); - wxThread::ExitCode code = call.Wait(); + ruby_script_sem->Wait(); + if(ruby_thread->GetStatus()) + RubyScript::RubyError(); delete RubyProgressSink::inst; RubyProgressSink::inst = NULL; @@ -677,12 +709,12 @@ namespace Automation4 { VALUE RubyScript::backtrace_hook(VALUE self, VALUE backtr) { - int len = RARRAY_LEN(backtr); + int len = RARRAY(backtr)->len; VALUE err = rb_funcall(self, rb_intern("to_s"), 0); error = wxString(StringValueCStr(err), wxConvUTF8); for(int i = 0; i < len; ++i) { - VALUE str = RARRAY_PTR(backtr)[i]; + VALUE str = RARRAY(backtr)->ptr[i]; wxString line(StringValueCStr(str), wxConvUTF8); backtrace.Append(line + _T("\n")); } diff --git a/aegisub/auto4_ruby.h b/aegisub/auto4_ruby.h index 2ab02add3..fbbc7d155 100644 --- a/aegisub/auto4_ruby.h +++ b/aegisub/auto4_ruby.h @@ -158,26 +158,23 @@ namespace Automation4 { friend class RubyProgressSink; private: - static wxString error; - static wxString backtrace; void Create(); // load script and create internal structures etc. void Destroy(); // destroy internal structures, unreg features and delete environment static RubyScript* GetScriptObject(); + public: static VALUE RubyTextExtents(VALUE self, VALUE style, VALUE text); static VALUE RubyFrameToTime(VALUE self, VALUE frame); static VALUE RubyTimeToFrame(VALUE self, VALUE time); static VALUE RubyKeyFrames(VALUE self); static VALUE backtrace_hook(VALUE self, VALUE backtr); - public: RubyScript(const wxString &filename); static void RubyError(); static wxString GetError(); virtual ~RubyScript(); virtual void Reload(); - static VALUE RubyAegisub; static RubyScript* inst; }; @@ -243,14 +240,22 @@ namespace Automation4 { RubyCallArguments(VALUE _recv, ID _id, int _n, VALUE *_argv); }; - // A single call to a Ruby function, run inside a separate thread. - // This object should be created on the stack in the function that does the call. - class RubyThreadedCall : public wxThread { + // Separate thread for ruby interpreter + class RubyThread : public wxThread { private: + enum {NOTHING, CALL_FUNCTION, LOAD_FILE}; + int action; + int status; RubyCallArguments *args; + const char* file; VALUE *result; + void InitRuby(); + wxPathList include_path; public: - RubyThreadedCall(RubyCallArguments *args, VALUE *result); + RubyThread(wxPathList include_path); + void CallFunction(RubyCallArguments* arg, VALUE *res); + void LoadFile(const char* file); + int GetStatus() {return status;}; virtual ExitCode Entry(); }; diff --git a/aegisub/auto4_ruby_assfile.cpp b/aegisub/auto4_ruby_assfile.cpp index 4c2ad4683..ccc6f3ffe 100644 --- a/aegisub/auto4_ruby_assfile.cpp +++ b/aegisub/auto4_ruby_assfile.cpp @@ -351,7 +351,6 @@ namespace Automation4 { new_entry = reinterpret_cast(rb_protect(rb2AssWrapper, rbEntry, &status)); --size; }while(status != 0); // broken lines at the beginning? - rb_set_errinfo(Qnil);; // just in case entryIter e = ass->Line.begin(); if(new_entry->GetType() == ENTRY_DIALOGUE) // check if the first line is a dialogue @@ -369,15 +368,6 @@ namespace Automation4 { rbEntry = rb_ary_shift(subtitles); new_entry = reinterpret_cast(rb_protect(rb2AssWrapper, rbEntry, &status)); if(status == 0) ass->Line.push_back(new_entry); - else - { - if(RubyProgressSink::inst) - { - VALUE err = rb_errinfo(); - RubyProgressSink::inst->RubyDebugOut(1, &err, Qnil); - } - rb_set_errinfo(Qnil); - } } if (can_set_undo) { @@ -411,12 +401,6 @@ namespace Automation4 { RubyAssFile::~RubyAssFile() { RubyObjects::Get()->Unregister(rbAssFile); - int status; - rb_protect(rbGcWrapper, Qnil, &status); // run the gc - if(status != 0) - { - wxMessageBox(_T("Error"), _T("Error while running Ruby garbage collection")); - } } RubyAssFile::RubyAssFile(AssFile *_ass, bool _can_modify, bool _can_set_undo) @@ -424,7 +408,7 @@ namespace Automation4 { , can_modify(_can_modify) , can_set_undo(_can_set_undo) { - + rb_gc_disable(); rbAssFile = rb_ary_new2(ass->Line.size()); RubyObjects::Get()->Register(rbAssFile); @@ -434,17 +418,8 @@ namespace Automation4 { { VALUE res = rb_protect(rbAss2RbWrapper, reinterpret_cast(*entry), &status); if(status == 0) rb_ary_push(rbAssFile, res); - else - { - if(RubyProgressSink::inst) - { - VALUE err = rb_errinfo(); - RubyProgressSink::inst->RubyDebugOut(1, &err, Qnil); - } - rb_set_errinfo(Qnil); - } } - + rb_gc_enable(); // TODO //rb_define_module_function(RubyScript::RubyAegisub, "parse_tag_data",reinterpret_cast(&RubyParseTagData), 1); //rb_define_module_function(RubyScript::RubyAegisub, "unparse_tag_data",reinterpret_cast(&RubyUnparseTagData), 1);