Separate thread for Ruby

Originally committed to SVN as r939.
This commit is contained in:
pomyk 2007-02-15 18:54:46 +00:00
parent bfa2edbe04
commit 9dcae98180
3 changed files with 137 additions and 125 deletions

View file

@ -68,9 +68,12 @@ namespace Automation4 {
RubyObjects *RubyObjects::inst = NULL; RubyObjects *RubyObjects::inst = NULL;
RubyScript * RubyScript::inst = NULL; // current Ruby Script RubyScript * RubyScript::inst = NULL; // current Ruby Script
RubyProgressSink* RubyProgressSink::inst = NULL; RubyProgressSink* RubyProgressSink::inst = NULL;
VALUE RubyScript::RubyAegisub = Qfalse; RubyThread* ruby_thread = NULL;
wxString RubyScript::backtrace = _T(""); wxSemaphore* ruby_thread_sem = NULL;
wxString RubyScript::error = _T(""); wxSemaphore* ruby_script_sem = NULL;
VALUE RubyAegisub = Qfalse;
wxString backtrace = _T("");
wxString error = _T("");
// RubyScript // 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() RubyScript::~RubyScript()
{ {
} }
@ -94,53 +110,25 @@ namespace Automation4 {
void RubyScript::Create() void RubyScript::Create()
{ {
Destroy(); Destroy();
RubyScript::inst = this;
try { try {
#if defined(NT) if(ruby_thread == NULL)
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<RB_HOOK>(&RubyFeatureMacro::RubyRegister), 4);
rb_define_module_function(RubyAegisub, "register_filter",reinterpret_cast<RB_HOOK>(&RubyFeatureFilter::RubyRegister), 5);
rb_define_module_function(RubyAegisub, "text_extents",reinterpret_cast<RB_HOOK>(&RubyTextExtents), 2);
rb_define_module_function(RubyAegisub, "frame_to_time",reinterpret_cast<RB_HOOK>(&RubyFrameToTime), 1);
rb_define_module_function(RubyAegisub, "time_to_frame",reinterpret_cast<RB_HOOK>(&RubyTimeToFrame), 1);
rb_define_module_function(RubyAegisub, "key_frames",reinterpret_cast<RB_HOOK>(&RubyKeyFrames), 0);
rb_define_module_function(rb_eException, "set_backtrace",reinterpret_cast<RB_HOOK>(&backtrace_hook), 1);
rb_define_module_function(RubyScript::RubyAegisub, "progress_set",reinterpret_cast<RB_HOOK>(&RubyProgressSink::RubySetProgress), 1);
rb_define_module_function(RubyScript::RubyAegisub, "progress_task",reinterpret_cast<RB_HOOK>(&RubyProgressSink::RubySetTask), 1);
rb_define_module_function(RubyScript::RubyAegisub, "progress_title",reinterpret_cast<RB_HOOK>(&RubyProgressSink::RubySetTitle), 1);
rb_define_module_function(RubyScript::RubyAegisub, "debug_out",reinterpret_cast<RB_HOOK>(&RubyProgressSink::RubyDebugOut), -1);
rb_define_module_function(RubyScript::RubyAegisub, "get_cancelled",reinterpret_cast<RB_HOOK>(&RubyProgressSink::RubyGetCancelled), 0);
rb_define_module_function(RubyScript::RubyAegisub, "display_dialog",reinterpret_cast<RB_HOOK>(&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))); 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; int status = 0;
wxCharBuffer buf = GetFilename().mb_str(wxConvISO8859_1); wxCharBuffer buf = GetFilename().mb_str(wxConvISO8859_1);
const char *t = buf.data(); const char *t = buf.data();
ruby_thread->LoadFile(t);
rb_protect(rbLoadWrapper, rb_str_new2(t), &status); ruby_thread_sem->Post();
if(status > 0) // something bad happened (probably parsing error) ruby_script_sem->Wait();
{ if(ruby_thread->GetStatus())
wxString *err = new wxString(_T("An error occurred initialising the script file \"") + GetFilename() + _T("\":\n\n") + GetError()); RubyScript::RubyError();
throw err->c_str();
}
VALUE global_var = rb_gv_get("$script_name"); VALUE global_var = rb_gv_get("$script_name");
if(TYPE(global_var) == T_STRING) if(TYPE(global_var) == T_STRING)
@ -165,9 +153,6 @@ namespace Automation4 {
void RubyScript::Destroy() void RubyScript::Destroy()
{ {
if(loaded) {
// ruby_finalize(); // broken in 1.9 ?_?
}
// remove features // remove features
for (int i = 0; i < (int)features.size(); i++) { for (int i = 0; i < (int)features.size(); i++) {
@ -265,6 +250,8 @@ namespace Automation4 {
void RubyScript::RubyError() void RubyScript::RubyError()
{ {
wxMessageBox(RubyScript::inst->GetError(), _T("Error"),wxICON_ERROR | wxOK); wxMessageBox(RubyScript::inst->GetError(), _T("Error"),wxICON_ERROR | wxOK);
error = _T("");
backtrace = _T("");
} }
@ -335,13 +322,12 @@ namespace Automation4 {
argv[2] = INT2FIX(active); argv[2] = INT2FIX(active);
RubyCallArguments arg(rb_mKernel, rb_to_id(validation_fun), 3, argv); RubyCallArguments arg(rb_mKernel, rb_to_id(validation_fun), 3, argv);
VALUE result; VALUE result;
RubyThreadedCall call(&arg, &result); ruby_thread->CallFunction(&arg, &result);
wxThread::ExitCode code = call.Wait(); ruby_thread_sem->Post();
if(code) ruby_script_sem->Wait();
{ if(ruby_thread->GetStatus())
wxMessageBox(RubyScript::GetError(), _T("Error running validation function"),wxICON_ERROR | wxOK); RubyScript::RubyError();
if(result != Qnil && result != Qfalse) {
} else if(result != Qnil && result != Qfalse) {
return true; return true;
} }
@ -350,7 +336,6 @@ namespace Automation4 {
void RubyFeatureMacro::Process(AssFile *subs, const std::vector<int> &selected, int active, wxWindow * const progress_parent) void RubyFeatureMacro::Process(AssFile *subs, const std::vector<int> &selected, int active, wxWindow * const progress_parent)
{ {
rb_gc_disable();
delete RubyProgressSink::inst; delete RubyProgressSink::inst;
RubyProgressSink::inst = new RubyProgressSink(progress_parent, false); RubyProgressSink::inst = new RubyProgressSink(progress_parent, false);
RubyProgressSink::inst->SetTitle(GetName()); RubyProgressSink::inst->SetTitle(GetName());
@ -363,13 +348,17 @@ namespace Automation4 {
argv[2] = INT2FIX(active); argv[2] = INT2FIX(active);
RubyCallArguments arg(rb_mKernel, rb_to_id(macro_fun), 3, argv); RubyCallArguments arg(rb_mKernel, rb_to_id(macro_fun), 3, argv);
VALUE result; VALUE result;
RubyThreadedCall call(&arg, &result); ruby_thread->CallFunction(&arg, &result);
ruby_thread_sem->Post();
RubyProgressSink::inst->ShowModal(); RubyProgressSink::inst->ShowModal();
wxThread::ExitCode code = call.Wait(); ruby_script_sem->Wait();
delete RubyProgressSink::inst; delete RubyProgressSink::inst;
RubyProgressSink::inst = NULL; 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; bool end = false;
for(int i = 0; i < RARRAY(result)->len && !end; ++i) for(int i = 0; i < RARRAY(result)->len && !end; ++i)
{ {
@ -397,16 +386,51 @@ namespace Automation4 {
break; break;
} }
} }
rb_gc_enable();
} }
delete subsobj; 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<RB_HOOK>(&RubyFeatureMacro::RubyRegister), 4);
rb_define_module_function(RubyAegisub, "register_filter",reinterpret_cast<RB_HOOK>(&RubyFeatureFilter::RubyRegister), 5);
rb_define_module_function(RubyAegisub, "text_extents",reinterpret_cast<RB_HOOK>(&RubyScript::RubyTextExtents), 2);
rb_define_module_function(RubyAegisub, "frame_to_time",reinterpret_cast<RB_HOOK>(&RubyScript::RubyFrameToTime), 1);
rb_define_module_function(RubyAegisub, "time_to_frame",reinterpret_cast<RB_HOOK>(&RubyScript::RubyTimeToFrame), 1);
rb_define_module_function(RubyAegisub, "key_frames",reinterpret_cast<RB_HOOK>(&RubyScript::RubyKeyFrames), 0);
rb_define_module_function(rb_eException, "set_backtrace",reinterpret_cast<RB_HOOK>(&RubyScript::backtrace_hook), 1);
rb_define_module_function(RubyAegisub, "progress_set",reinterpret_cast<RB_HOOK>(&RubyProgressSink::RubySetProgress), 1);
rb_define_module_function(RubyAegisub, "progress_task",reinterpret_cast<RB_HOOK>(&RubyProgressSink::RubySetTask), 1);
rb_define_module_function(RubyAegisub, "progress_title",reinterpret_cast<RB_HOOK>(&RubyProgressSink::RubySetTitle), 1);
rb_define_module_function(RubyAegisub, "debug_out",reinterpret_cast<RB_HOOK>(&RubyProgressSink::RubyDebugOut), -1);
rb_define_module_function(RubyAegisub, "get_cancelled",reinterpret_cast<RB_HOOK>(&RubyProgressSink::RubyGetCancelled), 0);
rb_define_module_function(RubyAegisub, "display_dialog",reinterpret_cast<RB_HOOK>(&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) : wxThread(wxTHREAD_JOINABLE)
,args(a), result(res) ,include_path(paths)
,action(NOTHING)
{ {
int prio = Options.AsInt(_T("Automation Thread Priority")); int prio = Options.AsInt(_T("Automation Thread Priority"));
if (prio == 0) prio = 50; // normal if (prio == 0) prio = 50; // normal
@ -418,20 +442,29 @@ namespace Automation4 {
Run(); Run();
} }
wxThread::ExitCode RubyThreadedCall::Entry() wxThread::ExitCode RubyThread::Entry()
{ {
int error = 0; InitRuby();
*result = rb_protect(rbCallWrapper, reinterpret_cast<VALUE>(args), &error); ruby_script_sem->Post();
if(RubyProgressSink::inst) do {
{ ruby_thread_sem->Wait();
RubyProgressSink::inst->script_finished = true; status = 0;
wxWakeUpIdle(); switch(action)
} {
if(error) { case LOAD_FILE:
RubyScript::RubyError(); rb_protect(rbLoadWrapper, rb_str_new2(file), &status);
return (wxThread::ExitCode)1; break;
} case CALL_FUNCTION:
return (wxThread::ExitCode)0; *result = rb_protect(rbCallWrapper, reinterpret_cast<VALUE>(args), &status);
if(RubyProgressSink::inst)
{
RubyProgressSink::inst->script_finished = true;
wxWakeUpIdle();
}
break;
}
ruby_script_sem->Post();
}while(1);
} }
// RubyFeatureFilter // RubyFeatureFilter
@ -466,7 +499,6 @@ namespace Automation4 {
{ {
try { try {
rb_gc_disable();
VALUE cfg; VALUE cfg;
if (has_config && config_dialog) { if (has_config && config_dialog) {
cfg = config_dialog->RubyReadBack(); cfg = config_dialog->RubyReadBack();
@ -481,28 +513,25 @@ namespace Automation4 {
argv[1] = cfg; // config argv[1] = cfg; // config
RubyCallArguments arg(rb_mKernel, rb_to_id(filter_fun), 2, argv); RubyCallArguments arg(rb_mKernel, rb_to_id(filter_fun), 2, argv);
VALUE result; VALUE result;
RubyThreadedCall call(&arg, &result); ruby_thread->CallFunction(&arg, &result);
ruby_thread_sem->Post();
RubyProgressSink::inst->ShowModal(); RubyProgressSink::inst->ShowModal();
ruby_script_sem->Wait();
if(ruby_thread->GetStatus())
RubyScript::RubyError();
RubyProgressSink::inst = NULL; RubyProgressSink::inst = NULL;
wxThread::ExitCode code = call.Wait();
delete RubyProgressSink::inst; delete RubyProgressSink::inst;
/*if(code) // error reporting doesn't work in ruby 1.9 if(TYPE(result) == T_ARRAY)
{
if(TYPE(result) == T_STRING)
throw StringValueCStr(result);
else throw "Unknown Error";
}
else*/ if(TYPE(result) == T_ARRAY)
{ {
rb_gc_disable();
subsobj->RubyUpdateAssFile(result); subsobj->RubyUpdateAssFile(result);
rb_gc_enable();
} }
delete subsobj; delete subsobj;
} catch (const char* e) { } catch (const char* e) {
wxString *err = new wxString(e, wxConvUTF8); wxString *err = new wxString(e, wxConvUTF8);
wxMessageBox(*err, _T("Error running filter"),wxICON_ERROR | wxOK); wxMessageBox(*err, _T("Error running filter"),wxICON_ERROR | wxOK);
} }
rb_gc_enable();
rb_gc_start();
} }
ScriptConfigDialog* RubyFeatureFilter::GenerateConfigDialog(wxWindow *parent) ScriptConfigDialog* RubyFeatureFilter::GenerateConfigDialog(wxWindow *parent)
@ -523,9 +552,12 @@ namespace Automation4 {
argv[1] = Qnil; // TODO: stored options argv[1] = Qnil; // TODO: stored options
RubyCallArguments arg(rb_mKernel, rb_to_id(dialog_fun), 2, argv); RubyCallArguments arg(rb_mKernel, rb_to_id(dialog_fun), 2, argv);
VALUE dialog_data; VALUE dialog_data;
RubyThreadedCall call(&arg, &dialog_data); ruby_thread->CallFunction(&arg, &dialog_data);
ruby_thread_sem->Post();
RubyProgressSink::inst->ShowModal(); RubyProgressSink::inst->ShowModal();
wxThread::ExitCode code = call.Wait(); ruby_script_sem->Wait();
if(ruby_thread->GetStatus())
RubyScript::RubyError();
delete RubyProgressSink::inst; delete RubyProgressSink::inst;
RubyProgressSink::inst = NULL; RubyProgressSink::inst = NULL;
@ -677,12 +709,12 @@ namespace Automation4 {
VALUE RubyScript::backtrace_hook(VALUE self, VALUE backtr) 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); VALUE err = rb_funcall(self, rb_intern("to_s"), 0);
error = wxString(StringValueCStr(err), wxConvUTF8); error = wxString(StringValueCStr(err), wxConvUTF8);
for(int i = 0; i < len; ++i) for(int i = 0; i < len; ++i)
{ {
VALUE str = RARRAY_PTR(backtr)[i]; VALUE str = RARRAY(backtr)->ptr[i];
wxString line(StringValueCStr(str), wxConvUTF8); wxString line(StringValueCStr(str), wxConvUTF8);
backtrace.Append(line + _T("\n")); backtrace.Append(line + _T("\n"));
} }

View file

@ -158,26 +158,23 @@ namespace Automation4 {
friend class RubyProgressSink; friend class RubyProgressSink;
private: private:
static wxString error;
static wxString backtrace;
void Create(); // load script and create internal structures etc. void Create(); // load script and create internal structures etc.
void Destroy(); // destroy internal structures, unreg features and delete environment void Destroy(); // destroy internal structures, unreg features and delete environment
static RubyScript* GetScriptObject(); static RubyScript* GetScriptObject();
public:
static VALUE RubyTextExtents(VALUE self, VALUE style, VALUE text); static VALUE RubyTextExtents(VALUE self, VALUE style, VALUE text);
static VALUE RubyFrameToTime(VALUE self, VALUE frame); static VALUE RubyFrameToTime(VALUE self, VALUE frame);
static VALUE RubyTimeToFrame(VALUE self, VALUE time); static VALUE RubyTimeToFrame(VALUE self, VALUE time);
static VALUE RubyKeyFrames(VALUE self); static VALUE RubyKeyFrames(VALUE self);
static VALUE backtrace_hook(VALUE self, VALUE backtr); static VALUE backtrace_hook(VALUE self, VALUE backtr);
public:
RubyScript(const wxString &filename); RubyScript(const wxString &filename);
static void RubyError(); static void RubyError();
static wxString GetError(); static wxString GetError();
virtual ~RubyScript(); virtual ~RubyScript();
virtual void Reload(); virtual void Reload();
static VALUE RubyAegisub;
static RubyScript* inst; static RubyScript* inst;
}; };
@ -243,14 +240,22 @@ namespace Automation4 {
RubyCallArguments(VALUE _recv, ID _id, int _n, VALUE *_argv); RubyCallArguments(VALUE _recv, ID _id, int _n, VALUE *_argv);
}; };
// A single call to a Ruby function, run inside a separate thread. // Separate thread for ruby interpreter
// This object should be created on the stack in the function that does the call. class RubyThread : public wxThread {
class RubyThreadedCall : public wxThread {
private: private:
enum {NOTHING, CALL_FUNCTION, LOAD_FILE};
int action;
int status;
RubyCallArguments *args; RubyCallArguments *args;
const char* file;
VALUE *result; VALUE *result;
void InitRuby();
wxPathList include_path;
public: 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(); virtual ExitCode Entry();
}; };

View file

@ -351,7 +351,6 @@ namespace Automation4 {
new_entry = reinterpret_cast<AssEntry*>(rb_protect(rb2AssWrapper, rbEntry, &status)); new_entry = reinterpret_cast<AssEntry*>(rb_protect(rb2AssWrapper, rbEntry, &status));
--size; --size;
}while(status != 0); // broken lines at the beginning? }while(status != 0); // broken lines at the beginning?
rb_set_errinfo(Qnil);; // just in case
entryIter e = ass->Line.begin(); entryIter e = ass->Line.begin();
if(new_entry->GetType() == ENTRY_DIALOGUE) // check if the first line is a dialogue 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); rbEntry = rb_ary_shift(subtitles);
new_entry = reinterpret_cast<AssEntry*>(rb_protect(rb2AssWrapper, rbEntry, &status)); new_entry = reinterpret_cast<AssEntry*>(rb_protect(rb2AssWrapper, rbEntry, &status));
if(status == 0) ass->Line.push_back(new_entry); 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) { if (can_set_undo) {
@ -411,12 +401,6 @@ namespace Automation4 {
RubyAssFile::~RubyAssFile() RubyAssFile::~RubyAssFile()
{ {
RubyObjects::Get()->Unregister(rbAssFile); 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) RubyAssFile::RubyAssFile(AssFile *_ass, bool _can_modify, bool _can_set_undo)
@ -424,7 +408,7 @@ namespace Automation4 {
, can_modify(_can_modify) , can_modify(_can_modify)
, can_set_undo(_can_set_undo) , can_set_undo(_can_set_undo)
{ {
rb_gc_disable();
rbAssFile = rb_ary_new2(ass->Line.size()); rbAssFile = rb_ary_new2(ass->Line.size());
RubyObjects::Get()->Register(rbAssFile); RubyObjects::Get()->Register(rbAssFile);
@ -434,17 +418,8 @@ namespace Automation4 {
{ {
VALUE res = rb_protect(rbAss2RbWrapper, reinterpret_cast<VALUE>(*entry), &status); VALUE res = rb_protect(rbAss2RbWrapper, reinterpret_cast<VALUE>(*entry), &status);
if(status == 0) rb_ary_push(rbAssFile, res); 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 // TODO
//rb_define_module_function(RubyScript::RubyAegisub, "parse_tag_data",reinterpret_cast<RB_HOOK>(&RubyParseTagData), 1); //rb_define_module_function(RubyScript::RubyAegisub, "parse_tag_data",reinterpret_cast<RB_HOOK>(&RubyParseTagData), 1);
//rb_define_module_function(RubyScript::RubyAegisub, "unparse_tag_data",reinterpret_cast<RB_HOOK>(&RubyUnparseTagData), 1); //rb_define_module_function(RubyScript::RubyAegisub, "unparse_tag_data",reinterpret_cast<RB_HOOK>(&RubyUnparseTagData), 1);