After working with entity systems for about year, I have an idiosyncratic way of defining any of my components. Keep in mind that components are just dumb data objects, not proper OO-objects, and I'm roughly following Adam Martin's approach. Here's the way I would define a position component for two-dimensional floating-point space:
public class Position {
private float x;
private float y;
public float x() { return x; }
public float y() { return y; }
public Position x(float x) { this.x=x; return this; }
public Position y(float y) { this.y=y; return this; }
}
I've skipped documentation for the sake of brevity. I like to keep my data private, even though it's exposed through accessors and mutators—I just can't bring myself to make public instance fields in Java. The naming convention for accessors and mutators is a Smalltalk approach, using the same name for both accessor and mutator, which can lean designs towards fluent API design instead of pushbutton (see Fowler's DSL book for more on this). The mutator returns
code
to promote fluent API design as well, to permit code like the following.
Position p = new Position().x(10).y(15);
Now if I'm feeling really spiffy, I'll use a builder:
Position p = new Position.Builder().x(10).y(15).build();
but that's not my point.
My point is that this is almost all boilerplate code. All I really want to say is that there's a component called
Position
and it has two float
s, x
and y
. So maybe what I really want to be able to write is something like this:Component.build :Position do
float :x
float :y
end
The title of the post gives it away, but this is a little Ruby DSL that I hacked together this afternoon, just to see if I could. I've been meaning to learn Ruby for some time. I have read quite a bit about Ruby and its use in DSLs, but never actually created one. This time around, I used Jonathan Magen's tutorial as a reference.
The
Component
module is the starting point, and it uses what I take to be a fairly standard approach, sending blocks of code from the context to be executed within the object context:module Component
def self.build(name, &block)
builder = ComponentBuilder.new(name)
builder.instance_eval(&block)
builder.build()
return builder
end
end
The one custom piece is the
build
method on the ComponentBuilder
. What this does (perhaps not clearly indicated by the name, in retrospect) is tell the constructed object to dump out a Java source file based on the content of the block. The ComponentBuilder
looks like this:class ComponentBuilder
def initialize(name)
@name = name
@floats = []
end
def float(x)
@floats << x
end
def build
File.open(@name.to_s + ".java", "w") do |theFile|
theFile.syswrite("public class #{@name.to_s} implements Component {\n")
unless @floats.nil?
@floats.each do |var|
theFile.syswrite <<BLOCK
private float #{var};
public float #{var}() { return #{var}; }
public #{@name.to_s} #{var}(float #{var}) {
this.#{var} = #{var};
return this;
}
BLOCK
end
end
theFile.syswrite("}")
end
end
end
Here's a very quick walkthrough for those readers who do not know Ruby. The
initialize
method is the constructor, and it's setting the instance variable @name
to the argument's value and initializing an empty array for @floats
. The next method is called float
, which is not a reserved word in Ruby, and it appends its argument to the @floats
array. The build
method opens the appropriately-named file, writes the class declaration, and then iterates through the @floats
field, dumping out the field, accessor, and mutator definition for each one.There were two specific pieces of Ruby syntax that I had to learn to make this work: here documents and expression substitution in strings. The oddly named "here documents" are multiline string literals. In my program, they run from
<<BLOCK
to the recurrence of BLOCK
. This is a really useful feature for a program that is essentially filling templates and dumping them out. C# has something similar, but Java—my usual production language— does not. Note that you can do it with Groovy, though it appears the proposal to put this in 1.7 hasn't been approved. Regardless, expression substitution in strings is simple and elegant: within a string literal, put an expression within #{...}
, and it is evaluated and interpolated into the string at runtime.In order to make my program a bit more robust, I added configuration information to control the Java package and path to the destination as well as per-class and per-field embedded Javadoc, passed as optional parameters (via variable-length argument lists). The biggest problem I had with this was creating directories automatically from relative paths, but in the end, I pulled it off with a little custom method shown below.
def defensive_makedir(dir)
array = dir.split('/')
accum = '.'
array.each do |partial|
accum = accum + "/" + partial
unless File.directory? accum
Dir.mkdir accum
end
end
end
To integrate this into Eclipse, I wrote a simple ant build script. Honestly, I wrote this very early in the experiment, but the process of debugging Ruby through Eclipse and Ant was quite cumbersome, and once I hopped over to a console and emacs, I was able to iterate much faster. To incorporate this into the build process in Eclipse, hop over to your project preferences and check out the "Builders" section. This requires the JRuby jar to be on the build path.
<project name="FunWithComponents" default="make_components">
<property name="lib" location="${basedir}/vendor/lib"/>
<property name="jruby.jar" location="${lib}/jruby.jar"/>
<property name="ruby.src" location="${basedir}/ruby"/>
<property name="generated.src" location="${basedir}/gen"/>
<target name="make_components">
<java jar="${jruby.jar}" fork="true" dir="${ruby.src}">
<arg value="${ruby.src}/make_components.rb"/>
</java>
</target>
</project>
I enjoyed working on this and learned a bit about Ruby, both in terms of the language and how to express myself within it. I acknowledge that I do so much work in Java, it can be hard to switch paradigms or idioms, and so it's good to actually make something useful from time to time. However, it also makes me wonder if all that trouble is actually worth it when I can immediately imagine how to support a Java-based solution like the following.
public static void main(String[] args) {
ComponentFactory.instance().build("Position")//
.withFloat("x")//
.withFloat("y")//
.build();
}
very cool. I too now that I've almost completed the component breakdown on the Java side with my efforts am looking toward various JVM languages. I'll likely mostly be focusing on Scala first due to the type-safe & joint compilation aspects as that will work well for Android / J2SE cross-platform concerns. I suppose this being a move towards a firm component-functional direction emphasizing COP over OOP except for a bit of inheritance between components and the listener pattern for event distro and timing / clocking callbacks. Should be interesting to see what entity system goodness Scala can reveal on the DSL level or otherwise. IE should be possible to produce a Scala based clock source for anonymous callbacks. I'm very excited to start working with Scala as that'll be some fresh territory after 15 years of mostly Java. Of course I'll get my Groovy on via Gradle integration on the build side of things.
ReplyDelete