Voyaging Mind

Curious coding and more.

Input_Streams

One of the goals I am attempting to do is use the full range of language features and ideas that are contained in Dart. Streams are one area that I have not been making use of even though they have been on the fringes of what I have been coding for while.

In my little game engine I have been using callbacks for keyup and keydown detection. These built on top of the Streams that Dart provide in its core html library. Not using streams meant I had a little bit more code than necessary. For simple arcade style games having them as seperate callbacks felt like more work than it should be. Rethinking the design I decided to see if a single callback, that could handle KeyUp, KeyDown, provides a better fit.

One approach would have been to provide listeners to all input events I was listenting to and transform them to my own custom event before calling the callback. I decided to take a slightly different route and provide a Stream of custom events that you could add one or more listeners.

The custom events are needed to abstract away the fact we are running on the web platform. With so much going on in the land of dart I feel it’s worth abstracting this out.

This mean I had to write some code to transform the item in each stream streams into my own custom events before passing them on. This turned out to be easy as streams contain a map method that you can use with a transform function.

Combining Streams together also turns out to be easy if you use a StreamController. The remaining housekeeping was the ability to remove all the listeners. We use this when we, for instance, move from the title screen to the in game screen as we would not want the title screen input listeners firing during the game play.

Let’s look at the code. First we had to create the events. We deal only with KeyUp and KeyDown events at the moment.

abstract class Event {} class KeyDownEvent extends Event { KeyEvent _e; int get keyCode => _e.keyCode; KeyDownEvent(this._e); preventDefault() => _e.preventDefault(); } class KeyUpEvent extends Event { KeyEvent _e; int get keyCode => _e.keyCode; KeyUpEvent(this._e); preventDefault() => _e.preventDefault(); } // Return type indicated if callback handled the event. typedef bool EventListener(Event e);

I create a base type for events and then extend it for the KeyUp and KeyDown situations. In theory I would like users to be able to trigger events through this system but for now that is not a requirement. The preventDefault is unsatisfying as it’s existance is a leaky abstraction that tells us we are running in a browser. At the moment I don’t see an easy way hide this.

To transform a HTML keydown event we must transform the stream so we do this

keyDownStream = window.onKeyDown.map((KeyEvent e) => new KeyDownEvent(e));

Every IO stream will need a line like this.

To merge the streams we add a listeners to each stream and push the converted events into a stream controller we have created. Once we have done this for each of the IO stream we support we get stream of custom events out of the StreamController.

Supporting multiple listeners means that our new all encompassing event stream needs to be a broadcast one. Here is the whole class including the vital cancelAll method. This relies on us storing StreamSubscriptions returned when we add listeners

class EventManager { Stream inputStream; List listeners = new List(); Stream keyDownStream; Stream keyUpStream; EventManager(Window window) { keyDownStream = window.onKeyDown.map((KeyEvent e) => new KeyDownEvent(e)); keyUpStream = window.onKeyUp.map((KeyEvent e) => new KeyUpEvent(e)); var sc = new StreamController.broadcast(); keyDownStream.listen((e) => sc.add(e)); keyUpStream.listen((e) => sc.add(e)); inputStream = sc.stream; } listen(EventListener el) => listeners.add(inputStream.listen(el)); cancelAll() { listeners.forEach((e) => e.cancel()); listeners.clear(); } }

There will be one instance of the EventManager in a game and as we change between states in a game it listeners will cleared and added.

To reduce allocations I should be using factory style constructor for my custom events.

This class is about 50 lines of dart code including blank lines. It’s also easy to add other input events and possible my own Event streams should I wish to do so. Will my design stand the test of time? One can never be sure with a question like that but it feel simpiler to use compared with what came before it.


Share

comments powered by Disqus