Rereading your question ... I guess maybe I read more into your question than you originally wanted...
class GameLoop(Scene): def __init__(self): self.go_list = ; def add_gameobject(self, go): self.go_list.append(go) self.add_child(go) def remove_gameobject(self,go): #... remove from go_list and remove_child def setup(self): for i in range(40): self.add_gameobject( GameObject() ) def update(self): for go in self.go_list: go.update()
Doesn't something like this accomplish what you are after, without a lot of muss & fuss?
Now, I think you could accomplish various types of physics behaviours using decorators -- for instance, you could have a projectile decorator which simply takes current velocity and adjusts position. Then, you could have a affected_by_gravity decorator which adjusts velocity in each time step by g*dt in y. A bouncy decorator that looks for collisions and then inverts velocity, etc. Each decorator would have the form
def affected_by_gravity(cls): originalupdate = cls.__dict__.get('update) def update(self, dt): self.velocity = self.parent.gravity * dt if callable(originalupdate): originalupdate(self,dt) setattr(cls,'update',update) return cls
then you could have
@affected_by_gravity @projectile_physics @bouncy class Ball(ShapeNode): def __init__(self): pass
I was saying you simply have something like this
def update(self): for node in self.children: if hasattr(node, 'update') and callable(node.update): node.update()
You could actually do something like this via a class decorator
def auto_update_children(cls): originalupdate = cls.__dict__.get('update') # Possibly None. def update(self): for node in self.children: if hasattr(node, 'update') and callable(node.update): node.update() #finally, if the class already had an update... call it. so you can define classes with an update method, it will update children, then call your specific code.. if callable(originalupdate): originalupdate(self) setattr(cls, 'update',update) return cls @auto_update_children class Game(Scene): def update(self): #do any specific game logic... children have all been update'd when this gets called check_collsitions() etc() #...
Likewise, you could then use said decorator on different "layers" that manage their own game state and children.
The other approach is that you don't have to literally call update on every object. You probably have classes of objects -- player, enemy, projectiles, powerups, whatever.
Keep those as lists inside your scene, and then loop through the lists.
The advantage of that is you can do things like check collisions of every enemy with player, or projectiles with player.
def add_enemy(self): enemy = EnemyNode(....) # ... some code to add a random enemy... self.enemies.append(enemy) self.add_child(enemy) def add_projectile(self, initial_position, initial_velocity): projectile = Projectile(initial_position, initial_velocity) self.projectiles.append(projectile) self.add_child(projectile) def update(self): for enemy in self.enemies: enemy.update() player.update() for projectile in self.projectiles: projectile.update() if projectile.bbox.intersects(player.bbox): #kill player, ...
So, you don't have to have a separate named object for everything in your scene, you have categories of objects that get added so that they can start being updated.
Ahh, I see you are trying to make sure the callback gets run on the main thread... The above code doesn't do that--it just sets the callback on the main thread.
If things within the callback must be done on the main thread, you need such code inside the callback handler itself. For instance, you could use a decorator on the handler.
Another common problem is calling ObjCInstance on some non object. @shinyformica had a thread a while back about a wierd seg fault, that we were able to fix I think by prechecking some condition in a loop, and only proceeding once things were correct. The details evade me.
In other words, on_main_thread returns a method, which you call using arguments. The way you had it, you were calling a function, and passing it's output, which was not a function, to on_main_thread.
I would also question whether you need to be calling on_main_thread at all here.
The reason is that you have defined lb inside a function -- main(), not as a global.
Either use globals, or use attributes directly accessible from your UI elements. Sender in your code will be the button--so sender.superview leads to your main view in this case, and sender.superview.lb.text='test' etc let's you store info in your own custom attribute names. Just be sure they are unique names that are not already attributes of ui.View.
When you are trying into to access subviews you can do
if you originally define view names when you add them to your main view.
Or, you can add attributes of the button to point to it's target label,
B=ui.Button(...) L=ui.Label() B.target_label=L ... #Then in your callback sender.target_label.text='...'
Basically to iOS, the only things non the main thread are user interface code and callbacks. Everything else is in the background queue. By default, there are just these two threads, and in_background gets queued onto the background thread -- meaning your main code must exit before anything from in_background will run. That leads to strange scenarios -- you just never have code in the main script that blocks waiting for something from a in_background function, since the in_background code won't execute until the main script is done. Also, never have blocking dependencies between two in_background items. It is often better to create your own thread if you plan on having code wait in the background for other conditions to be true.
Callbacks -- button presses, etc are called on the main thread.