Yes - there are cases where reciprocal references naturally are the most practical way to solve various problems, and the one you arrived at is one of those.
What’s wrong with that? Not many - in a long life system, with a lot of creation and destruction of objects you can have cases where the Python collector Garbage will get lost and will "leak memory" - but even you will not have so many occurrences in a single game, and neither the Collector grabage gets lost so much. (In case you create new instances of the player in other "levels" and the player has himself callbacks to handle keyboard/mouse/joysitick events you could have more drastic side effects).
What does language have to ensure that you have no problems with reciprocal references? The "weakreferences" - are weak references in which, once the original object no longer exists, the referenced object points to the "empty" - this avoids potential problems of persisitndo objects in memory when no longer in use. I talk more of them down.
As for the "semantic problem" of - in case - the game needs to know who the player is, and the player needs to know what the game is, this is in fact a 'no problem'. Normal. In some projects it is normal to have a unified record (Registry) of some of the object types in the game - for example, the "game" could be a "Singleton" - in this case the player does not need a "self.game" - will always have a "game" global available where it can call methods. Similarly, the "player", if it is a single object (without simultaneous multi-players, etc...) could be a global resource, finished in the appropriate methods of the game, at the end of the game, phase change, etc....
Keeping them as references in the reciprocal instances is plus ordered and organized that the global resources - therefore it has no problem at all - and it facilitates rightly multiplayers, demo modes, computer-controlled player (which can be another instance of player, be in the same "game" but elsewhere on the map, for example).
To keep the answer complete, there is a "beautiful" and independent form of programming language, to avoid cross references, if you prefer: it is in all the player’s methods (that need it), you pass the game instance as parameter. I mean, instead of self.player.update()
, the signature of the "update" method of Player would be def update(self, game):
and your calls would be self.player.update(self)
. I’ve never done so - but it’s perfectly feasible.
So in fact, it’s only the practical question of avoiding hypothetical memory or resource leaks, which is solvable with the weakref I mentioned above. In Python you can do this:
from weakref import proxy
class Game:
def __init__(self):
self.player = Player(self)
def redraw(self):
print(self.player)
class Player:
def __init__(self, game):
self.game = proxy(game)
def update(self):
try:
self.game.redraw()
except ReferenceError:
# self.game foi apagado
pass
And testing at the terminal:
In [25]: g = Game()
In [26]: p = g.player
In [27]: p.update()
<__main__.Player object at 0x7fc3a856a438>
In [28]: del g
In [29]: p.update()
In [30]:
If you choose to do this, it is worth reading the documentation of the weakref module - there are more practical ways than try...except ReferenceError:
to use them, for example dictionaries and lists where items are automatically deleted if the instance they point to no longer exists.
In time, to give a real example of how these "cross-references" are used fairly in the context of games: I really like using the library Pygame - Among its few advantages is to have a simple api for 2D drawings and real-time control of keyboard and mouse events, and not to be a framework: the developer is responsible for the entire game flow. In Pygame the question of references to objects in the game is treated with what he calls "groups of sprites" - a game object (has to be a specific subclass), has a "Kill" method, and may be in one or several "groups". These groups work as sets (sets) in Python - for example, I could have a group with all the objects moving in the game - be it shots, ships, the player, etc.. and a group with a single object (the single instance of Player) - So the player is in several groups - and the simplicity of the thing is: you call the method kill
player, and himself removes from all groups (so it will not be called in the next update of "tudp what moves"). Note that for this there is implicitly the "reciprocal reference": that is, the object "knows" in which groups it is. And everything works beautifully, without side effects, and transparently for the final programmer (so much so that I don’t even know if pygame uses weakrefs in its pygame.sprite.Group
or not.)
If it’s a good idea I don’t know, but the memory won’t duplicate because both variables will point to the same reference.
– Jéf Bueno
I was wondering how the player needs to know the state of the game to play. Who will call the play will not be the game itself? If so, he already has access to his own state.
– Woss
I’m gonna have to rethink the whole system the way it’s never going forward.
– Mateus Cardoso Silva
@Mateuscardososilva congratulations, began to understand... :)
– Maniero
"I was wondering how the player needs to know the state of the game to play. " = there are many very common situations - if there is a map with walls in the game, for example, in a player’s movement method, he needs to have access to this map to know if it doesn’t hit a wall or another object. The alternative would be to pass the logic of moving the object to the Game class, which would be counterproductive - or pass a game reference in all calls to the Move method - which is not so bad (alias, I will update my answer with this)
– jsbueno