 I'm escalating once again, and this time object-oriented programming is garbage. And we'll be looking at an example that's about 3,800 significant lines of code. It's this original NES emulator I found on GitHub. It's implemented in Golang. And I picked it partly because it's just interesting to look at a working NES emulator that's relatively short. It's nice and compact for being a working emulator. But I also picked it because it's actually a very moderate example of object-oriented programming gone wrong. It doesn't really commit any absurdities like examples in the last video. And actually on a line-by-line basis, the code is quite good. It's just structurally, it's written in what I would call an obligatory object-oriented style. It's written in an OO way for the sake of being OO, not to any real purpose. So credit to the project's author, Michael Fogelman, it's a cool project. And the code is actually just fine except for its OO-ness, and I wanted to demonstrate how it would be better in a straight procedural style. Now, I'm not going to give a full account of how the emulator works because actually, honestly, the details, I'm sketching out myself because I don't understand the details of the NES hardware. If you want to learn about NES hardware, the good place to start is this NESdev.com site. It's a developer bookie for the NES hardware, it explains all the components. And I'm just going to give a broad outline. So if you really want to understand the details, I would look at NESdev.com and then look at the two code examples. His original code, which is at github.com slash Fogelman slash NES. And then my procedural version, my rewrite, which is at slash bryanwill slash NES. And understand my version is based off this commit from October 6th because there was some kind of bug with the latest version. So I just worked off the version, which I knew actually worked. Also understand that I've only gotten this compiled and working and tested on Mac. It's supposed to work on Windows, but you have to get OpenGL and port audio compiled using MinGW, and I couldn't get port audio to compile for some reason and just couldn't figure it out on Windows. If anyone has figured out how to solve that problem, I'd like to hear how. In any case, here are the headline statistics. His original version of the code, the object-oriented version, is 3800 significant lines of code. And then my revised procedural code is virtually the same. So there wasn't any real difference there because, unlike the examples we saw in the previous video, on a line-by-line basis, he wasn't doing anything really terrible. However, whereas he has 26 files, in my version I have just nine files because, again, it's fun and obligatory OO style, which in Go typically means for every core data structure you have, you then have all its methods defined in their own file of code. So it's generally one data structure per file of code, and then all of its methods. It's not the biggest thing in the world, but I do tend to really dislike that style because one thing it means, you know, for some data types you don't have all that much going on, and so you have a lot of files with barely anything in them, and I really just hate having to browse a bunch of files where most of them don't have much of anything. Yes, as we'll discuss, there are problems navigating very large files, that files that are thousands of lines long, but I think in general it's a better problem than having to navigate a bunch of little files. A more substantive problem with his original code is that he has 321 functions slash methods, most of them are actually methods. In my revised code, I got rid of all the methods, I turned them all into functions, and we ended up in the end with 59 functions plus what I would call 54 sub-functions, functions within other functions, because what I did is whenever possible, if there's a function only used within another function and for whatever reason I couldn't just straight inline the code, then I would just make it a private function to that other function that would be nested inside. The advantage of that, you look at someone's code base and the surface area is shrunk. So, in truth, yeah, we do have here 113 functions in total, but half of them, at least we know, are confined to being used in just a confined local scope. So if we're trying to find a foothold of where do we start to understand this code, then we only have to consider 59 functions. We don't have to consider these other 54. Lastly, in his code, he had defined three interfaces, an interface and goes just a definition of a set of methods, very much like it is in Java. For any type, if it implements all the methods, then implicitly it implements that interface. In my code, I got rid of one of the interfaces entirely, and the remaining two I made what I called dummy interfaces, which is an interface with a single method of the same name that takes no arguments and returns nothing, and then you just have a do nothing method for any type that you wish to implement the interface. And this way, you can have effectively a reference type, because interface values are references and go, you can have an interface that points to potentially one of multiple different concrete types. So it's really kind of like having just a more type safe void pointer, because a void pointer in C can point to anything, whereas these interface references, it's constrained what they can point to. So it's a little more safe. And then when you actually use these references, I just have everywhere, I just have a type switch. And then in each case, you handle each specific concrete type. Effectively then, with these dummy interfaces, I'm not doing any polymorphism whatsoever, which is normally what you do with interfaces and go, you have a defined set of methods. And then for any call to those methods, it's a runtime dispatch based on the actual concrete type. With these dummy interfaces, there's just the one dummy method which you never call. Now a little bit of a tangent, I think there are cases and go where proper interfaces and their use is actually a good feature. It's handy in cases where you have an external boundary in your code between an API that you're consuming in your own code, or say you're creating an API and anyone who consumes your API, it's an external boundary from their code. And interfaces are useful across these external boundaries because it's a convenient way to define something that is general and flexible in a way that otherwise would be a little awkward, because alternatively you could just have, say, an API where the caller passes in some sort of function, and the function can be a closure of any kind of data type, so you can have varying behavior acting on different concrete types defined in the consumer's code that your API knows nothing about. Interfaces, I think, are actually just a cleaner, more explicit way of expressing that idea that the call of the API is going to be passing in something with behaviors they've defined themselves, and the concrete data acted upon could be something which the API knows nothing about. That's when interfaces are a good idea. And you see this in the standard Go library, things like the read interface and the writer interface. So it's a useful notion, just within code I control myself, I'm not going to be defining new types that other parts of my code can't know about. So I don't want to put in the effort to try and define general notions of interfaces if I don't really need it. That's double-edged sword with interfaces, is that they can be very general, but you don't want that burden of generality when you're writing your own code that you entirely control. Truly general code is something you should leave up to people creating proper APIs, libraries. General code just doesn't arise as the happenstance of ordinary application programming. Truly general solutions take a lot of time and effort and they're very hard to get right, and you're really just not going to look in to writing generalized solutions to problems. It just doesn't happen. There's just a very high bar of quality of what it takes for code to be truly general. And that, in fact, is a problem with arbitrary programming, that there's this idea that we're somehow going to be creating generalized code as a happenstance of normally writing code, but I think that's just wrong. It just doesn't really happen. That's a lot of explanation of why you should generally avoid using proper interfaces and goaling code, unless you're consuming an API or creating an API. Now, here's the actual process I undertook to arrive at my revised version of the code. I did it in basically this order. I first inlined nearly all of the single call functions and methods. I then removed the interface polymorphism as I just described. I converted all the methods to functions, which basically means just take what was the receiver and make it the first argument. I then simplified the data types because it turned out after inlining all these functions and removing the interfaces and converting the methods to functions became clear that many of the core data types had fields they really didn't need and there were a couple cases of data types existing that didn't need to exist at all. And then lastly I consolidated and reorganized the files because once I inlined all that code it turns out that a whole bunch of files had nothing in them. So I'll take these five points in reverse order more or less. Here first is the file structure I ended up with. In the root directory we just have one file main.go which virtually has nothing in it. It's a very short main function that just parses the command line arguments and then passes the results to this other function defined in the UI package. And so within the root directory we have these three packages, the NES package which is where all the emulator logic goes and the UI package where we have all the code for creating the window and initializing OpenGL and port audio and initializing GLFW which this code uses and all that business. Lastly we have this util package which is misleadingly named, it's really a test package. It's this single standalone binary program which just loads a ROM and tests it. So actually it's not part of the main program at all it's not imported by either main.go or the NES package or the UI package. Looking at the files of NES and UI, first NES the console.go file is most of the core logic of that package but then the CPU instructions file contains the core function that executes an instruction on the emulated CPU. memory.go contains most of the logic for how addresses are handled and so forth in the emulated hardware. And then NES underscore types. This is the thing I typically do with go code in each package. I wanna put all the types in just one file and I usually call it the same thing as the package underscore types. So that's what I've done here. Also all the constants are in this file and the init and main if any. There's an init in this particular package I believe. Looking at the UI package, the run.go that's the core of that package, the core logic. That's where the kickoff function that's called from main.go is. UI underscore types again, that's where all the types of this package are plus all the constants and the init function. I think there is one as well in this package. And lastly util.go, that's a bunch of miscellaneous little actual utility functions unlike the util package which is misleadingly named. But anyway util.go, I actually didn't touch that file at all except for changing an import I believe. If I were writing the project from scratch there's probably a lot of stuff in there that I wouldn't keep as separate little functions because a number of them are only called in one place but it's util code and util code's kinda special. It tends to be stuff that is actually really kind of generic in a way. Meaning not terribly specific to the logic of this program. You can imagine it being stuff that you would just copy and paste into other programs. I won't vouch for everything in that file being left as it is but it didn't seem like it was doing much harm so I didn't really touch it. Now before looking at the data types there's this famous quote which you've probably heard in some form or another. The original version is by Fred Brooks and is I think in his book, The Mythical Man Month. And it goes, show me your flow chart and conceal your tables and I shall continue to be mystified. Show me your tables and I won't usually need your flow chart. It'll be obvious. This quote is usually given in more modern terms today instead of flow chart we say code and instead of tables we say data. So he's saying essentially that understanding data is actually the key to understanding code not the code per se. Or rather if you saw just the code without first understanding the data types it's really hard to follow what's happening. Assuming this is true and I think it is the question then is well isn't object oriented programming a more data oriented way of programming isn't it then an easier way to understand code? I would say no because as I argued in the first video what happens in object oriented code is it tends to introduce a lot of data types that aren't really data types. They don't exist because we actually need this data in your code. We need this reified entity that otherwise could just be code. And so we end up polluting our data with unnecessary stuff. Stuff that is kind of mystifying if you try and think of it as just actually being data because it's not. And then aside from actually introducing types we don't need at all even within the types we actually do need you might then confuse things in object oriented code by introducing fields that don't really need to be there at least from the perspective of just the data don't really make sense. They're there for code reasons, for object oriented reasons. And in fact as we'll show in the next few slides this is exactly what happens in Michael Fogelman's original code. Another thing that tends to happen in object oriented code is that your data tends to get unnecessarily fractured into small tiny components. Many parts of data that logically go together can get unnecessarily atomized into separate parts. And then also you combine that with the propensity for having many, many files they tend to get scattered throughout your whole code base in a way that can just be very confusing and make the code unnecessarily hard to navigate. Looking now at my data types in the revised version of the code I'll start with the stuff that makes up the actual emulator. So looking at the bottom right here first we have this console struct with first a pointer to a CPU, then an APU and a PPU. APU is the audio processing unit and PPU is the picture processing unit. We'll look at those structures in a moment. But then we also have a pointer to a cartridge, a pointer to controller one and controller two because of course you can have up to two controllers on the original NES. And this mapper thing determines how addresses get resolved. Some addresses resolve to RAM but then other addresses resolve to the other components in the system. And this mapper is partly what determines that logic. I'm not really certain why the mapper is here part of the console and not part of the cartridge because in the actual hardware it's a component of the cartridge. Different cartridges have different mappers. I think it was made directly part of the console as just a more convenient way of accessing it. But if we were really being stringent about modeling the actual hardware we'd make it part of the cartridge. As you can see above there is a mapper field in the cartridge but that's just a byte specifying which type of mapper because this emulator, the actual NES had many mappers but there were several that were the most common and I think this emulator only supports so let's see there's like five of them that it supports. And that's what this mapper byte designates. We load a cartridge in the code and then we actually create an actual instance of mapper based upon the type specified in the mapper field of the cartridge. In any case we'll look at the mapper types in a moment. So lastly in the console we have a RAM field which is just a slice of bytes. Looking at the cartridge struct we first have PRG, a slice of bytes which is just the actual instructions on the cartridge. And CHR which is actually sort for character but generally this refers to little tiles of various sprites and background imagery in the game. There will actually be arts and sound data in the PRG but the CHR I believe is special in how the PPU accesses it. The SRAM is a little confusingly named it's not static RAM it's just plain dynamic RAM I believe it requires refresh cycles. But it's the save RAM it's where if you have a cartridge with save states like I guess Zelda and a few others that's why some cartridges also have batteries because this SRAM needs constant power. The mapper field we just discussed and then this mirror byte specifies a mirroring mode which refers to how certain address ranges are actually duplicated. So for a portion of the address space certain ranges of addresses are mirrored with other portions of the address space. They refer to the actual same byte of memory or the same address of some device like the PPU or something. In the top left we have a controller struct with buttons which is an array of eight booleans just indicating whether the eight buttons of the controller are pressed at the moment. Index and strobe don't know what those are. Strobe I think has something to do with the turbo button feature and holding down a button and then it counts as repeated fast button presses. Index though I have no idea. In the CPU struct we have first a cycles field which is just the count of the number of cycles that have gone by. That's just always incremented. It's never reset in the course of the game so but it is a UN64 so it shouldn't be likely that we ever overflow. Unless you run the game for a very, very, very long time. After cycles we have all the various registers of the CPU including first the PC, the program counter and that's a UN16 because it's a 16 bit address space, SP, the stack pointer, the accumulator and so forth. I'm gonna go through all the details there and then at the end we have an interrupt byte which signals if an interrupt has been flagged and of what type and then stall in certain scenarios the CPU needs to wait so certain instructions will put into a state where it stalls for some number of cycles. That's the CPU struct at least in my version of the code and what's different in the original version is that the CPU has a memory field, an anonymous field for the memory type which was an interface I got rid of and when you have an anonymous interface type field and go it means that you can directly call the methods of that interface on the type you're embedding in directly here the CPU type we could call the methods of the memory type and it effectively dispatches them through its embedded memory type and it's the same as if we call it on CPU.memory in this case. Embedding the memory interface effectively makes CPU an implementer of memory. So it's kind of a weird mechanism in the language to begin with and then it turned out, oh wait, the concrete type being passed to this field was this other CPU memory struct object which had just one field, it was a pointer to console. So let me restate that, it was a really weird arrangement. You have the CPU type which has an embedded memory interface and the actual concrete type when we actually constructed the CPU is always this CPU memory struct thing which contains nothing but simply a pointer to console. So it's like a roundabout way of giving the struct a pointer to a console instead of having one just directly and somehow the implication is that the CPU is a type of memory and that's doubly weird. So already you have a CPU indirectly containing a console and a CPU which is also memory. This is not only a puzzling head scratcher, it's a total failure of real world data modeling. CPUs of course don't contain consoles, consoles contain CPUs. You don't need this weird recursion between them. And of course also CPUs access memory but they themselves aren't actually memory. So this all was very mystifying but eventually figured out after inline a bunch of methods it turned out the real reason for the strange arrangement was simply that in the methods of the CPU he was dealing with more stuff than just a CPU. It turns out he was also dealing with the console and rather than having those methods take pointers to console, passed in explicitly as arguments it must have seemed more convenient just to give the CPU a pointer to console field except for whatever reason he thought it would be needed to have this interface thing intermediating. It's just very perplexing stuff in ways I can barely articulate. And I think this is a very typical anti-pattern within object oriented programming. Within the methods of a data type it turns out very often, oh wait we need to also involve this other object. And so it's very tempting to make that other object a field of the class even if it doesn't really make sense data wise. And it also totally blows holes in any notion of encapsulation. You're supposed to be writing the methods that concern just the CPU. They shouldn't be reaching out and messing with everything else, right? What kind of encapsulation do you have if the CPU methods are all messing with the console and everything that's involved in the console? Which is basically everything else in this program. And it turns out this same mistake is echoed throughout the rest of Michael's code. In his other data structures like the PPU here he had a very similar arrangement. He had this memory field which was this other struct, a PPU memory struct with just a pointer to console and nothing else in it. And so his PPU recursively was containing a console even though the console contains a pointer to the very same PPU. As far as I can tell the only reason he did that is because either he was just getting confused thinking in object oriented terms or it was just a matter of convenience of wanting to have within the methods of the PPU easy access to the console instead of having to explicitly pass in the console. And then in his APU struct he did a similar thing but in simpler form instead of having a memory field holding a APU struct type which he never had he just simply had the APU directly containing a pointer to console. Again, I think just for convenience sake in the methods of his APU sometimes he needed to access the console and that's why it was there. And then in one of the components of the APU the APU is made up of these different parts. So there's the DMC which is a delta modulation channel then you have the pulse channel as triangle channel and noise channel those all get combined together to produce the sound. But any case in the DMC he had a pointer to CPU. So within this one component of the APU which is a component of the console we have this component which is a CPU that doesn't make any sense the APU doesn't contain a CPU. And yet this CPU pointer is there simply really just for convenience. It's there because we've divided everything into these separate classes that are supposed to be self-contained and encapsulated and yet these objects are effectively reaching into each other and calling each other's methods in a way that totally defeats encapsulation. In any case I won't really go over the details of the APU or the PPU except to note that here in the PPU we have these fields front and back up in the top left here. They're both pointers to images, RGBA images. And those as you might expect are the front and back buffers. I'm not sure if the actual NES hardware has a front and back buffer I think it may just have one image buffer that it constantly reads from as it writes. But in any case the emulator at least does have a front and back buffer so the front buffer is what's always being displayed and the back is where the PPU is always writing data to and when you construct a full frame then we swap the front and back buffers. The INES file header struct is what's used to parse the ROM files. INES is a standard ROM file format and it's the only one supported by the simulator. I didn't modify the struct in any way it's the same as the original code. The instruction struct is actually one I added myself because originally the code had an array of 256 functions. Each index of this array corresponding to an opcode and so you'd look up by opcode the function to run to execute an instruction. I changed the instruction logic so all that same stuff is in a big switch because I don't think the Go compiler will inline any of these functions and so having all these dynamic calls I thought might affect performance and so I wanted to see what would happen if you put it all into a big switch where there's no dynamic calls at all. But it turns out in any case for every instruction there's an associated mode and size and number of cycles and number of page cycles. Page cycles are the, it's the number of cycles when we cross page boundaries. So that's what all that is. We actually don't need the opcode and name fields in this instruction really we could delete them and it wouldn't affect any code. It's just there to make this big long list of instructions easier to read. Lastly in the emulator code we have the mappers I was talking about and mapper is a dummy interface as I described earlier. We have five actual concrete types called mapper one, mapper two, mapper three, mapper four and mapper seven. Don't ask me what happened to mapper five and six. That's just what was in the code. So I went with it maybe five and six he didn't get around to implementing. I don't know if these are standard names known to NES developers or not. In any case as you can see these mappers they'll have different sets of fields and so it makes sense to make them different types and yet we need them to all to be the same type. That's why we have the interface so that we can have a single field of our console struct and then anytime we access that mapper field in our code we're just gonna use a type switch and then in each of the five different cases handle these five different types. In the original code the mapper interface had three proper methods called read, write and step. So what was a read, write and step method implemented on each one of these types I replaced that with three type switches. One for where the read logic was one for where the write logic was and one for where the step logic was. And aside from freeing us from having to think in terms of abstract interfaces when we don't really have to I think the advantage of the switch approach is it means that all of the step logic and all of the write logic and all the read logic are contained in one place. They're not spread across five different files for five different mapper types and it turns out also that these polymorphic functions were all just called in one place. So their code I wanted to inline anyway and you can't really do that if you go the polymorphism route. Lastly here in the original code each one of these mapper types had an embedded pointer to cartridge field. So like the CPU and the APU and the PPU confusingly had a pointer to console field even though like a console contains a CPU not the other way around. Here we had the mappers containing cartridges even though really cartridges should contain mappers. Again, I think the real reason for this odd arrangement aside from just general object oriented confusion is simply because within the methods of the mappers it turns out that we wanted access to the cartridges too but then the proper thing to do then would have been to pass it in explicitly to the methods. As I said in the first video when in doubt explicitly parameterize. It's a rule that will spare you a lot of headaches. So that's it for all the types of the NES package all the emulator logic looking now at the UI package which is everything concerning creating the window and managing the audio and all that. It's considerably simpler. These are all the types. We have first a director which is just sort of an abstract composite of all the core things making up the game you have first a window which in this case is a GLF-W window. We have a pointer to this audio thing which is a struct with first a stream from the port audio library and then also this channel because what happens is that the emulator, the APU spits out floats and sends them on this channel which is then received in the UI package which then copies them to the stream. The view here is an abstract notion of what is currently on screen. It's either the game view the actual running emulation of some ROM or it's the menu view which in the emulator if you hit escape it shows you this menu that gives you an icon for all of the ROMs in your current working directory. So when you're looking at this menu then the view field of the director is a menu view but when you're actually playing a game it's a game view and then this menu view here is always a reference to the one menu view because during the course of the game if you reset the game or you load another ROM from the menu it creates a new game view so view is going to change over time but there's always just one menu view so it's just stored in this field of the director. And then lastly this timestamp field is just when we keep track of how much time has elapsed since the last frame we store the old time in this timestamp field. It's actually a vestige of the object we're going to design and I would have just gotten rid of it and used a simple local variable in a loop. Honestly these other fields probably could do the same thing really we don't really need a director struct perhaps. All of these director fields could probably just be locals in the main function loop but this is what he had in the original code and it's not too offensive so I just stuck with it. Now this texture struct thing is perhaps a little bit misnamed it's not just a general texture it's a specific thing used by the menu view because the menu view has this it presents as grid of icons representing all the ROMs and the current working director as I mentioned and this is what keeps track of all of those images those thumbnails and it's just these lookup tables where you look up by name of the ROM you look up the open jail handle to an actual texture. So the name is Conimus leading and honestly everything to do with the menu view when you hit escape and you see that list of ROMs it's all really half-baked and there are bugs in it which I didn't really bother to fix it doesn't seem like a very useful feature either because you can just restart the emulator so if say I was really taking over as maintainer of this project I would probably just rip the whole thing out but anyway it's there in the code so I wanted to just make sure this is an apples to apples comparison. So we'll actually ignore the menu view and its code and just look at the game view which is composed of a console pointer and the console object just represents the entirety of a new console every time you start a new game or you reset it or you select a new ROM from the menu it just creates a whole new console object and interestingly that's actually the only allocation in the course of the running the program so even though this is written in Go and it's garbage collected while you're just playing a game there shouldn't be any garbage collection puzzles cause there's nothing to collect it's not producing new garbage in the normal course of just running a game. The title field I think is just what gets displayed in the window title bar and hash is used for there's a feature where you can generate screenshots and actually record gifts and there's also in the newer version which I didn't base my code off of there's a still experimental I think save state feature and so the hash is used in the save file names. That's why there's a record boolean that's just a flag that gets set as you start recording and gets unset when you stop recording and then this frames slice of images that's where we just store the images as we play and then when we stop recording then all those frames are taken and generated into a GIF. Lastly the texture field in the game view that's just a handle to an OpenGL texture where we're rendering the actual game so we're just taking the frame buffer and spitting it out to that OpenGL texture of your frame. So those are all the types of the UI package and again the changes I made well I made the view interface a dummy interface whereas before it had a few methods whereas now I'm just using type switches to get all the same effect. The other thing I changed is that both the game view and the menu view had a pointer back to the director struct. So we had another scenario here of confusing recursion where the director contains the views but then the views also point back to the director and really the only reason for this is because it was just a matter of convenience of not having to pass in the director to the methods of game view and the menu view explicitly. After inlining a whole bunch of the methods that's now largely a moot issue but in the few cases where it remains and we have separate functions it still is just better to explicitly pass in these pointers rather than having this nonsensical data structure. So that's everything to do with the data types and then lastly the major thing I did with all of this code is I primarily inlined most of the functions or methods I should say. In virtually every case where a method was called in just one place I would just inline that code rather than have a whole separate function or method. The result is that in our UI package we end up with one core function that's about 500 lines long and then similarly in the NES package we have another core function that's about the same 500, 600 lines long. Depends exactly how you count because in a few cases, well I did this. So here in the core function of UI it's called run is basically just the kickoff function. So main does a trivial amount of business just parsing command line arguments to get the pass of the ROM and then it invokes this run function but see here at the top I have essentially these what I call sub-functions. There's clamp, scroll, set view, load texture and play game and these are all functions that are in here inside this run function because this is the only place where they're called they're not used outside of run and by creating them inside then that's explicitly clear they can't possibly be used elsewhere unless of course they're passed out of the function which they're not. Three of these are separate functions because they're used in more than one place within run but then load texture actually that's only invoked in one place but it's involved enough that I decided it was too ugly to put in line so I made it a separate function except rather than having external to run by putting inside run again it's clear that hey this is only used inside here so you don't have to think about it elsewhere. Now there is this sticking point with these sub-functions being at the top of the function I mean that's the logical place to put them because you can't declare them after the body of the function because then they wouldn't be visible effectively the local variables that had to be declared before they're used what I would really like is if Go or other languages had within a function I could write a nested function let's just say it's a reserved word sub-funk that declares a function that's local to this scope but not really a variable so for one thing the variable will be effectively constant you couldn't reassign these variables that's probably not a big deal but just for a little bit of extra assurance but more importantly I should be able to put these sub-funk declarations at the end of the function they should even be able to go after the last return so just for stylistic purposes you can have the actual proper start of the function which is this line port audio.initialize that should really be the first thing you see in the run function you don't want to have to scroll past these hundreds of lines of code of these sub-functions right? so that right there alone would be a big stylistic win but then also perhaps even more important I really would prefer these sub-functions to not see enclosing scope at all they should not have access to anything else in the run function I don't want to rely accidentally on closure closure is occasionally useful but if I don't really need it it's something I prefer avoiding again wherever possible I want all my code to be explicitly parameterized so anything that these sub-functions see should be stuff that's passed into them explicitly excluding of course actual global variables so keep that idea in mind if you look at this code and say oh geez that's really ugly having all those functions inside those other functions I agree, yeah it's not ideal I just want to have these things which are local to scope but just like top-level functions I shouldn't have to care about their declaration order which I think I do here in this case actually I think one of these functions is called by the other so I had to order them properly so that it's pre-declared and then visible in the other function like I don't have to think about that stuff I just want to have them private to this local function but not have to think about their declaration order or about closure or any of that stuff so anyway let's look at the actual run function here the first thing effectively we do in the program is we initialize the port audio library we create the audio struct object which remember just had a channel which we'll be using to pass the actual sound data from the console object from the APU back out here to the UI layer and then the other field of audio is the stream to create the stream we invoke portaudio.openstream and I don't know the details of the port audio library but basically I think what happens here is the port audio library this function here you pass it is being called every time it wants more audio data and so you can see it's coming from audio.channel it's getting the actual data and it's writing the sample to this output buffer so the slice of float32 is called out here you just put data in there and that's what you'll hear come out your speakers notice that the function uses a select statement so effectively if there's nothing waiting in the audio.channel if there's nothing to receive then it just fills the buffer with zeros so if our emulator can't keep up and supply audio data fast enough then we're just gonna hear silence and in case we get back our stream and we handle errors and we sign the stream to audio.stream and we just make sure that we clean up by closing the stream at the end so that the first statement next we initialize glfw which is just a convenient simple library for opening windows with opengl contexts and it does also some basic keyboard and input handling and all that it's not something you would really use for a professional grade project but it's convenient for a simple little program like this we then initialize this font mask variable which we declare to the top of the run function it's simply an image which contains the glyphs that are gonna be used when drawing the menu basically we just generate this image of character glyphs from this font data constant which is declared in UI underscore types I don't see any reason why we're generating a new image every time we run the program and we could just have it be a set ping in our project directory but this is what the original code did so it didn't change it once we set up the font mask we then create our actual window using glfw note that these window hint calls are telling glfw that we're using opengl version 2.1 which is a very old version of opengl but it's perfectly suitable for our purposes we then initialize opengl itself and then we set up our director object in the curly braces here which I added just to make it explicit that these local variables don't escape this context we're just doing some opengl business of binding the texture that we're gonna use to actually render and notice at the end here we're also setting up the so-called texture object which is part of the menu view thing which again I said is kind of misleadingly named next, if our command line args if the paths is just a length of one then that is assumed to be just the name of an individual NES ROM file and so we're gonna play a game and the play game function creates a console object and sets up a game view object and then itself actually calls the set view function which actually sets up the view in the director object and there's some set of business associated with it so it's not a totally trivial function but it's not very complicated otherwise if the length of the paths is not one then we're gonna start the emulator at the menu view and for that we've already created the menu view so we just pass it to set view directly at this point the initial state of the program is ready to go and we enter the main loop each time in the loop we first just clear the window then we wanna determine how much time has elapsed since the previous frame so we use this convenient function from glfw get time and we get the delta between the last timestamp and the new time that gets us dt which after the first frame it should be a very, very small value this window is gonna be tied to your refresh rate it uses vingsync so at 60 hertz it's gonna be about 0.016 about 16 milliseconds or something like that then we have this check to make sure the director's view is not nil before we proceed that actually I think we don't need it that's vestigial from the original code and it was in there I think because well this code was in a separate method and so it was trying to account for the more general case of well hey maybe that might be nil but it's very clear now with all the code inline that it's never gonna be nil so we don't actually need that so the real next line is this type switch and we're switching over the type of the director's view which is either gonna be a pointer to game view or a pointer to menu view and the game view is the one we really care about that's when we're actually doing emulation inside this case we first check if dt happens to be greater than one because maybe something happened in system and there was a really long gap between the last frame or also on the first frame this is gonna be a very large value because d.timestamp wasn't previously set so I think it actually is a false to zero in these special cases we treat dt the delta time is being equal to zero which will mean that in our emulation we don't actually do anything we're not gonna advance the simulation any steps in this case next we check if the players hit escape or if they've hit a certain button on the one of the two joysticks that signals us to go to the menu view and so we call set view and pass in the menu view next we update the controllers and once again I've dropped into a pair of curly braces just so that all these declared local variables you don't have to think of outside of this little scope and here we get keyboard and joystick input from these utility functions read keys and read joystick which themselves use GLFW this turbo variable will be true for three frames and it'll be false for the next three frames and just goes back and forth every six frames and that's just basically to simulate the effect of turbo buttons on certain NES controllers where you just hold it down and it automatically repeats and once we read the input using these util functions we then pass them on to the NES module via these functions set buttons one and set buttons two and then we call the core function of the NES module which is called step seconds and that's the other main function we'll walk through but very briefly it steps through however many simulated cycles of the NES hardware given the amount of time so you know the NES ran at a certain clock rate so it figures out how many cycles of the CPU and so forth. After advancing the hardware simulation we then actually render the frame using OpenGL and I won't go through the details here because they're a little confusing but basically we need to figure out the dimensions and figure out how to represent in the OpenGL coordinate system for the window how to draw the texture and the texture is taken from the NES.buffer function that you pass in the console and you get back the front side buffer which is just an image and then we use that set texture utility function will make sure that OpenGL actually draws that texture image. Everything in the curly braces here that's where the actual drawing is done after computing what the dimensions should be in terms of the OpenGL window coordinates. Last thing here there's if v.record that is if the record flag is true then we're gonna want to append the current frame to the frames slice and notice that we have to copy the image from the front buffer because the front buffer is gonna be actually overwritten in the next frame so we would lose it if we just passed a reference to it so we need an actual copy and that gets stored in v.frames. That's the whole case for pointer to game view but then there's also pointer to menu view when we're looking at the menu and I won't go into any details here it's just basically figuring out how to draw this grid of icons and display and also there's logic for you can use the cursor buttons or the gamepad to navigate among the icons and then select them and it's actually I should say buggy if you try and run the program you'll notice that if you hit up from the top line of this grid you'll then crash the program and I didn't bother to fix that. After the type switch of handling either the game view or the menu view we then invoke on the window the glfw window object you invoke this swap buffers method which swaps the front and backside buffers of our window program not the front and backside buffers of the emulator of the emulated NES hardware but the actual window so this is what really displays the frame and also by default it's a v-synced so it's also what's effectively regulating our time it's why every time we go through this loop DT is usually going to be one sixtieth of a second it's going to be about 16 milliseconds. After swapping buffers we then pull the events that's what actually reads the input from the operating system like Windows sends messages to your program that's what actually reads them and then in the next iteration of the loop when we call the util functions which in turn call the glfw functions that read the input if we didn't call pull events then our program would never process the messages and glfw would just say hey there's no input even if there was so that's why we call this function and that's the very last thing in our loop and then once we leave the loop the last thing the program does in the run method is it sets the view to nil this doesn't really do anything it's just clean up I don't know if it's really truly necessary you could probably leave that out and the program would terminate just fine so that's all of run and again that's the core of the UI package looking now at the step seconds function which is the core of the NES package there's sort of a similar pattern here at the top we have a number of private functions of sub-functions whatever you want to call them including triggerRQ which is used in a couple places and then actually stepPPU and stepPPU are only called in one place but they're complex enough I thought they should do their own separate functions except for the fact they are only called in this one function step seconds so I just made them private to step seconds we won't go through the details of those functions so just looking here at the actual business of step seconds first we assign to cycles a CPU frequency which is a constant multiplied by the actual number of seconds that we're going to be stepping through in this interval for this frame and then we're going to loop while cycles is greater than zero so each time after executing the CPU instruction we will decrement cycles by the number of cycles that one instruction took and at some point we're going to either hit zero or go below zero and then we're going to break out of this loop and so that's what this CPU cycles variable will be set to eventually we will assign it the number of CPU cycles for this one instruction in this iteration of the loop first off we just assigned to CPU console.CPU just as a convenience make it less for both to access the CPU if CPU stall is greater than zero that's the number of cycles we want to stall for so we decrement CPU stall by one and then CPU cycles for this iteration will just be one so in some sense it's like we executed a no op instruction of one cycle but there's no actual instruction we're consuming so not exactly the same thing but close in any case if we're not stalling then we want to execute an actual instruction but first we need to check if there was an interrupt triggered and if so then we run the interrupt first and there's two kind of interrupts there's an IRQ and then a NMI and nonmasculine interrupt details of all that I'm not clear on anyway after the interrupt handling we then execute an actual instruction we read byte here we'll read from the next program counter address it will get the actual instruction byte the instructions in NNS are always a single byte that gives us an op code which we pass to execute instruction along with the console and then execute instruction does all the business for that particular op code that will update CPU dot cycles we subtract to get how many cycles that one instruction took that accounts for simulated in the CPU and most of the core logic is in that execute instruction function then for the PPU however many CPU cycles we executed we then need to run through three times as many PPU cycles because the two run in sync but the PPU actually runs at a three times faster clock rate unlike the CPU the PPU isn't being fed instructions the PPU just has its own internal state with registers and it looks at memory but it's just a fixed function processor that every cycle it's doing the same logic right and all that core logic is in the step PPU function which we call for however many CPU cycles times three we had but then for certain ROMs which use this particular mapper the one we're calling mapper four there's some extra logic that needs to be done so that's why we have this type switch here come to think of it I'm not really sure why this type switch is outside the step PPU function seems like it should just be the last thing done in that function but anyway here it is lastly after handling the PPU we then step the APU for however many CPU cycles we did because the APU unlike the PPU runs at the same clock rate as the CPU in fact I believe it actually is part of the same component I think the APU and the CPU are actually really the same package or something in any case the step APU like the PPU doesn't really operate on instructions it's just every cycle does the same thing it follows the same logic rather and whereas the PPU in our emulator is generating the back buffer and then swapping that with the front buffer every time it creates a new frame the APU here is feeding that channel of float 32s that gets sent out to the port audio stream and it just has to feed that data fast enough to make sure we have continuous sound after the APU very last thing in our loop we just decrement cycles by however many CPU cycles we step through in this iteration so I hope that gives you a basic foothold in understanding how this emulator works and you could probably then delve into the details yourself I think what you will find looking at the original version of the code and my version of the code is that my version is probably much easier to follow because it's not cut up into tiny little methods so you coming fresh to this code base trying to find your way around what is neatly sequential or at least a neat sequence of branching that's all clearly put together in one place in code you're not jumping all around and you're not trying to interpret these names and try and think of what they might mean these function names or method names what they might mean in some general case I think that's part of the problem with atomizing your code into really small functions and methods it's because generally when I look at a function I'm trying to think of well there's a function here it must have some general case it's trying to account for that's why it's its own separate function but typically an object-oriented code and also even in some non-object-oriented code people when they atomize their code into really tiny functions and methods they're thinking in overly general terms often in a way is actually really misleading for example in the original version of this code there are a number of constructor functions that I got rid of because I just inline them and the problem with those constructor functions in which in go they're not literally constructors they're just regular functions that by convention say new X to create a new X object but in any case when you see such a function you're thinking well so this must be something done in many different parts of code and so it must be accounting for some general case of creating X but it turned out that actually no that function's only called in one place and so the reason that constructor does what it does is because you have that one particular use case in mind you're not really accounting for the general case at all by creating a standalone function you're actually misleading the reader into thinking that some general case has been accounted for when in fact it hasn't on top of that you may often I think end up wasting your time because when you define a function you feel that obligation to try and generalize the code and so you'll waste time solving the general case of a problem that you don't even have you just have the specific problem and if you don't unnecessarily split code off into separate functions you'll much less likely waste time trying to generalize your solutions so I do think that my procedural version of the code is considerably easier to read but keep in mind part of the reason I chose this project is because the emulation logic the NES package in particular it's a good example of code which if you don't understand the domain you're not really gonna follow the code at all anyway you're certainly not gonna understand the details so I hope the contrast illustrates here that the atomization into small methods doesn't really buy anything in terms of comprehensibility because it's already incomprehensible to someone who doesn't understand NES hardware anyway yes it is arranging the code into less intimidating bite-sized chunks but because you don't understand the domain you have no context for what those chunks really do and so you're not really gonna understand anyway you can understand the logic of simple short functions more easily I suppose but you're not gonna understand the whole any better in fact it may end up being more confusing and harder to grasp because everything is fractured. The UI package provides a different kind of contrast because that's a package that basically anyone should be able to understand that it's not any domain specific stuff at all really it's just creating a window using various libraries like GLFW and port audio and so forth but I think even there too you will find that the procedural version is particularly easier to understand because the core loop of the program is all there presented in more or less a straight sequence rather than being broken up. However there are places where I've inlined the code and the result is sometimes perhaps a little more difficult to understand just because you have this long function that then naturally has a lot of local variables to look at and consider and that is legitimately a problem that's probably the biggest drawback potentially to having longer functions is that over the course of a long function you're accumulating typically more and more variables that you have to think about and that can get confusing. In fact when I think about the complexity of a function I don't really think so much in terms of so-called cyclomatic complexity of like how deeply you're nesting loops and branches and so forth I mean that that is a concern but for me the real measure of complexity is well how many variables do I have to keep track of. Now in most languages including Go within a function you can have subscopes and so the variables you declare there you don't have to think about for the rest of the function. However there's this problem in the other direction where in the subscopes I don't necessarily want to think about everything from the enclosing scopes. As I mentioned in the first video it'd be really great if we had some language construct which solved that problem which allowed us to introduce what I call the use block where you drop down to a subscope but at the top in the header you have to specify exactly what you're taking from the enclosing scope and otherwise you don't see anything from the enclosing scope. In fact use blocks would be more like a one-time call sub function because the variables you bring from the enclosing scope would actually be copies just as if we were like calling a function and passing in arguments. You know the argument variables aren't the same as the parameter receiving variables right they're separate copies. So I want basically the same thing within my functions. So I hope you just keep in mind when looking at some of this code and you say oh gee that one function looks really ugly. Use your imagination a little bit and imagine if we did have the subfunk feature I talked about earlier and this use block thing imagine if you employed those how the parts that seem hairy could potentially be a lot cleaner. Though even then perhaps there are places in code where I went too far in inlining all the methods. I just wanted to take it to pretty much the logical extreme to demonstrate that what's supposed to be a horrible horrible sin according to many authorities is actually really not all that scary at all. I mean it can get a little problematic but it's in many ways much better than the atomizing everything to tiny methods approach. So you may choose in your code to dial back and split things more often to their own separate functions. And I probably would too a little bit. I hope what I've illustrated at the very least with this code is you should not be seeking to constantly separate stuff off into functions. You should only be separating stuff off into functions until you actually have a problem. You have a problem of hey this code is too complicated or hey I have this piece of code which gets repeated in multiple places and so it definitely needed a function.