Is it valid for two objects to have reciprocal references?

1

Is this an acceptable practice or is it a gambiarra and should it never be done? Is there any solution to avoid this? It turns out that the object player needs to know the state of the game to be able to play, but the game has it so it would have been duplicating itself in memory (maybe not in Python but in another language)?

class Player(object):

    def __init__(self, game):
        ...
        self.game = game


class Game(object):

    def __init__(self):
        ...
        self.player = Player(self)
    
asked by anonymous 11.07.2018 / 19:14

1 answer

2

Yes - there are cases where reciprocal references are of course the most practical way of solving various problems, and the one that you have come up with is one of those.

What's the problem? Not many - in a long-lived system with lots of creation and destruction of objects you may have cases where the garbage collector of Python will get lost and will "leak memory" - but you will not have so many occurrences like this in a single game, and nor is the recording collector lost so much. (In case you create new instances of the player in other "phases" and the player has itself callbacks to handle keyboard / mouse / joysitick events you could have more drastic side effects.)

What does language have to ensure that you do not have problems with reciprocal references? "Weakreferences" are weak references in which, since the original object no longer exists, the referencing object points to the "empty" - this avoids potential object problems persisting in memory when they are no longer in use . I speak more of them below.

As for the "semantic problem" of - in this case - the game needs to know who the player is, and the player needs to know what the game is, this is indeed a 'no problem'. Normal. On some projects it is normal to have a unified 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 global "game" available where he can call methods. In the same way, 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. ..

Keep them as referrals in reciprocal instances is more ordered and organized than global resources - so it has no problem at all - and facilitates just multiplayers, demo modes, computer controlled player (which can be another player instance, to be in the same game, but in another part of the map, for example).

To keep the answer complete, there is a "pretty" and independent of the programming language, if you want to avoid cross references, if you prefer: it is in all methods of the player (that need it), you pass the instance of the game as a parameter. That is, instead of self.player.update() , Player's signature update method signature would be def update(self, game): and your calls would be self.player.update(self) . I have never done so - but it is perfectly feasible.

So, in fact, there's only the practical question of avoiding hypothetical leaks of memory or resources, which is resolvable 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 on 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's worth reading the weakref module documentation - there are more practical ways than try...except ReferenceError: to use them, such as dictionaries and lists where items are deleted automatically if the instance they point to stops.

In time, to give a real example of how these "cross-references" are used a lot in the context of games: I really like using the kill method of the player, and it removes itself from all groups (so it will not be called in the next update of "all that moves"). Notice that for this there is implicitly the "reciprocal reference": that is, the object "knows" in which groups it is. And everything works beautifully, with no side effects, and transparently to the final programmer (so much so that I do not even know if pygame uses weakrefs in its pygame.sprite.Group or not.)

    
11.07.2018 / 22:42