Game Week 3

SFML logo

The past two weeks, we've faced some of Python's language typing limitations as we try to create generic classes. We're officially switching to C++/SFML. See last week's post for some details and a specific problem example in Python.

We've always had C++ on our learning wishlist, but it's finally time to do it. I took C Programming as my final elective before undergrad graduation, and I finished the entire semester's course material (all labs) within 2-3 weeks (it was at a community college, so take that with a grain of salt if you please) plus an extra "honors project" (I probably would have done another one if COVID hadn't hit right then).

C++ gives us access to deep control over memory in our program. Python is beautiful to the eyes and can be made to do quite a lot, but we ran into problems with the typing system, especially as it relates to generics. We had a composable GameNode class that has handle_input, update, and render. It can have any number of GameNode children, i.e. it's recursive. Now that it's time to work on GUI, we wanted a similar recursive composable class that has all the same handle_input/... functionality as GameNode but with a stronger guarantee: instead of children being any GameNode, GUIElements' children are also GUIElements. Then our GUIElement has recursive functions for drilling down to be able to quickly figure out exactly which element the mouse is hovering over, rather than checking overlap with every single GUI element.

Specifically, we don't want to iterate over the rectangles of every single element on the screen - we should only check a given element if the mouse is hovering over it, then check that element's children to see if the mouse is on top of any of those, and recurse until we know the exact element the mouse is hovering over. This saves a lot of unnecessary iterations, calculations, and overhead.

We had some code like this:

from typing import List, Optional, TypeVar

from ..game_node import GameNode

class GUIElement(GameNode['GUIElementType']):
    _parent: 'Optional[GUIElementType]'
    _children: List['GUIElementType']
    ...

GUIElementType = TypeVar('GUIElementType', bound=GUIElement, covariant=True, default=GUIElement)

Our original GameNode can have a parent that is also a GameNode, and children which are also GameNodes. Now we want a GameNode-like object GUIElement which is like a GameNode and can be treated the same as a GameNode, but we want to make a stronger assertion that its parent will be a GUIElement and its children will be GUIElements. We had upgraded our project from Python 3.12 to 3.13 specifically for the default argument of typing.TypeVar(..., default=...), but as mentioned in our previous post, our seemingly simple example kills Mypy server instantly.

Additionally, we had to make concessions with Python typing as we sometimes accepted typing errors. I searched high and low for a solution but eventually gave up because it was actually time to make progress on our codebase:

class GUIElement(GameNode['GUIElementType']):
    _parent: 'Optional[GUIElementType]'
    ...
    @property
    def parent(self) -> 'GUIElement|None':
        return self._parent

    @parent.setter
    def parent(self, value: 'GUIElement') -> None:
        # Unnecessary "cast" call; type is already
        # "GUIElement[GUIElement[GUIElementType@GUIElement]]"
        # Pylance reportUnnecessaryCast
        self._parent = cast(GUIElement, value)
        # Cannot assign to attribute "_parent" for class
        # "GUIElement[GUIElementType@GUIElement]*"
        #   Expression of type "GUIElement[GUIElement[GUIElementType@GUIElement]]"
        #       cannot be assigned to attribute "_parent" of
        #       class "GUIElement[GUIElementType@GUIElement]"
        self._parent = value # as hideous as this was, we had to accept it and move on.

I've been using Python for over 10 years now, but do we really want to keep fighting with the typing system? I think it may finally be time to move on..

Onto C++

So, it's time to dig up our old knowledge of C and start applying it to C++. As I was on StackOverflow trying to figure out the core issues with Python to get it working, I took a detour to Tag search, chose Python, and then sorted by highest votes. This is what we get. Now I know more about Python than I ever did before. And so, this seems like a great way to learn some really important topics for a given language, especially one so complex and contentious as C++. Here's the same search but for C++. This includes a post about The Definitive C++ Book Guide and List since C++ books are cheaper by the dozen and most of them are bad. REALLY BAD. Most of them encourage you to do bad things that would make the true gray beards of C++ rant and rave.

Now we can get more strict guarantees and can ideally ensure the generic typing that we want. It will take time to learn, but I was already pushing against my own experiential limits with Python, so why not just apply that hard work to another valuable language? We learned some generic typing back in Java, but I can't take Java seriously since it's Boilerplate: The Language and their restrictive licensing that caters to mega corporations (thanks, Oracle).

Learning C++ will go hand-in-hand with the knowledge that university dumped in my brain about operating systems. The specific class I took recommended the book Operating Systems: Three Easy Pieces by Remzi H. Arpaci-Dusseau and Andrea C. Arpaci-Dusseau (although operating systems are commonly written in C, not C++, but they still have a lot of valuable ideas and solve a lot of similar problems as we will have in game development). The challenge will be with navigating the multitude of different C++ standards and avoiding learning things incorrectly from outdated or ill-informed resources. C and C++ are well known for the horrendous bugs they can allow (buffer overflow/underflow, use after free, and the list goes on) because of various pointer misuses, and it is paramount to understand memory layout and apply standards and best practices appropriately.

Game. Duh.

And.. the whole point of this is to build a game! For that, we will be using SFML. They have a dedicated Learn page where I've navigated to download the SFML Game Development book's examples source code, where I've already learned how to keep the game loop in sync with the realtime clock even when the update loop or the screen render takes too long (maybe Pygame was doing that for me already, but now we know for sure that we are handling it properly in C++).

I originally forgot to write a blog post last week, but recognized how much work I had actually done and wanted to make sure it's clear that I'm not sitting on my hands, so I went ahead and wrote last week's and back-dated it for consistency 🙂 Thanks for joining us this week! See you next week!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.