Python and pygame - what happened during the execution of this shell?

It is my hope that generosity will attract a person who knows something or two about PyGame's internal work (I tried to take a look at the source code .. there are tons) and can tell me if this is really related to the presence of threading.py, or if there is some practice I can generally avoid this from happening in other PyGame projects.

I created a wrapper for objects that explode when calling obj.kill() .

 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 

It takes the x and y coordinates of the object, then creates a new Explosion instance at these coordinates, adds it to allqueue , which I use to make sure that everything is update d during the game, and finally returns the function that it wraps - in this case obj.kill . obj.kill() is a pygame.sprite.Sprite method that removes an object from all sprite.Group instances to which it belongs.

Then I would simply wrap the method as follows: at the time of creating the Enemy instance.

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

When I started the game, explosions appeared in random places, and not somewhere near the actual coordinates of the object and y. Anecdotally, it didn’t even seem that it happened on the object (x, y) of origin or even on the roads that they traveled during their short existence on the screen (I am pretty good in my game not to brag), so I felt that should have excluded that x and y were assigned during method wrapping.

A little aimlessly, I futzed around and changed the code to this:

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

This change made it work "as intended" - when the self.kill() method is self.kill() , the explosion appears in the correct coordinates.

I do not understand why this works! Especially considering the PyGame documentation that says kill() does not necessarily delete the object; he simply removes him from all the groups to which he belongs.

from https://www.pygame.org/docs/ref/sprite.html#pygame.sprite.Sprite.kill -

kill ()

remove Sprite from all groups kill () -> None

The sprite is removed from all groups that contain it. This will not change anything about the state of the sprite. You can continue to use Sprite after calling this method, including adding it to groups.

So, although I accidentally solved my problem, I don’t understand this at all. Why does he behave this way?

EDIT: Based on some early comments, I tried to reproduce a similar condition without using the PyGame library, and I cannot do this.

Example:

 >>> 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- 

So, I immediately notice that before changing the coordinates in Stick.go() is whereat() , so it uses x and y immediately before incrementing, but this is fine; I could easily change the shell to wait until go() called. The problem is missing here, as it was in my PyGame project; the explosions were not even “enemy neighbors”, they appeared in all kinds of random places, and not in the sprite of the previous (x, y) coordinates (if it were, I might not have noticed the problem!).

UPDATE: After a user comment made me think, I cProfile Internet a bit, ran cProfile in the project in question, and here it is - PyGame definitely uses threading.py . Unfortunately, the same Internet was not good enough to tell me how PyGame uses threading.py , so apparently I need to read how threads work. Oi. :)

After reading PyGame (bleh) source code several times, I discovered one module that seems to decide when and why PyGame spawns a stream. From what I know about streams, this can lead to something like a race condition; x is not defined "in time" to execute the shell, and therefore (the Explosion object) ends in the wrong place. I saw other errors occurring in another project, which are a little harder in mathematics; they are more related to core dumps, but they usually include a pulse inability to receive an event or an event wait timeout or something similar (I have a long stack trace v lying around in case someone has a sleep problem).

At the end of the day, I suspect that this is something that I can avoid by adhering to certain practices, but I don’t know what these practices really are, and I don’t know how to prevent PyGame from arbitration, the processes get their own threads, and when it's worse than NOT. If you have a unified theory, I would love to know about it.

+7
python python-decorators pygame
source share
2 answers

Unfortunately, I have to say that for this particular problem the error was mine.

In the Explosion class, whose code was not included here, I incorrectly updated its self.x and self.y during its creation or its self.update() method. Changing this code, combined with the shorthand for the code that I displayed here, caused this problem, while others disappeared.

I know that it is a little better to accept your own answer and close the question as a whole, especially after the problem was actually in another part of the code, especially after you raised the generosity. My apologies.

0
source share

I'm going to suggest that you use pygame.sprite.Group() to group sprites and then display them on the screen. Correct me if I am wrong. Then you create a conflict that is created using the attributes of the sprite, but then "destroys" the sprite, which means that any groups you created using pygame.sprite.Group that then contain this sprite will no longer contain this sprite. Now, given that you have added the link to your question, I'm going to assume that you have already read the documents.

So, here is the code that you posted with comments on what everyone does.

 #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 

So this means that it calls make_explosion and then calls kill on the sprite, which removes it from any group. Please tell me if you need any clarification on this issue or if you need a better or deeper answer. Or tell me if you have any other things you need.

0
source share

All Articles