You can get the full details in the readme for cxr.state.state.
In summary, I realized while working with pygame that I needed a consistent and repeatable way to give stateful behavior to game objects. So I figured out a way to utilize a doubly-nested function as a decorator. So I can do something like this...
@player.add_state("falling")
def player_falling(event):
self.gravity += 0.1
# etc, behavior goes here
And that's the usual boilerplate pretty much entirely eliminated.
The decorator is a bit hard to grok, but take a look and then I'll explain it:
def add_state(self, key):
def outer_wrapper(f):
def inner_wrapper(event):
f(event)
if key in self._states:
raise StateError(f"State add failed: {self.name}({self.key}) StateManager already has a state with key {key}")
else:
self._states[key] = inner_wrapper
if not self._current:
self._current = self._states[key]
self._current_key = key
return outer_wrapper
The important behavior is that if your decorator is a function that a) takes arguments and 1) returns a function, then that returned function is what is applied to the decorated function. So @player.add_state("falling") is calling the add_state function, which returns outer_wrapper, and that function is applied as the decorator which catalogues the state accordingly. This pattern utilizes something called a closure.
The decorator for the controller (the core function for default behavior and whatnot) is much simpler:
def controller(self, f):
self._controller = f
And decorators really CAN be that simple. Its applied behaviors can be counterintuitive, but if you're finding yourself wrapping a lot of unrelated stuff in very similar-looking stuff, a decorator is probably what you need.
