Wednesday, August 27, 2008

Moving

New URL: http://adam-payne.blogspot.com

Monday, August 18, 2008

JRuby + JMonkeyEngine = Hello 3D World

JRuby released version 1.1.3 last month, and I missed it because these days I'm all corporate, and I have limited access to the Internet from work, and little time to get online from home. Speaking of which, I'm on daddy-diaper-duty tonight, so I'll keep this brief so as not to get interrupted...

As Charlie Nutter posted recently, "There are a few areas that remain to be tackled... Refactoring the extensive logic governing how Ruby classes can extend abstract or concrete Java classes. Kresten Krab Thorup contributed this well over a year ago, and it's kinda been an island unto itself. It will take a considerable effort to rework."

Too true, that issue has been a thorn in my virtual side for a while now. I've been interested in transitioning into game programming as a career move, and a lot of the libraries that I've dabbled with use abstract classes that must be extended. At one point, JRuby was incapable of doing that, since a Java object was actually decended from JavaProxy, not the actual Java class being extended.

Not so any more! Tonight I pulled down trunk, and loaded up jirb. I had also built the 3D scene graph library JMonkeyEngine, by all accounts a good one for Java. Rumor has it that Sun's Project Wonderland will be using it instead of Java3D, and they're dropping development on the latter for the time being. Anywho, I was able to place the native libraries in my working directory, require the jars, and...

class MyGame < com.jme.app.SimpleGame
def simpleInitGame
end
end

game = MyGame.new
game.start


and just like that, I had a bunch of output from LWJGL and an empty window! But not just any empty window... an empty window into a virtual world, yet to be populated with 3D things, and a little message in the bottom corner telling me that I was getting about 2K FPS and nothing to display. Hit ESC, and the window went away and Java shut down. Alls well that ends well.

Now, to put something into the virtual world will require more work on JRuby's Java Integration, because SimpleGame uses a protected field called rootNode, and the JRuby subclass can't access that field yet. But, oh, once it can, then it's on!

Here's how you'd do it in Scala:

class MyGame extends com.jme.app.SimpleGame {
def simpleInitGame() = { rootNode attachChild new com.jme.scene.shape.Sphere("my sphere",10,10,5) }
}
var game = new MyGame
game start


Works pretty well. My only gripe with Scala right now is the lack of Ruby-ish method aliasing ala obj.blah=x instead of obj.setBlah(x) in Java Integration land. Scala's val statement does a nice job of adding those setters and getters for pure Scala objects though.

Until JRuby gets protected field access working, this bit of code does the job:

f=javax.swing.JFrame.new 'Blah'
f.default_close_operation=0
b=javax.swing.JButton.new 'Quit'
game=com.jmex.game.StandardGame.new 'test game'
b.add_action_listener do |ev|
game.shutdown
sleep 2
java.lang.System.exit 0
end
f.add b
game.start
f.pack
f.show
state = com.jmex.game.state.DebugGameState.new
sphere = com.jme.scene.shape.Sphere.new('my sphere',10,10,5)
sphere.set_random_colors
sphere.update_render_state
state.root_node.attach_child sphere
com.jmex.game.state.GameStateManager.instance.attach_child state
state.active=true


Now there's a cool prismatic sphere in there. The window closer button is for safety's sake, because the default behavior of DebugGameState is to immediately exit Java, which tends to happen in the middle of the OpenGL loop and booches the VM. StandardGame is a move away from SimpleGame into a more OO-ish paradigm, and root_node is a Ruby-ized alias to getRootNode(). But hey, at least it's a working proof-of-concept: JRuby's Java Integration has certainly matured, and you can do some pretty snazzy stuff with it.

Wednesday, March 05, 2008

Integrate a forum with a Rails site

So you've got a fancy rails site where Users can log in and do stuff. And you'd like them to be able to host a community forum on the site, using your existing User model to authenticate users for posting, moderation, and administration. The Savage Beast 2.0 engine does just that. In typical Rails style, you just follow its conventions for determining the logged-in user and authenticating them, and you're good to go with a mini-Beast forum.

When I tried this out yesterday, I ran into tons of issues with the Engines plugin and the way that Rails development environment loads, unloads, and reloads classes. So here are a couple of things that I figured out:

  • First, I don't know thing one about Engines, other than they sound pretty useful. For some reason, after installing the engines plugin, I couldn't run my migrations. It looks like Engines tries to redefine the db:migrate task into a namespace that then migrates all of the plugins. But that didn't work with my version of Rake, so my workaround is just to migrate before installing Engines.

    *NOTE: I was using Rake 0.7.3, but when I tried using Rake 0.8.1, I didn't have the same error.
  • Lots of issues with modules being unloaded in development. So again the solution is to avoid the problem, and run in production mode.

    *NOTE: This is actually due to Rails Dependencies module and the way that Models and Controllers are loaded and unloaded in between requests, and it supposedly happens with lots of different plugins in development. Basically, if your plugin defines models or controllers, you have to call "unloadable" inside the class. Otherwise Rails doesn't unload the class at the end of the request, but does unload referenced modules in your main app. So when Savage Beast's ForumsController calls methods of AuthenticatedSystem included in ApplicationController, the AuthenticatedSystem module gets unloaded from ApplicationController but not ForumsController, and on the next request it won't be reloaded because it is still defined.
  • I also have the old 1.2.3 Rails on my work machine, since I'm maintaining old apps that I don't have time to migrate to Rails 2.0, so as you'll see, I'm explicitly freezing the 2.0.2 gems to vendor/rails.

