GD ha escrito: > This is what I needed to replace LUA: > - Ability to load a script *from*memory*, not from a file (important). Use rb_eval_string_protect(). > - Run any init code in the script. BEGIN {} calls or rb_eval_string_protect() of whatever you need. > - Hook a bunch of local functions (C++) so that the script can call them for > information from the main code body. Ideally, handle this using SWIG, as either a ruby extension or a pseudo extension you link in statically. Avoid doing the binding yourself manually. Binding functions for most basic C constructs (ints, chars, etc) is VERY easy. Doing proper binding of classes, it is not as easy, as the GC requires you to be careful. Best thing for you to do is look at SWIG's output. SWIG also deals properly now with inheritance of C++ classes into ruby, including multiple inheritance. > - Call functions in the script both periodically and in response to events (eg. > an update call every 1/60 of a second, in response to mouse clicks, etc). This may be much trickier with the current ruby C interpreter, depending on what you are looking for. You can start a C++ thread to do it, but the ruby C interpreter is not thread safe. So, if you have ruby code running in two different threads at the same time, bad things WILL happen. You can also try to rely on a single thread and let ruby's own light threads handle this. However, this may not be as efficient as actual multi-threaded code. Mind you, ruby is not the only language with issues here. Python is also pretty bad at this. LUA and TCL are in a class of their own as far as multithreading interpreters go. > - Clean up everything entirely when the level is over. > > This is what I have encountered: > > - There doesn't appear to be any way to indicate that Ruby should clean > everything up and get to a state where I can start again. I have attempted to > work around this by making each script contain a single unique class that I use, > but this is a terrible hack. ruby_cleanup(0); SHOULD clean up the interpreter. Problem is not so much cleaning the interpreter but restarting it again. I did find issues when I did this (back in 1.8.0). I think now issues are better, but I won't vouch for it being 100% solved. If all fails, best thing you can do is what you did. Encapsulate all in its own class and clean that instead. That will still leave the class/module constants around, but it is better than nothing. > - How do I load a script from memory? rb_eval_string()? rb_compile_string()? > Something else? Use rb_eval_string_protect(). > - The documentation is flawed. It indicates calling rb_run(), but that can > eventually call exit, which is the last thing you want from an embedded > language. It mentions rb_apply() as having an argc parameter. Again, rb_eval_string_protect() is what you probably want. > - I am getting inexplicable crashes when calling the garbage collector. This is a probably a bug in your code. Take a look at rubyMEL at highend3d.com for a relatively simple embedding of MEL into a 3d application (Maya). Depending on platform (windows, mainly) you need other initialization code besides ruby_init(). > - The documentation is not at all clear how you protect something from the > garbage collector, or indicate it is no longer in use. You need to use a mark and a free function respectively, and use ruby's mysterious Data_Wrap_Struct stuff. This is the pretty hairy part of exposing a C++ api. Suggestion: look at SWIG's code. It has a bunch of wrapper functions to handle it nicely and it is now pretty mature. For any questions regarding SWIG not clear from the SWIG manual, refer to FXRuby's excellent wrapping of the Fox library. > - How does one call "Foo.new(args)" on a class from outside of Ruby, where the > args have been generated from outside of Ruby (hence no name) meaning You call it like any other method, with rb_funcall* like: VALUE cFoo; // defined somewhere else... int nargs = 1; VALUE args[] = { INT2NUM( 30 ) }; ID new_id = rb_intern("new"); rb_funcall2( cFoo, new_id, nargs, args ); > - rb_protect() is a complete mess. Rather than being able to call an arbitrary > function with a void pointer, you are stuck calling something that takes a > single VALUE arg. No, you are wrong. It is extremely well thought out. No need for ugly void*, thank god. Think of VALUE as being a void* (it is really an unsigned long). Ruby requires sizeof(unsigned long) == sizeof(void*) -- see ruby.h's check. Just do a simple casting of your pointer to VALUE in your caller and do the opposite cast from VALUE to whatever in your receiver function. See how all the RCAST() and similar macros work in ruby.h. > - The code runs fine for a while if I don't call the garbage collector. > Unfortunately, I get stack depth errors and eventual segfaults if I make > repeated calls to the Ruby script (via rb_apply()). This likely means you wrapped your C++ classes incorrectly or you are trying to run ruby in a multi-threaded environment. If the first is true, look and learn from SWIG. If the second is true, remove the multi-threaded code, create a global mutex that all your ruby functions should call before doing anything or use a different scripting language at this time. Ruby is not thread safe, unlike LUA. > - I can't find one example that completely and correctly demonstrates the whole > process involved in having C call Ruby then call C, return values through the > chain neatly, and clean up. If there was such a reference, it would be outstanding. There's no real trick to it. Ruby is fine as an embedded language, just as you remember it is not safe as a multi-threaded embedded language. YARV may eventually solve this somewhere in the future. The unofficial fork of Ruby that is Sydney (and now rubinius) supposedly is multi-thread safe, but is 32-bits only (and I won't vouch for it). Python is just as bad as Ruby, btw, albeit it avoids some threading issues by using a global lock every time two threads try to run some code simultaneously, effectively running only a single thread at a time. In ruby, you can do the same, but you need to create this global mutex yourself. LUA and TCL are different and better in this aspect, as they both can use different (and very light) interpreter instances to execute code. > > Am I alone in thinking this? Should I be looking at other embeddable languages > (Python, for example), or just go back and get the LUA code working again? Am I > doing something wrong? Truth is... you are looking at the two best languages for embedding. LUA is admittedly better to embed due its multi-threading aspects, while Ruby is admittedly better for embedding due to its nice syntax. > > Is there anything I can do to successfully and reliably add support for my > favourite scripting language into my project? Or is it simply an unrealistic > prospect? > Follow the advices I gave you and you should be able to get ruby as an embedded language. Wrapping classes for the garbage collector is the hairy part, but you can steal all of SWIG's source code for that now (or use SWIG itself for doing it). However, if your game engine does require heavy multi-threading in the scripting language, you are indeed looking at the wrong language. LUA is still the best language for this, unfortunately.