Posted: 05 April 2012
I was browsing the source of the newest libGDX update, and came across
RemoteInput.java.
It looks like this was
added in early 2011, but its
totally new to me. Anyway, its a very slick little pair of tools that
lets you run a proxy on your phone and forward that phone's events to your
libGDX app on your desktop. This lets you maintain the very fast
edit/compile/crash cycle on your desktop without losing the ability to
test phone-only events like compass, accelerometer and multi-touch.
Anyway, I don't (yet) want it for all that. I've been thinking about how
to automate some testing of my libGDX app, and realized that I could use
this approach to save and playback input events. At first, I thought I
might hack the remote end to read events and send them over a loopback
network, but I quickly realized it would be simpler to just build
something similar to RemoteInput.java
that injects events by reading
them from a file (instead of reading them off the network).
Both RemoteInput.java
and my new InputGenerator.java
work on the same
handy property of libGDX, which lets you replace the input management
system trivially. Just create a new input manager and stuff it in as the
default.
fakeInput = new InputGenerator(new StringReader("1\n0 99\n1 99\n1000\n")); Gdx.input = fakeInput;
You need a bunch of code in your new input manager to maintain the (fake) input
system's state. Thankfully Mario wrote all that in RemoteInput.java
and
it was easy to crib from.
Instead of the current binary format used to send events across the network, I chose a very simplistic text format for the saved event logs:
<event> <eventArgs ...>
where every element is just an integer. The file format isn't very readable, but its reasonable to edit it or fix it manually if necessary.
To generate an event log, I added some log statements to my existing
InputProcessor
that write the events in the above format to a file.
This is an easy way to record a session for later playback. (Or as a
basis for manually editing the file.)
Since I just record absolute touch event coordinates, the screen at replay time needs to match that at recording time. This should be possible to enforce (or work around by recording and replaying screen size changes, or scaling the recorded events), but I haven't implemented any of that yet. It should also be possible to playback events that only make sense on a physical device (like accelerometer or compass events), but I haven't needed that yet. Android keys like MENU and BACK are also currently ignored.
Other improvements I'd like to make are entries in the file that change the inter-event delay (currently hardcoded to 7ms). Entries that repeat an event N times (to simulate hammering the system). A tool that generates random streams of "interesting" valid events as stress testing. And better versioning so I know when the run-time system is too "different" from the environment that recorded the events.
On the implementation side, instead of the relatively straight-forward
"giant case statement" approach that RemoteInput.java
uses for encoding
and decoding events, I thought I'd try a more "OO" approach. That was a
mistake. It took about four times as much code to express the same work
(of converting a list of integers describing an event into changes in the
event state and callbacks to the registered InputProcessor). On the other
hand, using java.util.Scanner
to parse the file worked out very nicely.
The code is currently bound up with a bunch of stuff specific to my
project (especially my logging system), so I need to extract that before
sharing the code. I should probably refactor RemoteInput.java
and build
this on/under that code, too.