Building multiplayer game using Streams (Devoxx UK 2016)

Building multiplayer game using Streams (Devoxx UK 2016)

In this talk you will learn how stream-based programming can be used to implement web frontend and multiplayer backend of the classic game: Snake.

Building dynamic applications using imperative approach tends to create lots of unmaintainable code. Stream-based programming tries to solve this problem by introducing fully declarative way of defining application logic. While using streams, you will focus on WHAT needs to be done, not HOW and WHEN.

The talk is divided into 3 parts. In the first part you will learn how to create a frontend of the Snake web game using streams as building blocks. Then, we will move to the server side and use Scala and Akka Stream library to create backend service that will allow the game to be played by multiple players. In the third part, we will discuss reactive streams and how they make asynchronous communication safe.

6f6dc1b13fd3fe35d36db3adafcb0c8e?s=128

Michał Płachta

June 09, 2016
Tweet

Transcript

  1. Devoxx UK / 09.06.2016 @miciek Building multiplayer game using Streams

    Michał Płachta
  2. Devoxx UK / 09.06.2016 @miciek Streams? • way of defining

    application logic • focused on data transformations • explicit time dependencies • declarative
  3. Devoxx UK / 09.06.2016 @miciek What’s inside? • how frontend

    web app written in streams looks like • how backend app written using streams looks like • how do Reactive Streams keep our streams safe?
  4. Devoxx UK / 09.06.2016 @miciek Meet Snake & Fruit Snake

    Fruit
  5. Devoxx UK / 09.06.2016 @miciek Our First Stream: “Ticks” stream

  6. Devoxx UK / 09.06.2016 @miciek “Directions” stream

  7. Devoxx UK / 09.06.2016 @miciek “Snake’s Head Positions” stream

  8. Devoxx UK / 09.06.2016 @miciek Stream operators: map & scan

    stream.map(f) • transforms values from stream using function f • returns a new stream with transformed values stream.scan(init, f) • accumulates values from stream using function f ◦ f(acc,val)=>new_acc • returns a new stream that outputs accumulator value
  9. Devoxx UK / 09.06.2016 @miciek Once again: the code

  10. Devoxx UK / 09.06.2016 @miciek Displaying values from the stream

  11. Devoxx UK / 09.06.2016 @miciek Streams are reusable

  12. Devoxx UK / 09.06.2016 @miciek Streams & Operators are lazy

    No elements or No terminal function
  13. Devoxx UK / 09.06.2016 @miciek “Key presses” stream

  14. Devoxx UK / 09.06.2016 @miciek “Left Key Presses” stream

  15. Devoxx UK / 09.06.2016 @miciek “Right Key Presses” stream

  16. Devoxx UK / 09.06.2016 @miciek Stream operators: filter stream.filter(p) •

    checks each value from stream against the predicate passed as p • returns a new stream that outputs only values that satisfy condition p Already used: • map(f) • scan(init, f(acc, val))
  17. Devoxx UK / 09.06.2016 @miciek “Left Rotations” stream

  18. Devoxx UK / 09.06.2016 @miciek “Right Rotations” stream

  19. Devoxx UK / 09.06.2016 @miciek “Actions” stream

  20. Devoxx UK / 09.06.2016 @miciek Stream operators: merge stream.merge(stream2) •

    stream is merged with stream2 • returns a new stream that outputs values from both stream and stream2 as they appear Already used: • map(f) • scan(init, f(acc, val)) • filter(p)
  21. Devoxx UK / 09.06.2016 @miciek New “Directions” stream based on

    user input
  22. Devoxx UK / 09.06.2016 @miciek Recap: Old “Directions” stream

  23. Devoxx UK / 09.06.2016 @miciek “Directions” stream: Better, but no

    timing! vs
  24. Devoxx UK / 09.06.2016 @miciek Stream operators: sampledBy stream.sampledBy(stream2) •

    last value from stream is taken when any value appears in stream2 • returns a new stream that outputs values from stream sampled by values from stream2 Already used: • map(f) • scan(init, f(acc, val)) • filter(p) • merge(stream2)
  25. Devoxx UK / 09.06.2016 @miciek “Directions” stream: user input &

    timing
  26. Devoxx UK / 09.06.2016 @miciek “Snake Head Positions”: no change

    needed
  27. Devoxx UK / 09.06.2016 @miciek User moves the snake: the

    code
  28. Devoxx UK / 09.06.2016 @miciek Current implementation • “ticks” •

    “key presses” • “left key presses” • “right key presses” • “left rotations” • “right rotations” • “actions” • “dir changes” • “directions” • “snake head positions” + map, scan, filter, merge & sampledBy 10 streams
  29. Devoxx UK / 09.06.2016 @miciek “Snakes” stream

  30. Devoxx UK / 09.06.2016 @miciek Stream operators: slidingWindow stream.slidingWindow(n) •

    aggregate last n values • returns a new stream that outputs arrays of last n values from the stream • can be implemented using scan Already used: • map(f) • scan(init, f(acc, val)) • filter(p) • merge(stream2) • sampledBy(stream2)
  31. Devoxx UK / 09.06.2016 @miciek Quiz: Eating a fruit? using

    6 operators • map(f) • scan(init, f(acc, val)) • filter(p) • merge(stream2) • sampledBy(stream2) • slidingWindow(n) & 11 streams • “ticks” • “key presses” • “left key presses” • “right key presses” • “left rotations” • “right rotations” • “actions” • “direction changes” • “directions” • “snake head positions” • “snakes”
  32. Devoxx UK / 09.06.2016 @miciek “Fruit eaten events” stream

  33. Devoxx UK / 09.06.2016 @miciek Frontend in action!

  34. Devoxx UK / 09.06.2016 @miciek Architecture: 11 streams, 2 ins

    & 2 outs
  35. Devoxx UK / 09.06.2016 @miciek Snake’s going multiplayer

  36. Devoxx UK / 09.06.2016 @miciek Snake Server App Requirements •

    client must send snake positions to the server each time it changes • server must generate fruits • server must keep scores • server must broadcast: ◦ positions of all snakes to other players ◦ positions of fruits to all players ◦ current scoreboard to all players
  37. Devoxx UK / 09.06.2016 @miciek Let’s design Server App Flow!

    one player flow Game Event
  38. Devoxx UK / 09.06.2016 @miciek WebSocket connected to the Flow

  39. Devoxx UK / 09.06.2016 @miciek WebSocket as a stream: Server

    App
  40. Devoxx UK / 09.06.2016 @miciek Snake App Flow (one player)

  41. Devoxx UK / 09.06.2016 @miciek Let’s implement Server Flow for

    one player
  42. Devoxx UK / 09.06.2016 @miciek Moved to server: “Fruit eaten

    events” stream
  43. Devoxx UK / 09.06.2016 @miciek “Fruits” stream

  44. Devoxx UK / 09.06.2016 @miciek “Fruits” stream using Akka Stream

  45. Devoxx UK / 09.06.2016 @miciek “Score updates” stream

  46. Devoxx UK / 09.06.2016 @miciek “Scores” stream

  47. Devoxx UK / 09.06.2016 @miciek “Scores” stream using Akka Stream

  48. Devoxx UK / 09.06.2016 @miciek Game Logic Server Flow

  49. Devoxx UK / 09.06.2016 @miciek Game Logic Flow using Akka

    Stream
  50. Devoxx UK / 09.06.2016 @miciek Merging Player Flows outputs

  51. Devoxx UK / 09.06.2016 @miciek Server app flow using Akka

    Stream
  52. Devoxx UK / 09.06.2016 @miciek Backend in action!

  53. Devoxx UK / 09.06.2016 @miciek What if there are thousands

    of players?
  54. Devoxx UK / 09.06.2016 @miciek Crossing Async Boundary Push Model

    • not safe when Subscriber slower • perfect when Subscriber faster • too slow when Subscriber faster • perfect when Subscriber slower Pull Model
  55. Devoxx UK / 09.06.2016 @miciek Reactive Streams Streams with supply

    & demand • “as fast as possible, but not faster” • consumer wants to pull elements (= demand) • only then publisher can push ◦ it supplies elements until demand is satisfied
  56. Devoxx UK / 09.06.2016 @miciek Reactive Streams Lack of demand

    = Backpressure
  57. Devoxx UK / 09.06.2016 @miciek Example of Reactive Streams

  58. Devoxx UK / 09.06.2016 @miciek Reactive Streams

  59. Devoxx UK / 09.06.2016 @miciek Reactive Streams API public interface

    Publisher<T> { public void subscribe(Subscriber<? super T> s); } public interface Subscriber<T> { public void onSubscribe(Subscription s); public void onNext(T t); public void onError(Throwable t); public void onComplete(); } public interface Subscription { public void request(long n); public void cancel(); } public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {}
  60. Devoxx UK / 09.06.2016 @miciek Simple API, hard implementation... https://github.com/reactive-streams/reactive-streams-jvm/blob/v1.0.0/README.md#specification

  61. Devoxx UK / 09.06.2016 @miciek Reactive Streams API implementations •

    Project Reactor • Ratpack • Vert.x • Slick • Akka Stream • JDK 9 • Integrations: ◦ Kafka, ◦ Mongo, ◦ Cassandra
  62. Devoxx UK / 09.06.2016 @miciek Akka Stream model Publisher =>

    Source Processor => Flow Subscriber => Sink Our Snake implementation uses Reactive Streams!
  63. Devoxx UK / 09.06.2016 @miciek • NO ◦ frontend is

    pushing every 100ms • Possible solutions: ◦ Reactive Streams web frameworks? ◦ conflate/expand? ◦ buffer between frontend & backend? Is Snake Multiplayer safe?
  64. Devoxx UK / 09.06.2016 @miciek Links Build YOUR OWN Snake

    using Streams - blog post (step-by-step tutorial) michalplachta.com/2016/05/11/reactive-ui-by-example/ Client Side Code github.com/miciek/web-snake-react-bacon Server Side Code github.com/miciek/snake-multiplayer-akka-streams Play with streams and operators! rxmarbles.com/ Akka Streams doc.akka.io/docs/akka/2.4.4/scala/stream/ BaconJS - streams in JavaScript baconjs.github.io/
  65. Devoxx UK / 09.06.2016 @miciek Building Snake using Streams Michał

    Płachta THANK YOU www.michalplachta.com