Skip to content

Ruby Processing Internals and JRuby Tricks

Martin Prout edited this page Aug 26, 2015 · 33 revisions

Schema:

The Guts:

Ruby-Processing uses regular Ruby for generating sketches, exporting applications and applets; and uses Java via JRuby for running Processing. The main class, Processing::App, inherits from Processing’s PApplet, and has access to all of the PApplet’s methods and fields. When you call methods inside of your sketch, they may end up being called within JRuby-space, or out in Java-space, depending on whether the method is defined in the Processing::App or the processing.core.PApplet. For most methods defined in the Processing API, it entails calling into Java, with JRuby taking care of translating all the arguments and the return value. However, a handful of useful methods (like load_library, render_mode, and many of the math functions) are defined in JRuby. Ruby-processing also provides a more ruby-like alternative to processings PVector class (to use this you will need to load the ‘vecmath’ library) see examples.

Inside the lib folder of the Ruby-Processing source, you’ll find:

  • a ruby folder (that contains the vendored jruby-complete.jar), since processing-2.4 processing jars are all accessed by classpath, from an installed vanilla processing.
  • ruby-processing is the belly of the beast. It contains the Processing::App itself, the runners, and the exporters.
    • app.rb provides the Processing::App that you inherit from when you write a Ruby-Processing sketch. It takes care to try and map the many Processing methods, static methods and fields to accessible constructs in idiomatic Ruby. It provides methods for loading Processing libraries and Ruby libraries into your sketch. It knows how to display a Ruby-Processing sketch in a window, in a full-screen context.
    • runner.rb is the class that executes all of the rp5 commands. All calls to rp5 begin by going through the Processing::Runner in a vanilla Ruby process, before being replaced by the Java process that will run one of the scripts in the lib/runners folder via JRuby. In that folder, you can find runners for the run, live, and watch commands.
    • The exporters folder contains all the classes needed to export Ruby-Processing sketches.

JRuby’s Guts

For a fuller description of JRuby internals, take a look at JRuby Wiki, but here’s a brief overview:

  • JRuby is a 100% Java implementation of the Ruby programming language. It is Ruby for the JVM.
  • Internally, Java can handle many different JRuby runtimes running in separate threads. All objects have a reference to the runtime that they belong to, and cannot be transported across runtimes without being serialized.
  • JRuby threads map directly to Java threads, so you can use the full power of your multi-core or multi-processor machine to drive computationally intensive Ruby-Processing sketches, within a single process.
  • Ruby can call methods defined in Java space via bytecode-generated adapter classes that directly invoke the methods. This avoids Java’s reflection capabilities, and provides better performance. There is still significant overhead when calling into Java, however, and as the JRuby team works on reducing this overhead, Ruby-Processing continues to speed up. Core Ruby classes have even further table-based method optimizations.
  • JRuby runs with a JIT mode that will compile interpreted calls after 20 runs through the interpreter.
  • JRuby 1.7.22 is virtually 100% compatible with the Ruby language (version-1.9.3). Any libraries that are pure-Ruby and stay away from POSIX-only functions or platform-specific features are generally compatible with JRuby. JRuby-9.0.0.0 is mainly 2.2 compatible, but is not quite as fast as which is why we don’t install jruby-complete-9.0.0.0 yet (however see JRubyArt where we use JRuby-9.0.0.0 from the start because we have the faith).

JRuby Tricks & Tips

These tricks are taken from the JRuby wiki page Calling Java from JRuby, and more are available there.

Ruby-Processing already does a require 'java' for you, which gives you access to many of the core Java libraries from Ruby-space. For example, it allows you to refer to name-spaced java classes by their full paths. The following code pops open a Swing window:

win = javax.swing.JFrame.new('A Window')
win.set_visible(true)

To gain access to java classes stored in jar files, simply require the jar. To load java classes into your namespace, you can java_import them:

java_import "com.giganticorp.audio.AudioAnalyzer"
analyzer = AudioAnalyzer.new

All Java camel-cased method names on imported classes will become available to you as their Ruby underscored counterparts, so clapHandsIfHappy => clap_hands_if_happy. All Java objects gain a handful of additional methods: java_class returns the Java class of a given object, and java_kind_of? lets you know if your object is an instance of a given Java class.

JRuby’s open classes allow you to add methods to existing Java classes, but your additions will only be available from the JRuby side. Watch out for class name collisions between core Ruby classes and imported Java ones. If you java_import "java.lang.Thread" and then write MyThread < Thread, MyThread will actually inherit from core Ruby’s Thread and not the java one that you just imported.

JRuby classes can mix in Java interfaces as modules in order to implement them.

class SomeFlexibleClass
  include java.lang.Runnable
  include java.lang.Comparable
end

JRuby does a wonderful thing with Ruby’s blocks, where a block can be converted to the appropriate Java interface. It works by converting the block to a Proc, and then decorating it with a proxy object that runs the block for any method called on the interface. Runnable is the prime example here. For example:

button = javax.swing.JButton.new "Press me!"
button.add_action_listener {|event| event.source.text = "I done been pressed." }