Here's my install script. If you follow along (and none of the dependent plugin trunks have changed in a way that breaks things), you should be all set.


# steps to create a sample app for Savage Beast 2.0 using Restful Authentication as the User model
# requires: rails, svn
# note: better to run in Production environment than development, because development will cause
# Models and Controllers to unload in between requests, causing unexplained missing constants and methods
rails savage_beast_test
cd savage_beast_test
rake rails:freeze:edge TAG=rel_2-0-2
cd ..
ruby savage_beast_test/vendor/rails/railties/bin/rails savage_beast_test
# to overwrite all, type: a
cd savage_beast_test
ruby script/plugin install restful_authentication
ruby script/generate authenticated user sessions
cd vendor
cd plugins
svn checkout http://savage-beast-2.googlecode.com/svn/trunk/ savage_beast
cd ..
cd ..
cp vendor/plugins/savage_beast/db/migrate/001_create_savage_tables.rb db/migrate/002_create_savage_tables.rb
rake db:migrate
rake db:migrate RAILS_ENV=production
ruby script/plugin install white_list
ruby script/plugin install white_list_formatted_content
ruby script/plugin install acts_as_list
ruby script/plugin install svn://errtheblog.com/svn/plugins/gibberish
ruby script/plugin install svn://errtheblog.com/svn/plugins/will_paginate
ruby script/plugin install http://svn.rails-engines.org/engines/trunk



# *** app file modifications: ***

# config/environment.rb:
# Bootstrap the Rails environment, frameworks, and default configuration
require File.join(File.dirname(__FILE__), 'boot')
# Boot the Engines plugin
require File.join(File.dirname(__FILE__), '../vendor/plugins/engines/boot')
#...

# config/routes.rb:
ActionController::Routing::Routes.draw do |map|
map.from_plugin :savage_beast

map.resources :users

map.resource :session
#...

# app/models/user.rb:
include SavageBeast::UserInit
def display_name
login
end
def admin?
true
end
def currently_online
false
end
protected
#...



# app/controllers/application.rb:
class ApplicationController < ActionController::Base
helper :all # include all helpers, all the time
include AuthenticatedSystem
def admin?
logged_in?
end
#...


# *** minor fix to Savage Beast application_helper.rb:
# (note: restful_authentication#current_user returns :false instead of nil)
# vendor/plugins/savage_beast/app/helpers/application_helper.rb:
def beast_user_name
#(current_user ? current_user.display_name : "Guest" )
(logged_in? ? current_user.display_name : "Guest" )
end


ruby script/server -e production

# get: http://localhost:3000/forums
# - first hit causes an error, lowpro.js missing. Second hit works.
# get: http://localhost:3000/forums/new
# - redirects to /sessions/new
# get: http://localhost:3000/users/new
# create a new user... logs in and redirects to /forums/new with "Thanks for signing up!" flash
# create a new forum... redirects to /forums/1 with no topics
# click New Topic link, gets /forums/1/topics/new
# create new topic/first post, redirects to /forums/1/topics/1

Monday, January 14, 2008

Runnable Java Procs in JRuby

One of the cool features of the JRuby Swing GUI builder library, Profligacy (by the ever awesome Zed Shaw of Mongrel fame) is the Proc#to_runnable method that converts procs into a java.lang.Runnable interface that can be passed into any Java method that accepts an instance of Runnable, such as java.lang.Thread.new(). Profligacy's implementation looks something like this:

class RunnableProc
include java.lang.Runnable
def initialize &block
@block = block
end
def run
@block.call
end
end
class Proc
def to_runnable
RunnableProc.new &self
end
end


I prefer doing this instead:

class Proc
include java.lang.Runnable
alias_method :run, :call
end
p=Proc.new do
puts 'running'
end


The only disadvantage to that is that you can't use the familiar proc { ... } and lambda {...}, but I like having a simple 4-liner. And the Runnable interface is really useful in JRuby for working around protected Java methods when creating JRuby subclasses: I can mock up and compile a simple Java subclass that accepts a Runnable, then instantiate it in JRuby with a Proc. For example, the java.util.TimerTask class has a protected constructor, so you can't instantiate it in JRuby, even as a subclass, because a subclass is really a subclass of JavaProxy with an instance variable of the Java class. So:


