Forum: JRuby Overriding A Method For A Specific Instance

F4a55256d7dbb04f2a0287c1c64b38ea?d=identicon&s=25 Michael Chapman (kabbotta)
on 2014-05-19 04:36
I'm using JRuby with the libGDX framework, and I've gotten stuck trying
to setup some InputListeners that worked just fine when I was using
Java.

In Java, the process looks like this:

    actor.addListener(new InputListener() {
        public boolean touchDown (
            InputEvent event, float x, float y, int pointer, int button)
{
                System.out.println("working");
            });

where the touchDown method is overidden as it is instantiated.

In Ruby, I've tried to use:

    listener = InputListener.new
    def listener.touchDown(event, x, y, pointer, button)
      puts 'working'
    end

    @inv_button.add_listener(listener)

and all of its variants like class << listener def...end, and I've got
nothing. I have got it to respond using:

    @inv_button.add_listener(
        Class.new(InputListener) do
            def touchDown(event, x, y, pointer, button)
                puts 'working'
            end
        end.new
    end

But the problem is that I'm now stuck in the scope of the InputListener
class when redefining touchDown so I can't use it to setup the listener
as is intended. I don't understand why this isn't a problem with the
Java method. If I could pass something along to the new class in a proc
that would work, but I can't figure out how to do that.

Any suggestions to try a completely different method are appreciated as
well.
7ad5f543b9592aa423771338c057d7e5?d=identicon&s=25 Gergely Nagy (Guest)
on 2014-05-19 04:57
(Received via mailing list)
There you're just passing a (n inputlistener sub)class to that
addListener().
In the Java, you instead create an anonymous inner class AND instantiate
it.. that latter part is missing in your ruby snippet.
So the smallest change is to simply call new():
  @inv_button.add_listener(
        Class.new(InputListener) do
            def touchDown(event, x, y, pointer, button)
                puts 'working'
            end
        end.new
    end

but that's not pretty.. Some(maybe me) sure will come back a nicer
alternative.
Good luck.
F4a55256d7dbb04f2a0287c1c64b38ea?d=identicon&s=25 Michael Chapman (kabbotta)
on 2014-05-19 05:04
Gergely Nagy wrote in post #1146420:
> There you're just passing a (n inputlistener sub)class to that
> addListener().
> In the Java, you instead create an anonymous inner class AND instantiate
> it.. that latter part is missing in your ruby snippet.
> So the smallest change is to simply call new():
>   @inv_button.add_listener(
>         Class.new(InputListener) do
>             def touchDown(event, x, y, pointer, button)
>                 puts 'working'
>             end
>         end.new
>     end
>
> but that's not pretty.. Some(maybe me) sure will come back a nicer
> alternative.
> Good luck.

Thanks. That looks like my second example, which does respond, but the
problem is that I don't know how to pass arguments in to the overridden
definition. So it's out of scope and I can't reach any of the data I
need to setup the method. But even then, I'd have to write a subclass of
InputListener that takes custom arguments. I think. Which seems really
ugly compared to the java.
486ca04f06d968004643ce5b47376ded?d=identicon&s=25 Keith B. (keith_b)
on 2014-05-19 05:43
(Received via mailing list)
Is InputListener a Java interface (rather than a class)?  If so, I think
that's the source of the issue.  In Java, your anonymous class
implements
the interface.  Ruby has no analog to Java interfaces, nor does it have
anonymous classes (although instances may resemble them a little since
one
can add methods to them, as you have done).

So it would make sense to me to have to create a custom Ruby class.
There
is a section on implementing Java interfaces in the "Using JRuby" book
with
some conveniences.  Gotta go now though...
F4a55256d7dbb04f2a0287c1c64b38ea?d=identicon&s=25 Michael Chapman (kabbotta)
on 2014-05-19 05:51
InputListener is a class that implements an interface. I don't know if
that matters.

This does work:

    @inv_button.add_listener(
        Class.new(InputListener) do
            def touchDown(event, x, y, pointer, button)
                puts 'working'
            end
        end.new
    )

But I'm stuck in the scope of InputListener then and I have no access to
the data I need to setup. I've tried to do this:

    @inv_button.add_listener(
        Class.new(Listener) do
            def touchDown(event, x, y, pointer, button)
                puts 'working'
            end
        end.new(@mgr)
    )

Where Listener is a class that extends InputListener, but has an
argument passed to its initialize method, but then it stops working and
no longer prints out working. It gives no error though. I've also tried
to have Listener implement EventListener using the JRuby method
java_implements EventListener, which is the interface that
InputListener implements, but it still ignores it.

Here are the specs for InputListener:
http://libgdx.badlogicgames.com/nightlies/docs/api...

and TextButton, the class with the add_listener method:
http://libgdx.badlogicgames.com/nightlies/docs/api...
C983ad33b47479f17a28e083e1bb3d8b?d=identicon&s=25 Eric West (edubkendo)
on 2014-05-19 06:11
(Received via mailing list)
Michael, The simplest and most idiomatic way to do this with JRuby is to
pass in a block which will get used as the method being over-riden on
the
InputListener object:

@inv_button.add_listener() do |event, x, y, pointer, button|
  puts 'working'end

I’ve written about all this in a pretty thorough blogpost at: Ruby,
Duck-Typing, Java, Interfaces and JRuby <http://nodehed.com/?p=127> .
It’s
a bit long, but I think you will find it very helpful.
​
F4a55256d7dbb04f2a0287c1c64b38ea?d=identicon&s=25 Michael Chapman (kabbotta)
on 2014-05-19 06:21
Eric West wrote in post #1146427:
> Michael, The simplest and most idiomatic way to do this with JRuby is to
> pass in a block which will get used as the method being over-riden on
> the
> InputListener object:
>
> @inv_button.add_listener() do |event, x, y, pointer, button|
>   puts 'working'end
>
> I’ve written about all this in a pretty thorough blogpost at: Ruby,
> Duck-Typing, Java, Interfaces and JRuby <http://nodehed.com/?p=127> .
> It’s
> a bit long, but I think you will find it very helpful.
> ​

Thanks, Eric. I'll spend some time reading your article. In your example
using the block, what happened to the reference to the actual method I'm
trying override? How will it know the block is intended for the
touchDown method of the InputListener?
C983ad33b47479f17a28e083e1bb3d8b?d=identicon&s=25 Eric West (edubkendo)
on 2014-05-19 06:55
(Received via mailing list)
Typically these listeners are written to accept an interface which
defines
a single method, so the block we pass to the method is used as the
implementation of that method. If the interface defined multiple
methods,
then passing a block might not work. I'm actually unsure.  I don't think
I've ever run into that though.  The single-method interface is just
such a
common pattern for this situation.  So even if the class implementing
the
interface also implements multiple other methods, usually the interface
the
listener is looking for only defines one, and jruby has all this
information at run time so its easy for it to pass what is needed.
F4a55256d7dbb04f2a0287c1c64b38ea?d=identicon&s=25 Michael Chapman (kabbotta)
on 2014-05-19 07:05
Ahh, I see. Yeah, this input listener is written to catch all of the
input events like touchDown, touchUp, keyDown, keyUp in separate
methods. There is a subclass of InputListener called ClickListener that
only has a single click method. I could try and set up the block with
that listener, but there are not separate interfaces for all of the
other input events. I'd still need a solution for the key and touch
events.
C983ad33b47479f17a28e083e1bb3d8b?d=identicon&s=25 Eric West (edubkendo)
on 2014-05-19 07:10
(Received via mailing list)
Yeah, your best bet in this situation , having looked at the docs you
linked, is write a Ruby class which inherits from InputListener and
implements all the methods your interested in, then pass an instance of
that.  My guess is that, if you pass a block, the block will be treated
as
an implementation of "handle" which I would wager would then get called
in
the event of any of the events, but yeah, if you need different
behaviors
for each of those, which I'm sure you do, writing a class which
implements
the needed methods is just the cleanest/most straightforward way to do
it.
F4a55256d7dbb04f2a0287c1c64b38ea?d=identicon&s=25 Michael Chapman (kabbotta)
on 2014-05-19 07:35
Thanks for all your help. I've been trying to do that, and I must be
missing something still. I have inherited from InputListener like this:

    class Listener < InputListener
      def initialize(mgr)
        super()
        @mgr = mgr
      end

      def keyDown(event, keycode)
        puts keycode
      end
    end

And then set it up like this:

    @inv_button.add_listener(Listener.new(@mgr))

And there is no error and no response from the listener.

It does work when I use the Class.new method:

    @inv_button.add_listener(
        Class.new(InputListener) do
            def touchDown(event, x, y, pointer, button)
                puts 'working'
            end
        end.new)

But the problem is that I'm stuck in the scope of InputListener and I
don't know how to pass anything into it, because this strategy does not
work when I use a Ruby class I inherited from InputListener which takes
arguments to initialize. It just ignores touchDown events again.

I'm rewriting this game, and it was originally written in JRuby as well,
and I swear I got this to work using something like this:

    listener = InputListener.new

    def listener.touchDown(event, x, y, pointer, button)
        puts 'working'
    end

    @inv_button.add_listener(listener)

Where the definition of touchDown was reachable from the calling script,
but something must be different this time.
486ca04f06d968004643ce5b47376ded?d=identicon&s=25 Keith B. (keith_b)
on 2014-05-19 17:31
(Received via mailing list)
Does the InputListener's constructor require any arguments?  It looks
like
not.  I'd suggest experimenting with trying super without the
parentheses
and omitting super altogether.  I think I remember issues regarding
super
in some situations.
C983ad33b47479f17a28e083e1bb3d8b?d=identicon&s=25 Eric West (edubkendo)
on 2014-05-19 20:23
(Received via mailing list)
@michael So there’s some weirdness happening here I think. I believe
your
issue may be that your intialize method takes an arg, whereas the
original
constructor doesn’t. There was a bug related to this at one point, when
subclassing Java classes and maybe its still around. Other potential
gotchas I’m seeing is wrt naming. Listener is definitely common enough
you
could be hitting some kind of collision there, so I’d either namespace
it
or modify it a little. Then, try using the no parens version of super in
your initializer. Also, you might try the fully qualified name for
InputListener, so:

require "java"
class GameListener <
Java::com.badlogic.gdx.scenes.scene2d::InputListener
  attr_accessor :mgr

  def initialize()
    super
  end

  def keyDown(event, keycode)
    puts keycode
  endend

listener = GameListener.new
listener.mgr = "manager" #pass whatever object you need
here.@inv_button.add_listener(listener)

Finally, if that doesn’t work my last suggestion would be to try calling
super inside the handler methods, so keyDown in this instance.
​
7ad5f543b9592aa423771338c057d7e5?d=identicon&s=25 Gergely Nagy (Guest)
on 2014-05-19 21:06
(Received via mailing list)
I don't see how the straightforward Listener subclassing doesn't work...
(btw you're consistently referring to touchDown() in the case it does
work,
and keyDown() where it does not... but I guess it's not as simple as
that
;-)

About passing parameter to the inline class - have you tried to just add
a
constructor?

   outer_mgr = @mgr
   add_listener(Class.new(InputListener) do
               def initialize(mgr)
                 @mgr = mgr
               end
               def touchDown(event, x, y, pointer, button)
                 puts "touchdown working #{@mgr}"
               end
             end.new(outer_mgr))

Another way (bit more magical):

   outer_mgr = @mgr
   add_listener(
             Class.new(InputListener) do
               define_method :touchDown do |event, x, y, pointer,
button|
                 puts "touchdown working #{outer_mgr}"
               end
             end.new
    )

And BTW, some "macro" weirdness:
    outer_mgr = @mgr
    add_listener(
              listener_for(:touchDown) do |event, x, y, pointer, button|
                            puts "touchdown working #{outer_mgr}"
              end)

#where:
   def listener_for(method, &block)
     Class.new(InputListener) do
       define_method(method, &block)
     end.new
   end
# etc...
There must be other, possibly cleaner ways (again the plain class should
be
fine), just checkin if we are on track.
HTH
F4a55256d7dbb04f2a0287c1c64b38ea?d=identicon&s=25 Michael Chapman (kabbotta)
on 2014-05-20 01:16
Great thinking, Nagy! Passing the constructor worked when combined with
the other suggestions to play around with calling super(), then I got
the macro solution to work like this:

    @inv_button.add_listener(
        listener_for(:touchDown) do |event, x, y, pointer, button|
            puts @mgr
        end)

    def listener_for(method, &block)
        Class.new(InputListener) do
            define_method(method, &block)

            def initialize(mgr)
                super()
                @mgr = mgr
            end
        end.new(@mgr)
    end


The button now responds and I have access to @mgr. Without the call to
super() it still failed. I'm definitely still interested in sleeker
solutions, but at least I don't have to create a custom class and can do
it all on the fly the way libGDX intended. Plus, after I setup 3 or 4
buttons, the Ruby way will already be shorter than the Java way.

You've all been extremely helpful. Thanks!
F4a55256d7dbb04f2a0287c1c64b38ea?d=identicon&s=25 Michael Chapman (kabbotta)
on 2014-05-20 04:44
I've realized that I still can't override more than one method on a
listener. Even if I call listener_for(...) every time and create a new
listener for each method, which seems excessive, it only responds to the
first listener on the button (libGDX does allow multiple listeners on an
actor). This is what I've been trying:

    @inv_listener = listener_for(:touchDown) do |event, x, y, pointer,
button|
        puts "starboard"
        true
    end


    @inv_listener.create_method(:touchUp) do |event, x, y, pointer,
button|
       puts "stankboard"
       true
    end


    def listener_for(method, &block)

        Class.new(InputListener) do

            define_method(method, &block)

            def initialize(mgr)
                super()
                @mgr = mgr
            end

            def create_method(name, &inner_block)
                self.class.send(:define_method, name, &inner_block)
            end

        end.new(@mgr)

    end

I can see that the method is being created by calling it:

    @inv_listener.touchUp(...)

It prints "starboard". It's strange. It's almost like anything I do to
the class after it's initialization is ignored by the listener. I tried
to pass more than one block and a list of method names, but I couldn't
figure out how to do it. I also tried calling super in the new methods
block.
F4a55256d7dbb04f2a0287c1c64b38ea?d=identicon&s=25 Michael Chapman (kabbotta)
on 2014-05-20 05:20
This completely works, but is a bit cumbersome. Both touchDown and enter
now respond. Any insights are appreciated.

    @inv_button.add_listener(
        Class.new(InputListener) do
            def initialize(mgr)
                super()
                @mgr = mgr
            end

            def touchDown(event, x, y, pointer, button)
                puts "touchDown"
            end

            def enter(event, x, y, pointer, fromActor)
                puts "enter"
            end
        end.new(@mgr))
486ca04f06d968004643ce5b47376ded?d=identicon&s=25 Keith B. (keith_b)
on 2014-05-20 14:14
(Received via mailing list)
In my opinion, it's clearer to have all the event handlers in one class
(as you now have it) rather than having a separate class for each
listener method.  I would probably even prefer an explicitly defined
class (could be an inner class), rather than the dynamically created
class.  I think there would be less cognitive effort required to parse
and understand it. But this did not work for you, right?  Second best
might be to encapsulate the class definition in its own method, assuming
you have other things going on in the method in which you call
@inv_button.add_listener.
7ad5f543b9592aa423771338c057d7e5?d=identicon&s=25 Gergely Nagy (Guest)
on 2014-05-20 20:39
(Received via mailing list)
>
> . Even if I call listener_for(...) every time and create a new
> listener for each method, which seems excessive,

may be, but  that's what your original java code did (new anonymous
classes
for each addListener call).


> it only responds to the first listener on the button.


So you're creating a new separate anonymous class each time, and it only
notifies the first one?
Haven't read the spec, are you sure multiple listeners are supported?
then
(of course that would be expected). Otherwise I'd fine it extremely
strange.

I can see that the method is being created by calling it:
>     @inv_listener.touchUp(...)
> It prints "starboard". It's strange


Well, it prints "skankboard" for me. Just went ahead and tried it
myself,
took your snippet, fixed up (basically moved the method defs above their
invocations - BTW I hope you didn't paste it directly because than I
would
totally be puzzled why it didn't throw an error).
Anyway, I ran it standalone using Jruby 1.7.9:   Works For Me.
Please use this gist to re-test it:
https://gist.github.com/inger/07a1ce0973b41bdc3946...
What Jruby version are you on?

BTW, I don't actually see what you want to do with that create_method
magic
there  - to dynamically add a new method to an already dynamically
created
class?

My idea behind that Class.new stuff (per listener) is to emulate to java
code, making it a more or less straight port.  However IMHO that create
method magic is definitely excessive and defeats the purpose.
(again, I do agree with the guys that cleaner solutions should work -
but
i'd suggest to nail down first, iow
MakeItWorkMakeItRightMakeItFast<http://c2.com/cgi/wiki?MakeItWorkMakeItRightMakeItFast>
)

HTH
F4a55256d7dbb04f2a0287c1c64b38ea?d=identicon&s=25 Michael Chapman (kabbotta)
on 2014-05-20 23:31
>Well, it prints "skankboard" for me.

Sorry, that was a typo. It also printed "stankboard" for me when I
manually called the method after creating it. I could use the method and
see that it was added to the class, but the listener ignored it whenever
I used my own subclass of InputListener.

>may be, but  that's what your original java code did (new anonymous
>classes

I meant creating a new anonymous class for every *method* that needed to
be added. The only reason I was using the whole create_method thing was
to try and get your macro version working with more than one overridden
method. The way you suggested listener_for had only one block and one
method being defined. I needed to be able to override more than one at a
time. Like touchUp and touchDown. I just couldn't figure out how to pass
in multiple method names and blocks. So I tried to add new methods after
initially using listener_for.

Otherwise, your first method where I just setup a new class with all the
needed methods worked great, but if I could get the macro version
working for multiple methods it would be like 10 times less code. Not a
big deal though. You guys have essentially solved my problem. Thanks.

>basically moved the method defs above their invocations - BTW I hope you >didn't
>paste it directly because than I would totally be puzzled why it didn't >throw an
>error).

Is that unusual that I can define a method after it's used in a class's
script? I know in python I had to use self.method() to get that to work,
but it seems to work just fine in Ruby... For instance, I'll often use a
method in initialize that is defined later in the script. I'm only using
1.9.3.
Please log in before posting. Registration is free and takes only a minute.
Existing account

NEW: Do you have a Google/GoogleMail, Yahoo or Facebook account? No registration required!
Log in with Google account | Log in with Yahoo account | Log in with Facebook account
No account? Register here.