SF Scala: Michał Płachta, "Building multiplayer game using streams"
Recording: SF Scala: Michał Płachta, "Building multiplayer game using streams"
[Music] so I dropped the last part but basically what we are going to do is to implement a multiplier web game using streams as building blocks so who knows what streams are and I'm not talking about Java eight streams yeah okay so there will be lots of code snippets and diagrams during the presentation so whenever you feel something is unclear just please ask your question let's prepare the battlefield so what are the streams there are four things I want to share with you about that so the first most important thing is that there are another way of defining application logic a different way and it's also a concurrency model so from the pro concurrency models that you can see on the slide this one is the most abstract and it's different than abstract in a way that you are focusing only on data transformations so you just have an element and then you have your piece of code basically a function we just transforms it into a different element and this helps us the programmers fight with the most our our the worst an enemy this is the time so whenever you programming using streams what you what you focus on is not what just happened or what will happen next you are focusing not on what is happening right now but what happens right now and this way we can we can say that the time is explicitly specified inside the stream so what you are seeing here is the diagram I will use throughout the presentation so basically time flows from left to right and those circles are the elements and so the right outmost element is is the oldest one this all means that we are becoming declarative programmers so not functional yet but but declarative right so we are programming using descriptions of transformations not the prescriptions of the whole algorithm like in imperative world so where we are more sane in this way because we are using pure functions more frequently so what's inside this talk I will just explain the concepts we came behind fronting application the code itself or the longer version of the talk includes some JavaScript JavaScript snippet of the real implementation because it's very very short and concise but right now we are going to go through the concepts alone and then we move to the scale-up part which is a back-end server for the multiplayer snake game and yeah we'll go through concept and code and then there will be live demo ok let's meet the main characters for today though that's the great thing is aboard the the green square right there is a snake just one Square and fruit is a in the green Green Square and this is our first stream so this is a stream of empty objects which are output each every 100 milliseconds so yeah this is called thick stream so this is our first stream it does just that what we can do with that is that we can create a new stream out of it and all the streams that we create our immutable so we can take this thick stream which is at the top then map or using the function which is provided and then we get a new immutable stream out of it we will call it directions stream because it will output the new direction of the snake should the direction the snake should take next and as you see we are mapping with a function that always returns vector 0 1 so what we get at the output stream is that is 0 1 vector output every 100 milliseconds we can then use this direction stream in order to create our our snake's head position stream we can use a second of six operators I want to use today and that is called scan and what scan does you probably know that but I was just explaining very quickly so it accumulates a value right using the function that we provide so this is the second line of the operator it starts with a vector 0 0 so this is like the starting position and then we accumulate the position using the just vector addition operator so every time there is a new element our new vector zero one new direction in the input stream which is Direction stream then we just accumulate it inside our new output stream and each time this accumulate accumulator changes we just output it so that buttons the bottom screen is snakes heads position cool so this is what we have right now zero one means that we always go down right and this is not the game because there is no there's no input so we can really play the game yet so this is how it looks right now so let's change it right let's introduce a new stream which will be a keypresses stream each time the user presses a button on the keyboard there's a object in the stream and it will hold a keyboard event what we can do with that is that we can filter it of course and we will create a left key press let's keep presses stream so what it does it just filters out all key presses that are now to the left key press and that leaves us with just left key process each time the user presses left key we'll have an a value here in the left key process stream so similarly similarly we can do it with the right key process and what we can do next is we can map again left left rotation stream is the stream will get after applying a map to the key process so again the top stream is whenever user process left key here's a value and then each value in this key process stream is mapped to a rotate left function so we have a stream of functions right now what it's what it allows us to do or to think about when you think about it whenever users presses a left key we have a function here that we need to use in order to transform the current direction and and here's the question is is it synchronized with the ticks no it is not because when you look at the source of all the events the source of the events is just user pressing their key the key right and this is a very good question but will in few slides it will all be solved but yeah you you you see you've seen the problem just just before I wanted it I wanted you to see it very cool thank you okay so once again every time you user presses a button we have a function rotate left function output in the output stream left rotation string similarly we can do it with right rotations and we can merge them so we can merge those two streams in order to get a new one what this new one will output is each time one of those top two input streams output the value it's also at it also appears in a in the action stream in output stream which will call action stream okay so what is action stream when you think about it deeply whenever users want to change the direction the action stream will contain a function that we need to use in order to transform the current direction so this is where we get where we got right so that's very cool stuff and yeah so this is our new direction stream the old one you when you remember was just zero one each one hundred millisecond milliseconds and this one is just yeah each time user process something the direction changes and yeah the scan is just scanning through scanning through the objects of the of the input stream with the accumulator value initial accumulative accumulator accumulator value 0 1 so 0 1 is our initial direction and then we just accumulate functions so applying to the current direction and outputting it to the output stream which is which is the down one the questions are events are sequenced so the question is about merge merging two streams and the sequence between them so when I when they are very very close and probably they are as the source of them are from like this different synchronous systems you probably don't have any sequencing but if it's one system that provides those events you probably have some sequencing but this those those kind of race conditions are not really important right right here because when user process it's so fast that the key is so fast that he'd even doesn't know about that then you know the result is not known to us so it will eventually be stable after some time right the whatever whatever user presses the pattern the user process is not important but it will stabilize over time in this game but the oh but the question is very good because in your own business logic you need to think about where when were those events coming from right excuse me I didn't get it yeah but if you're going through a maze you won't be doing you know smashing buttons randomly right so that so the race conditions will not be there so this is the only source of the events here so it's just user pressing buttons so the sequence of the buttons will eventually get there yep and another question there's no difference there's no real difference so it basically whatever the stream library you are using for example for example in lacustrine this is a this is JavaScript but in akka streams the stages are using the same thread for example by default and when you don't want them to use one thread you need to put a sink everywhere in order to put them on different threads then this is so this is done intentionally right right but but the whole one pipeline is done in one thread so there's the sequence is there so it doesn't matter whether you put everything in one Operator or you split them in different operators because it will be done sequentially in one thread and another question yep so the only guarantee yeah so the question was what with what the guaranteed delivery guaranteed do we have innaka streams comparing for yeah ordering so basically the only guarantee you get like generally speaking is that if you have if you have parallel parallel pipelines right three parallel pipelines and all those pipelines have stages you know that this stage will finish before the next one and that's all okay so that's the problem you mentioned what we need to do so the current situation is that every time user presses a button the direction changes and the snake doesn't move at all because we don't we don't really do that right we need to make ticks make ticks again used inside the inside the stream inside our streams in order for the snake to move even though plier isn't pressing any buttons right so I just change a direction and I want snake to move along this direction until I change it again so that's why we use sampled by operator with just it takes two streams as an input and the top stream is a value stream and that bottom stream is a sampling stream right it's time there is a value in sampling stream here we take the last value from the value stream and output it in in the output stream of the whole operate sampled by operator and this is our real new Direction stream and so this doesn't have this problem right now and the other thing worth mentioning is that we don't really need to change snake head positions because a it's just using a new direction stream and nothing more should change here so the current implementations like that so we have a head going in the directions that user once we use ten streams and five operators so let's make the snake bigger what we can do is we can do another operator it's called sliding window it's sliding window does it just takes last n in this case to last n elements from the input stream and just output it as a very and the second quick win we can do is a fruit eaten event so the top the top stream is a head snake position snake head position and whenever that the head of the snake is position is the same as a fruit we have a value in the bottom stream which we'll call fruit it in events so very quick way to do it it an event right so we have something like that right now so user can control the snake with just those 11 streams and a fruit attend events can generate a new fruit so the architecture of the of the stream based application can be can be pictured as a flow so all the things I mentioned are right here on this picture and that two streams on the left are input streams right the only those are the streams which are based on outside world values tix are based on the clock which is an outside world value and keys is dependent on the user input which is also an outside world value all the other streams are based on those two or other on the in the pipeline so the arrows are the operators like filter map scan sampled by and those rectangles are the streams themselves and the right out most streams are snakes and fruits so those are the streams that have values that are used by outside world in the front end applicant front end application it's just rendering them right but in the multiplayer version we'll also need to send them to the server so yes next going multiplayer and there will be some sky I called finally after a few minutes right so the requirements very briefly will go for the requirements clients and snake positions to the server so each client client connects to the to those three mistakes error and each of them are just pushing the positions of the snake and the server generates fruit based on that and scores and broadcast all them to the clients so as you probably know I just by looking at those requirements I can see that it can be implemented using streams very quickly but this time we'll get the in the front-end part we we had this bottom-up solution right now I want to do it top down so I just I just generally create a top level flow from the left there are player player states so the name of the player ended snake positions so this is basically what clients will send to the server this is just one player flow so for every player in the game will have a different instance of this flow but the flow will be the same and what this flow should broadcast back is the game event which will hold another player state the fruits of course and this course because we need to render both scores and fruits and of course we need to render also the other players right because it's a multiplayer game we should see where the other players are and right in the middle there is a scholar acha streams application that we want to implement of course the best solution technically in order to connect front-end and back-end parties WebSocket connection so what we will do is that the client application we just we just walked through will connect to the server is a WebSocket and will sense players will send player States using that connection and the nod other direction will go those game events in order to do a stream based work akka stream based WebSocket application you need to do this kind of boilerplate here there's a helper helper function handler WebSocket messages and here's the flow so our streams connected to each other on the multiplayer side which would just pass through and this flow take multiple dot flow is this is this question mark in the middle the whole application looks like that client flow this is the the the front-end part that we you've already seen the player states go there in the right direction and left back to the client go player states fruits and scores yeah so fruit it in events that we implemented on the front end side will just be moved in this form to the server side because the server needs to be need need to be aware of where the fruits are and needs to be able to generate them for all the players and based on that each time somebody it's a fruit we can create a new stream a new stream which will be the fruit stream that will output back so whenever there is an eat an event the value here doesn't matter because just it's just an event right so the objects itself contains the information about the about somebody eating an apple so we just generate the random vector which will be the next fruit this is how it looks like in akka stream and flow is the type from akka stream and there are three type era meters there the first one is just the input input type and this one is the output type so here we can see that this flow fruit flow takes a list of player positions which is just the snake of the player and returns a fruit position and we are using some kind of this fruit position function in order to get a from the from our persistence layer of fruits in order to get the position which is not important right here but you can use it as you see using map I think operator from akka stream and yet so this is the fruits part let's move on based on the same immutable stream which is eaten events we can also do score updates right we just map over with a function that always returns one and we get a score updates stream what we can do with the score update stream is that we can of course scan scan it in order to get scores for this particular player because this is one player stream and this is how it looks in Scala code so again we have a flow this time the input type is fruit position and output type is int and and we yeah we just map fruit position based on the is it a new fruit position then this core is the updates is core abilities one and if it's a current fruit position then the update is core update is 0 then we scan over it and get the sum so this is a score flow very simple yeah ok and the game logic on server side is surprisingly small smaller than the front-end part I don't know if it's good or bad but but that's that's how it looks like so player States is are filtered then we go through scan map the same kind of approach was taken here as in front-end application even though we are in the backend side but the same concepts are used right and then the game logic flow like the all the all the flow that we want to do this is a comment because I don't really like the DSL Vaca stream web some people say that I can get used to that but still I want to always start with a with a comment to the method so this is what I wanted to achieve i wanted to broadcast player state that we get from the front end then may go go let it go through the fruit flow let go from through the score flow and then zip all those things in order to produce a game event right and this is how it looks in the code so this is basically the implementation of what I just said flow the in also there is a flow and player state is an input parameter game event is the output type parameter right and we are using a different a different style of akka stream graph DSL to connect all those things outlets and inlets together in order to produce a game even zip out of it so this is how a custom graph DSL looks like so we have everything set up I think basically the one thing that's left is that how should we broadcast our player position from this one particular flow to other players flows because each player has a different separate flow right and we need to somehow make all of them know about our positions so this is where this kind of merge goes in server up flow using aqua stream will look like that so this is I just started from like very small building blocks right just simple flows this one takes fruit position and outputs int this one takes player states and inputs game event right and then I can just connect them together very very easily so this is my application this is the flow I was sent I was passing as a parameter to the handle WebSocket messages at the beginning so this is it we just log everything that comes in then we this is web message is a web socket message so then we just take text out of it with parse it using spray Jason and then it goes via game logic flow which we just developed then it goes via game even brokers flow which I haven't shown you about this job broke just broadcasts those events to other players and then what we get back is the game event so we need to sterilize it to Jason and then map it over in order to get a WebSocket message and that's why the flow is from WebSocket message to WebSocket message because it goes around yeah and back in in action how it looks like so you can yeah you can see this this code here fifty three lines of code I'm not I'm not messing with you yeah and what we can do here localhost quick demo yeah so this is first player joint and my snake I'm getting better at this game already yeah okay and we can then select we can connect the server as a player B yeah and you can see that I'm just playing with myself right now but doesn't matter when you go to IntelliJ you see that those this debauch which I put here is working because that it's it's just showing us all messages that comes in through their phone and yep basically basically that's it so I will just I'll just skip to the slides links for it I know the question is there but Q&A will be like in one slide so you can build your own snake there's a blog post in JavaScript you can maybe you'll like it there is a client-side code a server-side code those I will try to tweet those guys in a minute and play you can play with stream operators and for for example Eric's marbles is a very very great site to play streams with so what it allows you to do is to for example for each operator you can just play around what happens right so this is an input stream I can operate on the input stream and what happens here is shown for example sampled by which is more involved you can see what what is really happening in the output stream whenever input stream changes so those questions about sequencing can can be you can be very easily answered by just playing with that and yeah that's basically it I use aqua streams on the on the server side and bekandjesse on the front-end side yeah that's all from my side I hope you guys enjoyed it and she and there's a question here so the question is whether using graph DSL whether we can have it cycles and the answer is yes so how do you actually around consumes a day and I know it's a graph with something statically defined and it gets materialized once the actual properties how do you dynamically connect to it by using actors as I can publish to subscribe like message broker or whatever what are you using how do i what so you have to have your you don't know your graph in advance right because please can connected go okay so the question is that those graphs are probably dynamic but the the answer is that they are not diamond dynamic they are they we know at compile time how they look like that's why i said that the one player flow is just one player flow that there's some jacketed trickery going on so the game even broadcast flow which I haven't shown is it's like more more involved one which I can show you right now but it's available in the code I can discuss with it but it using it uses actors on the side and materials value values so more advanced occurrence features so the question is would it be hard to distribute those flows across different machines and the answer is no because that's the whole point so that just like in akka you are just using some kind of modal programming model that is easily is parallelizable right or it can be it can be done on many different threads without even changing a code yes so that's why that's why you so it all depends on how you define your flow for example I use map I think my method and this one doesn't do the polarization of one stage so it does it as a synchronously so you can you can say which stage should be done asynchronously from from it another so this is your still your decision yeah okay are there any more questions no yeah one more question yeah so this is the third part of the other presentation which I didn't have time to show so yeah memory bounds so the question was about should and so that shouldn't I be concerned about memory limitations right memory limits yes the answer is I should be and this is where reactive streams comes in come in okay so there's a whole API the whole protocol to make the synchronous communication safe in regards to out of memory exceptions and and such yeah yeah this is all posted on github I do we have time for more questions one more question and that's all one more question that's all you don't understand correctly what the advantage separating it just so that it's more readable and that you can just you know we the keypresses stream can be used but in left and rights but it can also be used in pause reset you know in different stuff but it's just one stream which you can later reuse because what what I'm doing right here is just keyboard event it can be mapped and I can just have key code right there right mark to int and then reuse it all over so yeah it doesn't matter how many stages you do so it's more about maintainability and readability of the code right not the performance here because they are all squeezed into one at the end okay thank you very much guys yeah [Applause] [Music]