//RubyTimerTask.java
public class RubyTimerTask extends java.util.TimerTask
{
public Runnable task;

public RubyTimerTask(Runnable r){
this.task = r;
}
public void run(){
this.task.run();
}
}

#timer_test.rb
include_class 'RubyTimerTask'
p = Proc.new { puts 'running' }
rtt = RubyTimerTask.new p
t=java.util.Timer.new
t.schedule rtt, 2000 #run in 2 seconds
t.cancel #join the Timer's thread

Friday, December 21, 2007

JRuby 1.0.3, Rubygems 0.9.5 TypeError

Here's that error again: After updating Rubygems to 0.9.5 (yeah, I know 1.0.0 just came out, but that one does some weird stuff with bin script filenames that I'd rather not deal with right now), trying to install rails gives:

ERROR: While executing gem ... (TypeError)
can't convert nil into Array


Running gem with the -V and --backtrace options makes it seem that the problem comes up from checking a default Ruby version requirement. Gem::Requirement.default creates a new Gem::Requirement with version GTE 0, and when it compares that version 0 to the Ruby version 1.8.5, the 0 doesn't properly get turned into an array [0] and gets left as nil, which when compared to an Array causes the above TypeError. Patching Rubygems to make it compare to_ints to to_ints rather than @ints to #ints works around the problem, but doesn't address the real issue: why does the same code work in MRI but not JRuby?

So I fired up jirb and started checking it out, and found that Gem::Version#normalize and to_ints weren't being called when comparing to the version requirement from the gemspec. Comparing to the Gem::Requirement.default worked fine.

According to the response I got from Eric on the Rubygems patch I submitted, @ints gets set from Version#initialize, #yaml_initialize, and #marshal_load. So why isn't it getting set for the default in this case? All of those constructor methods call self.version=, and #version= calls normalize to turn the version string into an array. Initialize seemed to work, but a gemspec uses yaml, and I found that yaml_initialize isn't being called!


class Foo
def initialize
@foo=1
end
def yaml_initialize tag, val
@foo=5
end
end
foo=Foo.new
YAML.load YAML.dump(foo) # #


Doh! yaml_initialize is definitely NOT being called. So I submitted JRUBY-1786, and hopefully Ola Bini or someone else will know how to fix it in the java code. In the meantime, my rubygems/version.rb workaround still does the trick.

Wednesday, December 19, 2007

JRuby 1.0.3, Rails 2.0.2, Rubygems 0.9.5

So here's the deal... The easiest way to install Rails is through Rubygems, which automatically creates the "rails command" that invokes the rails application skeleton generator, in your ruby bin directory, and sticks rails in the rubygems load path so that you can require 'rubygems' and then require 'activerecord' etc.

Problems start to pop up when you try installing Rails 2.0 (the current, production-ready Rails) in JRuby. As I mentioned earlier, the new gems were created with Rubygems 0.9.5 rather than Rubygems 0.9.4 which ships in the JRuby binary package. In C-Ruby, you can easily upgrade Rubygems by running gem update --system. That runs gem's update_command.rb, which pulls down the latest rubygems-update-XXX.gem file, unpacks it, and then runs this little ditty:


Dir.chdir update_dir do
say "Installing RubyGems #{version_string}"
setup_cmd = "#{Gem.ruby} setup.rb #{args.join ' '}"

# Make sure old rubygems isn't loaded
if Gem.win_platform? then
system "set RUBYOPT= & #{setup_cmd}"
else
system "RUBYOPT=\"\" #{setup_cmd}"
end
end


Did you catch that? UnSet the environment variable RUBYOPT, and re-run the ruby command in the install directory, running setup.rb. Like I said, it works fine in C-Ruby, but here's how Gem.ruby is defined in Rubygems 0.9.4:


# Return the Ruby command to use to execute the Ruby interpreter.
def ruby
"ruby"
end


And here it is in 0.9.5:

# Return the Ruby command to use to execute the Ruby interpreter.
def ruby
if @ruby.nil? then
@ruby = File.join(Config::CONFIG['bindir'],
Config::CONFIG['ruby_install_name'])
@ruby << Config::CONFIG['EXEEXT']
end

@ruby
end


Which is correct. The old version just says, "the ruby interpreter command is 'ruby', and that's that." Which doesn't do a lick of good if ruby isn't in your path, and more importantly, if the command is jruby and not ruby! So running gem update --system won't do any good in JRuby. We can get around that by pulling down the gem and running setup.rb manually with JRuby:

