with - python function wrapper decorator




python et pygame-que s'est-il passé pendant l'exécution de ce wrapper? (2)

J'espère que la générosité attirera une personne qui connaît une ou deux choses sur le fonctionnement interne de PyGame (j'ai essayé de regarder le code source ... il y en a beaucoup) et peut me dire si cela est vraiment lié à la présence de threading.py, ou s'il y a un peu de pratique que je peux éviter en général pour éviter que cela ne se produise dans d'autres projets PyGame.

J'ai créé un wrapper pour les objets qui explosent quand obj.kill() est appelé.

def make_explosion(obj, func):
    def inner(*args, **kwargs):
        newX, newY = obj.x, obj.y
        newBoom = Explosion(newX, newY)
        allqueue.add(newBoom) #an instance of pygame.sprite.Group
        return func(*args, **kwargs)
    return inner

Il obtient les coordonnées x et y de l'objet, puis crée une nouvelle instance d' Explosion à ces coordonnées, l'ajoute à la allqueue que j'utilise pour s'assurer que tout est mis à update pendant le jeu, et retourne finalement la fonction qu'il enveloppe. cas, obj.kill . obj.kill() est une méthode pygame.sprite.Sprite qui supprime l'objet de toutes les instances de sprite.Group auquel il appartient.

Je voudrais alors simplement envelopper la méthode de la manière suivante, lors de la création d'une instance d' Enemy .

newEnemy = Enemy()
##other code to add new AI, point values…eventually, the following happens:
newEnemy.kill = make_explosion(newEnemy, newEnemy.kill)

Quand j'ai couru le jeu, les explosions sont apparues dans des endroits aléatoires, pas n'importe où près des coordonnées x et y réelles de l'objet. Pour l'anecdote, il ne semblait même pas que cela se produisait à l'origine de l'objet (x, y) ou même sur les chemins qu'ils ont parcourus pendant leur brève existence à l'écran (je suis plutôt bon dans mon jeu, pas pour me vanter) , donc je sentais que je devais exclure que x et y étaient assignés au moment où la méthode a été enveloppée.

Un peu sans but, j'ai été autour et changé le code à ceci:

def make_explosion(obj, func):
    def inner(*args, **kwargs):
        allqueue.add(Explosion(obj.x, obj.y))
        return func(*args, **kwargs)
    return inner

Cette modification a fait fonctionner "comme prévu" - lorsque la méthode self.kill() l'objet est appelée, l'explosion apparaît aux coordonnées correctes.

Ce que je ne comprends pas, c'est pourquoi cela fonctionne! Surtout en considérant la documentation de PyGame, qui stipule que kill() ne supprime pas nécessairement l'objet; il le supprime de tous les groupes auxquels il appartient.

à partir de https://www.pygame.org/docs/ref/sprite.html#pygame.sprite.Sprite.kill -

tuer()

supprimer le Sprite de tous les groupes kill () -> None

Le sprite est supprimé de tous les groupes qui le contiennent. Cela ne changera rien à l'état du Sprite. Il est possible de continuer à utiliser le Sprite après avoir appelé cette méthode, y compris en l'ajoutant à des groupes.

Donc, même si j'ai accidentellement la solution à mon problème, je ne le comprends pas du tout. Pourquoi se comporte-t-il de cette manière?

EDIT: Sur la base de quelques premiers commentaires, j'ai essayé de reproduire une condition similaire sans l'utilisation de la bibliothèque PyGame, et je suis incapable de le faire.

Par exemple:

>>> class A(object):
...     def __init__(self, x, y):
...             self.x = x
...             self.y = y
...     def go(self):
...             self.x += 1
...             self.y += 2
...             print "-go-"
...     def stop(self):
...             print "-stop-"
...             print "(%d, %d)" % (self.x, self.y)
...     def update(self):
...             self.go()
...             if self.x + self.y > 200:
...                     self.stop()
>>> Stick = A(15, 15)
>>> Stick.go()
-go-
>>> Stick.update()
-go-
>>> Stick.x
17
>>> Stick.y
19
>>> def whereat(x, y):
...     print "I find myself at (%d, %d)." % (x, y)
...
>>> def wrap(obj, func):
...     def inner(*args, **kwargs):
...             newX, newY = obj.x, obj.y
...             whereat(newX, newY)
...             return func(*args, **kwargs)
...     return inner
... 
>>> Stick.update = wrap(Stick, Stick.update)
>>> Stick.update()
I find myself at (17, 19).
-go-
>>> Stick.update()
I find myself at (18, 21).
-go-
>>> Stick.update()
I find myself at (19, 23).
-go-

