 What I'm going to talk about in this video are some ideas of had of late of how to do game logic programming in terms of code which is both purely functional and Garbage collected without pauses and those two things go hand-in-hand as I'll explain and As happy side effects the scheme I have in mind would also make it trivially easy to save game state on the fly like a quick Save system or just a general save system And also actually Trivially record the entirety of gameplay the entirety of the state changes within the game frame for frame For the purposes of like demo playback or just debugging, you know, that's extremely useful You want to be able to just go back and see exactly why that thing went wrong And then also this is more speculative, but I think also there are implications for how netcode is done Though that a layout as I'll explain a layout to variance of the idea I have in mind one of which is Definitely more expensive in terms of overhead but would facilitate I think certain ways of doing that code that the other doesn't which is more efficient But I'll talk about that when I come to it First I should back up and talk about the garbage collection problems So garbage collection is particularly problematic in games because even the best garbage collectors They can be really efficient at allocating objects, you know finding places to put your new allocations And they can be really efficient at finding all the dead objects all the things that need to go away But the sticking point is that at some point you're going to end up with fragmentation in your heap You're gonna have a bunch of holes of where you need to allocate and so you're gonna have to Compact the space you're gonna have to move objects around to defragment and when you move objects around That means you have to update all the references to those objects and doing so Really, there isn't any other way to do it except to pause the world to stop the world While this process is happening and that's what leads to potentially fairly nasty pauses like even on the order of seconds But then more well, we have generational garbage collectors when the minor Collection cycles tend to be much shorter than that but still the sort of thing that would cause Errant sort of like hitches in in gameplay I mean maybe on the order of like part of a frame for the minor garbage collection Cycles, but still it's it's it's really not what you want I mean, it's one thing to accept that you're doing your game in higher level code It's not CRC plus plus and so you can accept just a pervasive performance loss where you can do fewer things per frame And not deliver like the the as fancy graphics as latest greatest triple a and maybe like, you know Have less elaborate like physics simulation or no physics simulation that sort of stuff It's one thing to accept that sort of deficiency but I think completely different to accept that your game will just won't have smooth gameplay period and In a way that you can't predict And so that the workaround that people currently do when they target games From like JavaScript on the browser or like C sharp code in unity What they do is this technique called object pooling and an object pooling basically we we recycle our objects So instead of using a new operator to allocate our objects We have these pool things which keep around a whole list of objects that can be available for reuse But what that requires is that when we're done with an object we have to explicitly hand it back to our pools And so this object pooling technique arguably just is imposing the burden of manual memory management back on the programmer Anyway, even though they're in this managed environment this garbage collected environment, which is you know disappointing, right? It's you know kind of self-defeating arguably and I've heard the argument made that well You know once you take away garbage collection such that I'm really doing manual allocation or management rather If I have to manage the lifetime of my objects, how high level is this code really? I mean sure maybe it's dynamically typed if we're in JavaScript and so there's that distinction which I don't know It's kind of a double-edged sword. There's I mean there's advantages, but also disadvantages It's really arguably not that much higher level once you take it with garbage collection So what's the point of writing games in these languages? And then the other side of this problem is that well if I have to use object pooling Well, that's not really conducive to functional programming part of the elegance of functional programming Really does go hand-in-hand with garbage collection and the nature of functional code is because everything's immutable We tend to produce a lot more objects. So we kind of are birding ourselves with more and more garbage So anyway, the gist of what I'm going to describe our actually two different schemes whereby we take advantage of the fact that our game logic is functional and thereby we can have a special scheme wherein we don't have to manually Deallocate any objects created in our functionally pure code or we do our game logic We need to do our update function and yet we don't have any of the the pauses normally associated with garbage collected code So you may be wondering why do we want our game logic code to be functionally pure? Well, I think functional code tends to be more elegant and simpler in our stand. It tends to be a bit more compact and The biggest win is that from function to function. We don't have to worry about state mutations So the idea is that if you have your your update function as a pure function Well, that's calling a bunch of other pure functions probably and between all those functions We're not worried about some change to the state happening out from under us as we're doing our business because what can happen in a conventional game loop is that you have this big heap of mutable state and In one part of your game logic an earlier part you you do something that modifies it in a way that a later Part of your game logic maybe isn't expecting and you get all sorts of state bugs because of that in Function a pure code we have these different pieces of our game state which are producing different opinions You might say about how the underlying state should change and you have to explicitly reconcile those differences and by being more explicit about These conflicts were much less likely to produce these kinds of state bugs So the general pattern with our update function is that it's functionally pure And so it takes as input the previous game state as a as an immutable object along with The delta time since the last update the user input like what we get from the gamepad or other control input And then probably like a random seed if we have some sort of random generation business in our logic Among any other sort of information we need as input to our update function And the update function takes all this information and spits out a new state And so our game is a succession of states where each state in itself for the sake of the update function We treat as immutable But of course our game state is mutating over time from frame to frame Now you may be wondering well, you know, not everything in your game logic proceeds immediately from the previous frame Or so you would think so like in a game Maybe the player pushes a button and then like 10 seconds later not immediately but 10 seconds later some door opens Right. Well, how do you express that in this pattern? Because what we have is this pure function that takes the immediate previous state and to get the next state So how do we express those things happening over time? Well, very simply you just those sorts of future intentions you embed in your state So when the player in the frame where the player pushes the button that future intention somehow is encoded in the in the state Such that 10 seconds later after sufficient delta times have passed then our game logic reacts and opens the door So effectively anything in your game where something that happens in one frame might have consequences for frames down the line You have to keep that embedded in the game state Okay, so now I can finally get into describing this scheme I have in mind Though as I said there are actually two different schemes I've devised and I'll start actually with the much more complicated and probably more expensive one Because I think well first it's kind of interesting, but it may have some advantages. I'm not really certain at this point So let me let let that out first So first off imagine that when we generate say state one All the objects of that state and all the accompanying garbage when we produce that state It's allocated in some section of heat memory and then in the next frame when we generate state two We have a separate section where we allocate that state Data and all of its accompanying garbage and likewise with state three and four and so on so just imagine we just keep allocating states in and their garbage in new heap sections and Imagine that well because this code is purely functional We're representing states as immutable data structures as persistent data structures and the nature of these data structures Is that when we produce state two? We're producing it from pre-existing state one where all the stuff that we hold in common that stuff gets carried over the state two effectively is a series of objects that point in to parts of state one and Then in turn state three is an object that points into parts of state two and potentially into parts of state one So we can observe that given the immutable nature of all these objects We have this basic guarantee that state n is referencing objects from previous states and never subsequent states We can also observe if that an object in some state is Referenced by a later state then we know that that object must be present in all the intermediate states Because that's the only way objects get from one state to later states is they have to get carried through all their intermediate states Now the nature of these persistent state objects is that they're always going to be some kind of hierarchy or action We're actually some kind of directed acyclic graph where the root represents the whole state And then when it comes time to modify something in that state for a subsequent state The way these persistent data structures work is that chain of nodes gets replaced So here for example in this state one if this leaf node we want to modify something about it Well, then we're going to end up replacing it And so when we replace it we then have to replace the whole chain of nodes Down to the root and so we end up with something like this We end up with state 2 which is this new chain of nodes where these new nodes are pointing into the parts of the structure that didn't change So now assuming we have a state object like this Which is some directed acyclic graph because the nodes are all immutable We know that a node which is pointed to by another must be created before that other node a node Which points to other nodes must be younger than the nodes it points to and so assuming that we do our allocation of these node Objects starting at low addresses and working our way towards higher addresses Then we'll get some kind of layout like this where we get six five three four two one zero with zero the root node must be the last Object in in the whole thing and note that some of this ordering we have some options like it could happen that maybe Three was allocated after node two rather than before so maybe it comes after two in the order Those are unrelated nodes And so it might happen that one or the other is created before the other it could go either way But anyway, we get this basic guarantee that objects are only referencing stuff that comes before them in memory not after So now imagine that when we allocate these state nodes We're also allocating other garbage in the same space. And so those objects get interspersed If we now want to get rid of the garbage but preserve the state What we need to do is traverse from the root object the last part of our node hierarchy We need to depth first recursive traversal from that root object and starting at the leaf nodes Copy those into some other memory area and as we do this every time we copy an object We need to record in this table a mapping of these old addresses to their new addresses Such that when we copy subsequent objects, we can scan for references and update them Because remember the rule is that the later objects are the ones that are going to be referencing the older objects And so in this way with this table, we can update the references in one pass. We can do it as we are copying the objects Now because of the depth first traversal Depending upon exactly how that works. You're going to get cases where the original ordering of the Node objects is not going to be perfectly preserved But that's going to be for cases where it doesn't matter those are going to be unrelated objects And so it's fine here say if unrelated objects three and four which don't reference each other Get swapped when we copy all of these state nodes So now let's make things slightly more complicated What if we have multiple states multiple successive states which we want to copy to some other memory area? So here we have state n with the blue node objects, and then we have state n plus one with the purple Node objects. They've been allocated in separate memory sections, but we know for sure that State n is in lower addresses than state n plus one So we do the same thing we just did with state n, but then for state n plus one We need to keep around that table of old addresses mapped to the new addresses because Nodes in state n plus one are the purple nodes are going to potentially point into parts of state n of the previous state And so nodes of this later state we're going to have to fix their references so that they point to the new locations of those objects in the new memory area and Because this new state is referencing objects from the previous state We don't want to copy those objects multiple times. We only need one copy, right? So when we do the depth first traversal of this later state We just don't follow any references where the memory address is below the barrier between these two memory areas Any object with an address which is lower than the start of this memory area for the state n plus one Must have already been copied so we don't need to copy it again So at the end of this process we end up with a contiguous packing of the objects from state n and state n plus one And of course to access these objects We're going to want to keep a list somewhere of references to the roots of these state objects So with this technique if we had say an area of memory where we Allocated state one and all of its garbage state two and all of its garbage and then state three and all of its garbage And so forth we could copy all those states to remove the garbage and pack them contiguously A major problem with these persistent data structures However, is they're not really conducive to high performance when it comes time to say iterate through all of our entities Which we need to render that that sort of business where we need to loop very quickly through a whole series of things Because these data structures the way we organize the state is kind of spread out through memory So to ameliorate this problem We could have a separate copy of our game state which is a conventional mutable expression of that game state That's a little contiguously packed as much of structures of arrays or arrays of structures And the idea is that after we produce each new state We then need to update this mutable state from that new version of the state And the nice thing about these persistent data structures is they naturally are sort of a diff of their previous states So you take state n it's like naturally a diff of state n minus one from which it's derived And so algorithmically at least finding the parts of the state which we need to update is relatively cheap It just requires traversing from root all of the parts of the new state object And the thing is well if we're going to be doing that sort of traversal anyway each frame Then why don't we just do this state copying business at the same time? So instead of having successive memory areas where we allocate all the objects of each successive state We just have this one memory area which we reuse every time we want to create a new state and all of its accompanying garbage So we at some point need to get rid of old states because we can't keep them around forever But the problem is that our latest state our latest and greatest state that we actually need Maybe referencing objects way back in those earliest states So what we need is some sort of system whereby we can effectively roll forward all the parts from the old states that we need in the Latest states such that we can dispense with those old states So the way we can do this is where things get a little complicated to start out Imagine we have these six segments of memory in pairs you can think of them in pairs So there's A and B their segment C and D and their segment E and F and We start out by copying those individual states that we're generating we copy them into this segment B and We keep copying states into segment B up to some fixed maximum Let's just say it's 10 and practice a puppy to be considerably larger than 10 But let's just say it's 10 for simplicity so at the outside of her game We're gonna have states 0 through 9 all in segment B Once we hit our maximum though We then start putting new states in segment D as we do so we update any references using this map of EF to a at the outside However, this EF to a translation table is going to be blank because we didn't have anything in segments era if they got Moved to a so this this will be an empty table in this first pass But I mentioned it here to establish the pattern So anyway, what we also do at the same time as States are being added in the segment D is that we need to start doing a deep copy of states from segment B and Meanwhile updating the references as we copy them using the EF to a translation table and during this copy We're generating a new translation table of a B to C Again in this first pass this first cycle It doesn't really make sense because we don't have anything to deep copy when to copy from segment B to to see It's actually just gonna be a verbatim copy in this first instance, but again, I'm just establishing the pattern Now once we've finished copying stuff in from segment B into C We then need to go through all the references in segment B and update them using the EF to a translation table And and be clear about the time frame here So in the time that we are adding new states in our main thread of execution We're adding new states into segment D. There's a background thread. Hopefully running on a separate core, right? Which is doing this deep copy business into segment C and generating the a B to C translation table and then updating references in segment B and My assumption is that that can be done within the time frame Before we fill up segment D before we generate 10 complete states with segment D and at this point I'm assuming it's doable. I haven't actually tested this thing But I'm assuming it's doable because and it really should be less work than when we generate these states in the first place Because we're just copying some objects and updating some references. So it really should take less time In practice if we actually implement this thing We're gonna have to put in some kind of failsafe in case that this background job It doesn't get done within the timing window and it's not clear what to do exactly in that event Except maybe just pause the world as we wait for that job to finish Which would again be you know reintroducing pauses But I suspect my assumption is that on most machines that is particularly like desktop machines This would not be a problem that this background job should really always get done well within the time window Anyway, so once this is all done what we end up with is in segment D We have these 10 new states 10 to 19 and in segment C We have deep copies of what was all in segment B states 0 through 9 and Segment B has had references modified such that if it was referencing anything from segment E and F It now references copies of those objects that exist in segment A Though of course again in this first cycle there's nothing in E or F or a so that's not going to be the case in this first pass So now that segment D is full up with new states And we have copies in segment C and updated references in segment B We then repeat the whole process except now we're going to put the new states in segment F We're going to do a deep copy of states from segment D into segment E And then we're going to update the references in segment D And when we update those references we use a translation table of AB to C which we generated in the previous cycle and We also use that translation table when we do the deep copies into segment E And as we do that deep copy and we're generated a new tables of CD to E which we're going to use in the next cycle And also notice I forgot to mention this last cycle, but when we generate the new states in segment F We're using the translation table from AB to C to make sure that the new objects we're generating Don't reference anything from segments A and B Because the whole idea is that in the next cycle we're going to be reusing segment A and B And so now we're going to be creating new states in segment B overriding what was there We're generating states 30 to 39 and As we do so any new objects that get copied into segment B We update their references with the CD to E translation table so that they don't reference anything from C and D And we also then are going to deep copy stuff that was in F to segment A And then in the background as we're creating those new states in segment B We're going to deep copy states from segment F into segment A Using the CD to E translation table and during that copy we're generating this new EF to A translation table And then once that is done we're going to update any references to objects from C and D to point to their copies in segment E So after this cycle we're going to end up with new states 30 to 39 in segment B Segment A will have a deep copy of the segments from segment F And then segment F itself will have updated references such that there are no lingering references to stuff from segment C and D Such that in the next cycle we can reuse segments C and D So one more time just to make this absolutely explicit We create new states 40 to 49 in segment D overriding whatever was in that segment before And we're update the references with the EF to A translation table Which was generated in the previous cycle and then we need to deep copy the stuff from segment B into C Updating the references with the EF to A translation table And during that copy we create the AB to C translation table which we'll use in the next cycle And then lastly once that deep copy business is done We then go through segment B and update the references from E and F to point to objects in A So that we can clobber segments E and F in the next cycle And so this is what we end up with at the end of that process So that's the general idea is that we're sort of rolling old objects forward into deep copies in segments a c and e And then updating references so that we can leave one of these pairs of segments behind and reuse it in three cycles And as far as I can figure really does require these three pairs of segments Now as I mentioned briefly earlier it'd be really cool in our games if for the purpose of like Demo recording features and particularly for debugging if we could just Record all of the game state as we play it if you'd like journal all the state changes out to disk And so the way you would do that in this arrangement After we copy everything to segments a c and e when we when we would get a complete copy in those segments We then just need to basically do a straight binary copy of that entire segment out to disk Because those segments effectively are going to have a complete set of object references that are all At least you know relative to the starting address are all contained within that segment That's a special thing about segments a c and d is that they're because they're deep copies Those state objects are totally self-contained So I don't think we actually really need to do any sort of fancy serialization business because the references are all Correct relative to each other So we've just take that big contiguous chunk and put it on disk If you want to reload these states into different parts of memory we can do so And lastly be very clear if you're concerned about well, geez like preserving all this state must be massive like well What if my you know one individual state of my game is like I don't know 100 megabytes, which would be a really quite large state Well, the thing is about these persistent data structures. They're naturally effectively like diffs of the previous state So when we do these deep copies into segments a c and e Well, the first state is going to be a complete copy It's going to be this the full size of the state all the data But each state after is just the changes from the previous state And you know for some games, you know, maybe you have a whole lot of objects changing frame to frame But I think most games though Unless you're doing like some kind of physics simulation We have a lot of stuff that moves around or like if you represented particles somehow in your game state And you know, they're a bunch of them changing frame to frame Then you would have a problem with this pattern definitely But most games I think you can work around that There are ways I believe you could treat Physics simulation and particle simulation as like separate parts of your game state that aren't included in this And so in most games it'd actually be very rare to be changing tons of different stuff Each frame to frame and so yes in these segments a c and e like the first state might be quite large But then the remaining states are the minimal expression of what's changed from one frame to the next And because the first state in one of these segments is going to be a full representation of the state That's not a diff. It's probably conducive to make these segments Have like I don't know 500 states or so or a thousand like a good number of states in each segment And which of course means these memory areas which are our segments have to be quite large and But you know for most games on particularly on desktop platforms. We we definitely are not running out of memory. That's not our issue So this pattern is definitely expensive in terms of consuming a lot more memory than we otherwise would with a conventional mutable represented representation of game state But I think for many many games that's really not a huge issue and it'd be it'd be well worth it If you do object that hey, that all sounds way too expensive and complicated Well, fortunately, there's a much simpler solution which I think would induce much less overhead The only downside is that we would no longer have old states left around in memory You would only just have the previous state and the new state you just generated And perhaps it would be less conducive to the style of debugging I described where we can step through proceeding states But maybe not I'm not actually certain And the other advantage of the elaborate system I just laid out is that I think it may have implications for net code where You can do special things whereby you compare old states With the latest state and thereby potentially having an easier way of doing latency compensation I'm really not sure though. That's just very speculative idea Anyway, the much simpler solution to get pauses garbage collection and yet have our update function be purely functional The much simpler solution is to have a conventional mutable representation of our game state But to treat that mutable game state as if it is immutable for the duration of our update function And the idea is that in our update function What we produce is not really a complete new game state object But just a chain set a list of things we want to change for the next state Only once our pure update function returns this change set Do we then go through the change set and apply those changes into the actual mutable version of our game state? And that's when we would do the rendering part of our game loop and the other inherently impure stuff So exactly how this would work is that during our update function All objects would get allocated in this. Let's call it the update buffer And then once the function returns Then the return value that the set of changes is in that update buffer We need to traverse it and copy the changes into the actual mutable version of our game state And then we no longer need anything in the update buffer. So it can just be reused for the next frame for the next update So effectively at least for the scope of our pure update function. We have garbage collection yet no pauses It's almost like automatic stack allocation But not really because what we're producing in the end is some immutable object to change that object That may actually reference stuff from earlier calls that had returned That's that's that's all sitting there in this update buffer as if it were more like a heap and not an automatically allocated stack Now the one sticking point is well What if we do want to journal all the changes for for like the sake of a playback system for debugging and demo playback? Well, the obvious solution there is that Once you have a pure update function in particular It's really easy to implement a playback system because you just need to record your initial game state And then from there you just record all the inputs the the impure inputs to our update function We don't even need to record any of the state. We just record the part that affects the state like the user input the delta time Potentially network input or just pretty much everything. That's not the state Because if we have that information we can then reconstruct any state that our game went through by just starting at the beginning And running the same logic to get the same output and we could step through that in debug Obviously though, there's an impracticality here in that just like with video codex You don't want to just have that first keyframe and then a whole bunch of deltas with no more keyframes because then If you want to skip to like 10 minutes in or two hours in or whatever It would be horribly slow if we had to start always from the beginning to get to any frame to get to any state So what we want to do then is record periodic keyframes. Let's say, I don't know like every 10 seconds or something The problem though is that capturing a complete keyframe Would likely in most games introduce some momentary pause like on the order of I don't know Maybe just a frame jitter or even like full seconds if you have like a really large game state We can fix this though because in the scheme we're generating change sets and we can take advantage of that So what we do is that we want to amortize the cost of copying the game state over many frames So like say let's just say a thousand frames So we take a thousandth of the the game the memory area that holds our mutable game state And we copy a thousandth of it to some other memory area And then in the next frame we copy the next chunk and then the next frame the next chunk and so forth until after a thousand frames We've copied the entire memory area But of course what we end up with then is like a frankenstein that doesn't represent just one frame state But it's like a mishmash of the latest frame state and stuff from previous frames, right? Well at the same time if during all of these frames, we are also recording the change sets We can then play back those change sets in order over this memory area And what we will get in the end is a complete copy of the game state at the end of that a thousand frames And so we can then serialize that frame state and journal it out to disk along with all the recording of the the inputs to our update function like the the user input in the delta time and all that stuff And so now if we want to jump around in a playback recording with these keyframes, we can do so much more efficiently So that's basically it. That's my idea for a pauses garbage collection scheme that would work in games I'd really like to try an implementation of this. Unfortunately, there's no really obvious routes Like ideally I could just take a closure code in the context of java code and somehow do something with a garbage collector whereby Everything is allocated in this special buffer and then dispensed with after each frame But to my knowledge, there's no way of doing that. You don't have that kind of control in the jvm environment Other options would be like implementing closure that compiles to c code or something Perhaps and do that in the context of a c program where you do have control of your memory That would be an interesting approach, but obviously it would be a whole bunch of work in another regard Or probably the easiest way is to like take a basic outline of a game in c And just implement the the essential data types immutable data types used in Enclosure in c and and also like basic helper functions like equivalents, you know, like map and reduce and that sort of stuff Basically do an experiment where i'm doing functional style programming in the c language And that may be actually the easiest route the route that takes the least work to try out this idea It would still probably be in a substantial amount of work though I'm not really in the position to commit to that sort of thing at the moment So if anyone has some suggestions of how this might be implemented or just some critique of like why I wouldn't possibly work Or whatever. I'd actually be very interested in hearing it