bin\jruby -S gem install rubygems-update --no-rdoc --no-ri
cd lib\ruby\gems\1.8\gems\rubygems-update-0.9.5
..\..\..\..\..\..\bin\jruby setup.rb --no-rdoc --no-ri
cd ..\..\..\..\..\..


Now, there's one more issue with Rubygems to fix before we can get Rails installed. Fire up this bit of code in bin\jirb:

fname='lib/ruby/site_ruby/1.8/rubygems/version.rb'
lines = File.readlines fname
lines[126]


You should get back: " @ints <=> other.ints\n", and ints is an attr_accessor. Makes sense, but if you just try installing rails at this point, there's an issue where checking against version 0 does not properly initialize the ints array, which is just a major/minor version number split on dots. What we need to check is to_ints, which splits the version number and returns @ints, so try this:

lines[126].gsub! /@ints|(\.)ints/, "\\1to_ints"
File.open(fname,'w'){|f| lines.each{|line| f.write line } }


Now, you can run bin\jruby -S gem install rails --no-rdoc --no-ri and be good to go. Note that Rubygems 0.9.5 has -y (--include-dependencies) as the default option, and will nag you if you specify it.

Cheers!

More Rails2.0.2 : C-Ruby Win32

In my never-ending quest to figure out what's the deal with Rails2.0 and JRuby, I'm trying the new rails out in Win32 C-Ruby with a clean install.

Ruby-186-25 One-Click Windows Installer, installed to c:\rubytest


echo %PATH%
c:\rubytest\bin;...

gemwhich rake
c:/rubytest/lib/ruby/gems/1.8/gems/rake-0.7.2/lib/rake.rb


So far, so good. Now, let's update rubygems and get rails.


gem update --system (gem update DASH_DASHsystem)

gem --version
0.9.5

gem install rails
Successfully installed rake-0.7.3
Successfully installed activesupport-2.0.2
Successfully installed activerecord-2.0.2
Successfully installed actionpack-2.0.2
Successfully installed actionmailer-2.0.2
Successfully installed activeresource-2.0.2
Successfully installed rails-2.0.2
7 gems installed

rails testapp
cd testapp
ruby script/generate scaffold blah name:string
gem install sqlite3-ruby


(download and install sqlite3.dll to c:\rubytest\bin)

rake db:migrate
(in C:/rubytest/testapp)
== 1 CreateBlahs: migrating ===============
-- create_table(:blahs)
- 0.0620s
== 1 CreateBlahs: migrated (0.0620s) ======

ruby script/server

(in another console)

irb
require 'active_resource'
class Blah < ActiveResource::Base
self.site='http://localhost:3000/'
end
b=Blah.create :name => 'Bob'

=> #<Blah:0x335287c @prefix_options={}, @attributes={"name"=>"Bob", "updated_at"=>Wed Dec 19 14:58:45 UTC 2007, "id"=>1, "created_at"=>Wed Dec 19 14:58:45 UTC 2007}>


So that sets up a WEBrick server with a resource scaffold, routed as /blahs, and accesses it remotely using ActiveResource to post to /blahs.xml and return the new Blah object. You can also fire up the web browser and go to http://localhost:3000/blahs to see the effect.

So on a whim, I figured I'd retry installing rails with JRuby 1.0.3, but I forgot that I still had Rubygems 0.9.4 on that install, and never successfully got it upgraded, so that'll be the focus of my next post.

Tuesday, December 18, 2007

JRuby 1.0.3 on Rails 2.0.2 : Too sexy for your migrations

Wow, what a week. First we get a new stable JRuby release, quickly followed by the big whopper Rails 2.0! And with an interesting twist: in the young Rails 2.0.2 update, SQLite is now the default database instead of MySQL, which means you can roll out much faster and more simply in a normal C Ruby environment. In JRuby however, the results might be unpredictable. As far as I can tell, JRuby doesn't support SQLite, but I hoped that may have changed in the meantime.

The short of it is, it hasn't changed. Even in 1.0.3, you won't succeed with jruby -S gem install sqlite3-ruby. It stinks, but oh well. You can get around it easily enough by installing activerecord-jdbc and Derby, but I was hoping for a simple "rails and roll" scenario. You can also get back to using mysql as the database with rails myapp -d mysql.

There's also the matter of getting Rails2.0.2 installed in the first place. A bunch of people reported problems with gem, which boiled down to a version incompatibility between Rubygems 0.9.4 (which most people with the problem had, and which ships with JRuby) and 0.9.5 (with which the Rails gems were built). And running gem update --system from JRuby doesn't give the expected result, because the update script attempts to exec "ruby" at some point. So now I'm not even sure if my existing C-Ruby install is stable or not, since it might have run the update there. We'll see once it comes time for me to update any production Rails code... Anyway, I had to check rel_2-0-2 out of SVN to get Rails installed. But once I did that I was able to run the rails generator out of railties with -d mysql, generate a scaffold resource, and migrate it.