Donc, tout de suite, je remarque que whereat() est appelé avant le changement de coordonnées sous Stick.go() donc il utilise x et y juste avant l'incrémentation, mais c'est bien; Je pourrais facilement changer le wrapper pour attendre que go() soit appelé. Le problème n'est pas présent ici comme c'était le cas dans mon projet PyGame; les explosions n'étaient même pas "ennemies adjacentes", elles apparaissaient dans toutes sortes d'endroits aléatoires, pas aux coordonnées précédentes (x, y) du sprite (si c'était le cas, je n'ai peut-être même pas remarqué le problème!).

MISE À JOUR: Après qu'un commentaire d'utilisateur m'a fait me demander, j'ai un peu creusé autour d'Internet, cProfile couru cProfile sur le projet en question, et voilà, PyGame utilise définitivement threading.py . Malheureusement, le même Internet n'était pas assez bon pour me dire exactement comment PyGame utilise threading.py , donc apparemment je dois lire sur la façon dont les threads fonctionnent même. Oi. :)

Après avoir lu un peu le code source de PyGame (bleh) j'ai trouvé un module qui semble arbitrer quand et pourquoi PyGame engendre un thread. De ce que je connais peu sur les discussions, cela peut causer quelque chose comme une condition de course; x n'est pas défini 'dans le temps' pour que l'encapsuleur s'exécute, et donc il (l'objet Explosion ) finit au mauvais endroit. J'ai vu d'autres erreurs surgir dans un projet différent qui est un peu plus lourd sur les maths; elles sont plus proches des décharges de base, mais elles impliquent généralement que le pulse est incapable d'obtenir un événement, ou de temporiser attendre un événement, ou quelque chose de similaire (j'ai une longue trace de pile qui traîne au cas où quelqu'un aurait des problèmes en train de dormir).

En fin de compte, je soupçonne que c'est quelque chose que je peux éviter en m'adonnant à certaines pratiques, mais je ne sais pas ce que ces pratiques seraient réellement, et je ne sais pas comment empêcher PyGame d'arbitrer quels processus obtiennent leur propres fils et quand c'est pire que de ne pas le faire. S'il y a une théorie unifiée pour tout cela, j'aimerais l'apprendre.


Je vais supposer que vous utilisez pygame.sprite.Group() pour grouper les sprites, puis les afficher à l'écran. Corrigez-moi si je me trompe. Ensuite, vous créez un collison qui crée en utilisant les attributs de l'image-objet, puis "détruit" l'image-objet, ce qui signifie que tous les groupes créés à l'aide de pygame.sprite.Group qui contient cette image-objet ne contiendront plus cette image-objet. Maintenant, étant donné que vous mettez un lien dans votre question, je vais supposer que vous avez déjà lu les documents.

Donc, voici le code que vous avez posté avec des commentaires de ce que chacun fait.

#so here is your wrapper which will call make explosion before it kills the sprite.
def make_explosion(obj, func):
    #you call kill and so here it goes.
    def inner(*args, **kwargs):
        #you add the explosion to your queue
        allqueue.add(Explosion(obj.x, obj.y)) #create an instance of explosion inside.
        # return to the outside function `make_explosion`
        return func(*args, **kwargs)
    #you call inner second and then go to the inner.
    return inner
    #after it returns inner kill is called

Cela signifie qu'il appelle make_explosion, puis appelle kill sur l'image-objet qui le supprime de tout groupe. Veuillez me dire si vous avez besoin d'éclaircissements à ce sujet ou si vous avez besoin d'une réponse meilleure ou plus profonde. Ou dites-moi si vous avez d'autres choses dont vous avez besoin.


Malheureusement, je dois dire que pour cette question particulière, l'erreur était la mienne.

Dans la classe Explosion , dont le code n'était pas inclus ici, je ne mettais pas correctement à jour ses valeurs self.x et self.y lors de sa création ou sa méthode self.update() . Changer ce code, de concert avec le raccourcissement du code que j'ai affiché ici, a fait disparaître ce problème et d'autres.

Je sais que c'est un peu gauche d'accepter votre propre réponse et de conclure la question sommairement, surtout après que le problème a été dans une partie différente du code, surtout après avoir mis en place une prime. Mes excuses.







python-decorators