Duck Type Constructors for Vec2D and Vec3D
Duck Type in JRuby Extensions
Sometimes it would be nice to use duck typing to inter-convert between different vector types, that seem to proliferate in the processing environment. It is for this reason I’m experimenting with duck typing in Vec2D and Vec3D constructors:-
Vec2D
/**
*
* @param context ThreadContext
* @param klazz IRubyObject
* @param args optional (no args jx = 0, jy = 0)
* @return new Vec2 object (ruby)
*/
@JRubyMethod(name = "new", meta = true, rest = true)
public static final IRubyObject rbNew(ThreadContext context, IRubyObject klazz, IRubyObject[] args) {
Vec2 vec2 = (Vec2) ((RubyClass) klazz).allocate();
vec2.init(context, args);
return vec2;
}
/**
*
* @param runtime Ruby
* @param klass RubyClass
*/
public Vec2(Ruby runtime, RubyClass klass) {
super(runtime, klass);
}
void init(ThreadContext context, IRubyObject[] args) {
int count = Arity.checkArgumentCount(context.runtime, args, Arity.OPTIONAL.getValue(), 2);
if (count == 2) {
jx = (args[0] instanceof RubyFloat) ? ((RubyFloat) args[0]).getValue() : ((RubyFixnum) args[0]).getDoubleValue();
jy = (args[1] instanceof RubyFloat) ? ((RubyFloat) args[1]).getValue() : ((RubyFixnum) args[1]).getDoubleValue();
} // allow ruby ducktyping in constructor
if (count == 1) {
if (!(args[0].respondsTo("x"))) {throw context.runtime.newTypeError(args[0].getType() + " doesn't respond_to :x & :y");}
jx = ((args[0].callMethod(context, "x")) instanceof RubyFloat)
? ((RubyFloat) args[0].callMethod(context, "x")).getValue() : ((RubyFixnum) args[0].callMethod(context, "x")).getDoubleValue();
jy = ((args[0].callMethod(context, "y")) instanceof RubyFloat)
? ((RubyFloat) args[0].callMethod(context, "y")).getValue() : ((RubyFixnum) args[0].callMethod(context, "y")).getDoubleValue();
}
}
Vec3D
/**
*
* @param context ThreadContext
* @param klazz IRubyObject
* @param args optional (no args jx = 0, jy = 0, jz = 0) (2 args jz = 0)
* @return new Vec3 object (ruby)
*/
@JRubyMethod(name = "new", meta = true, rest = true)
public final static IRubyObject rbNew(ThreadContext context, IRubyObject klazz, IRubyObject[] args) {
Vec3 vec = (Vec3) ((RubyClass) klazz).allocate();
vec.init(context, args);
return vec;
}
/**
*
* @param runtime Ruby
* @param klass RubyClass
*/
public Vec3(Ruby runtime, RubyClass klass) {
super(runtime, klass);
}
void init(ThreadContext context, IRubyObject[] args) {
int count = Arity.checkArgumentCount(context.runtime, args, Arity.OPTIONAL.getValue(), 3);
if (count >= 2) {
jx = (args[0] instanceof RubyFloat)
? ((RubyFloat) args[0]).getValue() : ((RubyFixnum) args[0]).getDoubleValue();
jy = (args[1] instanceof RubyFloat)
? ((RubyFloat) args[1]).getValue() : ((RubyFixnum) args[1]).getDoubleValue();
}
if (count == 3) {
jz = (args[2] instanceof RubyFloat)
? ((RubyFloat) args[2]).getValue() : ((RubyFixnum) args[2]).getDoubleValue();
} // allow ruby ducktyping in constructor
if (count == 1) {
if (!(args[0].respondsTo("x"))) {throw context.runtime.newTypeError(args[0].getType() + " doesn't respond_to :x & :y");}
jx = ((args[0].callMethod(context, "x")) instanceof RubyFloat)
? ((RubyFloat) args[0].callMethod(context, "x")).getValue() : ((RubyFixnum) args[0].callMethod(context, "x")).getDoubleValue();
jy = ((args[0].callMethod(context, "y")) instanceof RubyFloat)
? ((RubyFloat) args[0].callMethod(context, "y")).getValue() : ((RubyFixnum) args[0].callMethod(context, "y")).getDoubleValue();
if (!(args[0].respondsTo("z"))) {return;} // allow promotion from 2D to 3D, sets jz = 0
jz = ((args[0].callMethod(context, "z")) instanceof RubyFloat) ? ((RubyFloat) args[0].callMethod(context, "z")).getValue() : ((RubyFixnum) args[0].callMethod(context, "z")).getDoubleValue();
}
}
Usage
Simple example where we create a point instance that responds to :x
and :y
but typically could be any other class that provides such methods that return either a float or fixnum.
Point = Struct.new(:x, :y)
point = Point.new(10, 10)
vec = Vec2D.new(point)