Submind YouTube summaries
Thumbnail for Building Resilient Live Service Games with Python: Lessons from the Trenches by Willem Thiart

Building Resilient Live Service Games with Python: Lessons from the Trenches by Willem Thiart

Watch on YouTube

Video summary

Willem Thiart presents a comprehensive look at building resilient live service games using Python, focusing on the architecture behind the card game "Demon's Hand." The system is designed as a thin client written in Lua, which acts merely as a display layer without knowledge of the underlying game logic. All critical gameplay mechanics, rules, and state management are handled server-side in Python to ensure consistency and prevent cheating. To manage this complexity, the team utilizes a Domain Specific Language (DSL) implemented directly in executable Python code, allowing for concise definitions of card effects, dice rolls, and status changes that are significantly easier to reason about than verbose English descriptions or traditional scripting languages. The architectural design prioritizes resilience and performance through an asynchronous event-driven model centered around AWS infrastructure. Gameplay logic is processed via a FIFO queue on EC2 instances rather than Lambda functions, a deliberate choice made to ensure sufficient RAM availability for the memory-intensive nature of game simulations. Non-gameplay interactions, such as viewing the world map or checking scoreboards, are handled via HTTP requests to serverless functions. The system leverages Python's `asyncio` event loop and custom implementations using libraries like `httpx` to optimize performance over standard AWS SDKs, demonstrating that developers should not hesitate to hand-roll solutions or replace default batteries when necessary to meet specific latency requirements. Resiliency is further enhanced by isolating Input/Output operations outside the main game loop, which simplifies debugging and allows for efficient batching of database writes to DynamoDB. The team employs extensive profiling tools like PyInstrument to monitor system behavior under load, ensuring that the asynchronous infrastructure remains stable. Additionally, the presentation explores advanced concepts such as using Abstract Base Classes for flexible interface design, which can even be leveraged with Large Language Models to generate new implementations, and theoretical approaches to maintaining game integrity across network interruptions by utilizing deterministic random number generation on mobile devices. Ultimately, the talk concludes that while standard libraries are useful, a deep understanding of Python's capabilities allows developers to build highly optimized, maintainable, and robust systems tailored to the unique demands of live service gaming.
Read the full video transcript
Hi. Hi everyone. It's really good to be here. Um, so I'm going to do a presentation in three parts. I'm going to explain what is demon's hand. Um I'm going to show you a really cool card DSL. So domain uh specific language and lastly I'm going to f finish with some resiliency. Okay. Um so what is Demon's Hand? It's a card game. Um live dungeon multiplayer. So there's other people playing with you. Um, scoreboards, live scoreboards. We've got emotes. Uh, the dungeons live, so there's lingering presents. I've got a nice world map. So, uh, over here you can see that's the hand. This is the board. You take, you basically just drag your cards to the board and stuff happens. Um, availability. We're on Google Play. says, "Really good." Um, going to go on it. Uh, uh, I mean, I'm old. I said iTunes Apple Store. Um, and Steam was everyone, uh, everyone as a gamer actually wants a Steam version. Um, cool. So, architecture. Um, sorry, it's a little bit of a AWS tinge because I I like it's what I know. So uh but the Python icon should be bigger. So we're going to focus mostly on Python today. Okay. Um and uh sorry I think the realization might be a little disappointing to some people but um the the client is is is written in Lua. Um we use the default engine. Um but in the presentation I've got a good news for you. Okay. Um, so we've just got two forms of connection. We've got the old web socket. Uh, it goes straight to AWS, has a really nice, um, interface and we kind of like mangle it a little bit. So, it goes on a queue, nice FIFO queue, and EC2 just gobbles it up and starts doing uh, gameplay logic. Um, so game danger logic goes to on the on the queue and all HTTP just follow like meta data and stuff like um if you want to see what's the world map HTTP you want to get scoreboard HTTP um everything that's not gameplay logic you just just do HTTP um and I like serverless function as a service so um there we go um just a question This does anyone got like an idea why we did EC2 instead of just lambda or not bikes? What's up with >> um RAM RAM games breathe RAM. Um Lambda doesn't really have a RAM, you know, because um a lambda executes and that RAM's gone. you can't really trust it. So, I would have done everything in Lambda, but um you know, we we got to use EC2 because of RAM. Um websocket over SQS. What's going on there? Um so, we love async. Um everything's async. It's just the way to go. We got a nice infinite loop over here. Um and we just pull things out of the FIFO queue. Come on. Um, and that's how the game logic works. Um, so, so, uh, over here just got a nice big fat gather and all the game simulation just gets pumped through and then we get the IO out and then we post the IO at the bottom. Um, this is not the actual algorithm, but uh, it's executable pseudo code. Um, I think recently free threading's come out. So, I think I'm going to do that next. Okay, cool. So, HTTP function as a service. All the stuff that players want to see and want to know about goes through a JSON API. Um, so good. This is actual code. Um, so Python just makes it really easy. So, okay. So, so, so even though we're using lure as the game uh code as the client side, it's a thin client. Okay. So, um, COD, the client has got no idea how the cards work. It's got no idea how the monsters work. It's got no idea how anything works. Basically, Python the server tells the client what's going on. Um, so it's so we got all these events. It's event driven. So, you know, we say draw card and then the card pops up, pops into the hand. Um, we got effects and stuff like that. Um, you see, you see that targeting system over there? Um, client doesn't know what it is. The server basically says um that card over there uh can hit that that monster over there, that monster over there, and you can move there and you this is also an option you got. Um so the card has got no idea about the game logic. It just gets a card from the server and the server says this is what you can do with it. Um it's really good because I can spend more time in Python. Um so Lou is a thin client. This is an example of a really critical uh message we send to the client. So, sorry I got to squeeze it over to squeeze it. It's kind of weird when you see Jason like that. Um, but yeah, that's an event. That's update hand over there. So, I the service says, "Hey, update your hand. Um, you got two cards today. You got cleave um and cost three and you've got a move card um and you can only hit those two targets. Okay. Uh and over here with cleave, cleave can do can go to that target, that target, that target. And um that stuff is affected by cleave because it's it's it's a uh I'll show you later. Cle's actually in this um surprise Google Sheets. So, where do we put the game rules? Where where do you put the DSL? We put it in a spreadsheet. Um why not? Um I used to be an accountant. So, um it seemed intuitive. Um yeah, this is the spreadsheet um part of it. You can see we're at the bees. So there's a lot more. Um you can see like we got like target of all. Got this DSL happening here. Um we got nice dice roll notation. Everyone loves dice roll notation. Um and then we just got the effects. I call this on effect. Uh it just tells you what the card does in a DSL. Um you get used to it. Um and then this is how hairy it gets. We've got like some sort of hook system. So you've got okay played on turn end um on turn end you draw one um stuff like that. So stuff's happening on this this uh this this uh all these hooks. We got hooks for everything. Okay. Um add a new hook. If you can't if it if it's tricky to implement something, you just add a new hook. It's fine. Um all right. Um, so I'm gonna show you the DSL now. Um, what does greed do? >> Draws two. I always forget what it does. Um, so over here we got uh just draw two. Um, that's the DSL. Um, got another card, coin flip. Uh, discard one. Um, draw from the deck. Um, so it's quite nice because it's actually uh sometimes it's more tur than the actual uh English which is really good. Um, it gets a bit more interesting now. Uh, we got a do. So do lets us do two lines like that. And we got another like a little if statement. Um, I think that double if might hint at what's going on. Um, but we'll get there soon. Um, so, and then we got the nice column for the damage. And that's how you implement uh, scavenge. Um, smite three. Um, so it's got a little bit of a if there's an undead monster, you use smite, you get a bonus. You get a super effective bonus right there. Um, what's going on there? What's going on there? So um [clears throat] so so like do you know what the dunder methods are for? They're for overriding. Okay, you should override. Don't that like like sometimes it's like oh why is this overridden? It doesn't work the way. Just override it. It's fine. There's no judgment. CFP. >> Um, well, nothing else to say. Just explicit is better, right? Um, so we just list all the stuff over there. Um, I like this one because you can see that the DSL is doing a little bit of like some sort of set magic. So, you basically say like this big set of monsters. Um, just add the status to them like that. And then, you know, now they're frozen. So, that's a status effect. I I think is quite nice because you you basically say like this thing impacts monsters. Um we'll do we'll show you more of that later. And there's a cleave. So So it's kind of like this type of action going across. You can see that the uh like it gets pretty cool because now you can pipe stuff. So now you've made a set. Um so the target with the when you when you tap the card on the left hand side and the right hand side the effect takes place. Um the most ambitious crossover since then 2010s all these uh functions crossing over. What a what what an ensemble. Um really lots of fun. you just say for each over there. Um and uh yeah, just a portion 51% uh just makes sure that you catch um what was the reason for that? But there was a reason for it. Oh um that wasn't supposed to be on the slide. Um game development, am I right? Okay. Um so on effect over here um chance so uh 70% this happens um easy peasy just just just try to remember this okay so chance 70% and then the effect happens over here um so surprise the DSL was actually just eval so all the stuff I just showed you is actually just execut ex executable Python um I love it it's so Um so here's an example of an implementation. Um chance is actually just an object, just a class. Um every single function has an execute method. Okay, so the system is just pumping out events. Okay, and these events uh there's a little bit of filtering happening, but we send them to the client. The client gets it. Uh we give it to the client. So the whole system is just generators generating events and giving them to the client. Um really like it. Um and for Eval, here you go Evvel, here's chance. And we just do this little silly thing over here. Um so that's all it is really. Um got a question. Why did I put the mutation decorator here? This is this is a custom decorator. What why I put it here? What about this is mutating? What does this what is what is chance mutating? Yes. Yes. Yes. Um in a card game, the random number generator is a resource you need to be fully aware of. Um if you're not paying attention to the number generator, it just gets a bit confusing about like you can't reproduce problems. So you got to plum the seed at the beginning of the game. Um, so that's why good answer. Um, so this is just another example. Nothing to it. So the little do thing, you just you're just iterating over the for loop over there. Um, and you just execute. Oh, over here you just get whatever it is and you just execute it. So everything is just generators. I love generators if you can't tell. Um but um it's interesting because like I don't have any async here. Um that's because nothing is allowed to do async. I've got a slide about that later. Um I'm not supposed to do any IO. Okay. So in the game state, no IO. Not allowed to do IO. I'll talk about that later. Um all right, the draw. So this is the draw function where you draw a card. I don't need to read it. Don't need to read it. Just need to know. Um, it's complicated, but you just hide it. The DSL hides it, right? So, whoever implements uses the draw function is like, oh, just draw one, draw two. Um, you can draw with all these other thingabobs. Um, just makes it easier. So, so this is where the DSL is really nice, everyone. Uh, you have super complicated game logic hide behind a DSL and, uh, it's just so much easier to reason about. Um, so this is just the deal the the card set. So we can do stuff like that. Um, the dund's been um overridden again. Um, overriding all these dundas. I love it. Um, you do some extra stuff like that. Uh, nothing to it really. Nothing much to say about that. Um, so this is the implementation of the card set. Uh, we got a pull function. So everything is just pulled and because we've overridden these guys we can like um get you start pulling from the or set and the answer set and you just get like a single stream. So we got a lot of card sets. Um just add as much as you want. Um makes it easier to reason about when you've got all this stuff. Um yeah. Yeah. lots of card sets. So you can like and and pipe those. Oh, so um does anyone suffer from happy code? C code that when you look at it, you just you just you're just really happy. So I really like how these worked out. I just found like it's quite um yeah, just this is so good. Um, okay. So, now I'm just going to move on to some resiliency stuff. Um, I want to talk about or JSON. Just one slide about or JSON because it's that's how good it is. It's just a nice swap in replacement. So, does everyone remember the the Google Sheets? Well, we're pulling it in here. Um, but we immediately loads it with all JSON. So, good. So much faster. Um if you do lots of JSON just just just just switch to um JSON watch out watch out for encoding though. Um UV loop. So because we love async there's this thing called UV loop which is a async event loop uh just just implemented a little bit faster. Um and this is it. This is this is this is all the calling code I got. Um, we just got UV loop install and for the rest for the rest of operation, we're just using normal async IO. Wonderful. I think the fact that it's only one slide shows you how good UV loop really is. It's not much to it. Um, it's a lesson. You can replace Python's uh batteries with whatever you want. Okay, it's just so good. Um, this I think that's a monkey patch. So, you can do it in Python. No problem. I think another language is a little bit harder than that. Um, HTTPX. Um, I love it. It's great. So, oh, AWS, right? So, um, we use Boto Boto 3. Um, but it's not async. It's not async. What's the point? Um, and like there's weird stuff going on in Boto 3. It's doing this P shape stuff. Why is it using CPU? Um, so, uh, I know I know there's other like other async libraries, but if you in in gamede div if you want things to go if we want to squeeze out performance, sometimes you just got to hand roll it. Um, so what we did was uh we took some of the services we liked like the as services and we just rewrote them in httpx. Um, so this is this um this is my favorite database. I love Dynamo DB. Um, not sponsored by AWS. That could be. Um, but yeah, all you got to do is just use the methods you love, make a nice interface. So you make your own interface like that and you just start implementing stuff. So So over here you got to have a baseline, right? So you get bo 3 and that's your baseline um implementation. Why do you need a baseline? Because then you you got to measure against something. Uh who who cares about the HTP X implementation? If you don't know how fast the Boto 3 one was. Um and if you want, you can go crazy. You can use Gevent. Um it's really nice. I am. So listen, if you spend time doing something, you can always make it a little bit faster. Um, just because the libraries exist, this the the ones that everyone uses, doesn't mean you need to use them. You could you can actually just roll your own. Um, it's okay. It's okay. Um, so I've fallen back in love with ABC from import ABC. Um, so good. I thought like I'm not going to use that stuff. It's it's it's it's solid objectorientated role play. Um, no, it's really good. Really good. I really recommend it. Um, so in the previous one, I showed you how you can just make a new implementation. It's really good for experimenting. Um, um, if you want to see like, oh, how is the system working? You just look at all the interfaces. You see how it like interfaces with everything. It's great. Um, oh, that's a little bullet point about LLM's. Um, yeah, you just tell LM, hey, uh, look at that, that interface over there. Can you make a new implementation? And, you know, maybe it stays on the guard rails inside that interface. So, interface is a really good, um, API. Um, so, isolate IO. Um the game loop doesn't have any IO inside it. Um which makes it easier uh to reproduce issues, you know. So if you got like a bug, it's easier to basically reproduce the bug. Um and I like having the IO outside because you can start batching it. Um, sorry, another Dynamo DB thing. Um, so scoreboards. Um, actually really easy. Uh, you just save your score. So you put the item there. Um, you got a partition. Can you just say like it's a score or whatever score. Um, and you just Z fill. Well, if your if that's how your database works, if your database is stringly types, you you z fill it. Um, and you just fetch it like that. um actually works pretty well. Uh I I guess we could slap a cache over here and call it a day. Um but it's it's it works okay. Um everyone should use PI instrument. It's so good. So good. Uh because we're using that SQS infrastructure, I can just say, "Hey, turn on the profiling." And then you play the game. Play the game. And then you you turn it off. Turn it off over there. And you can just fetch the profile from S3. I I think that's a good profile. I think so. I'm not sure. I'm not actually good at reading them, but like um you don't have the big fire thing. You got like it's like something seems like that async is working. Um and la last slide. Um, so there's these really cool pips that came out a few years ago actually. Um, PIP 73, PIP 738. Um, you could put Python on a mobile phone. >> Why am I not Why are we not doing this? So, um, uh, there's this bus tunnel like in Wellington, and it's I don't I haven't ridden it for a long time, but, um, what happens to your connection when you go through that thing? Um, so so you can let players like run the game on the phone. So, you put you put Python on the phone and then, uh, default is talking to Python and nobody knows. Um, and maybe this is like a little bit of research I got to do, but because the server is mostly a random number generator, theoretically I could say to the client, "Hey, hey, have a 100 random numbers." Okay. And then it should be able to or you could cheat. I guess you could pull the random number generators out and figure out how lucky you are. Um, but when you're out of the tunnel, then you just push all the input and the server because it's deterministic uh could figure out like how well you did while you're going through that tunnel. Um, cool. Thank you.