"""The main world definition"""
import common
import zone
import serialize
import geometry
import events
import actor
import profiler
class UnknownActor(Exception): """Could not find the actor"""
class DuplicateActor(Exception): """The actor was already in the world"""
class DuplicateZone(Exception): """The zone was already in the world"""
[docs]class World(common.Loggable, serialize.Serializable, common.EventAware):
"""The main world object
The :doc:`engine` will control main worlds. Each world has a number
of :doc:`zone` which contain :doc:`actor`.
"""
my_properties = (
serialize.S('name', '', 'the name of this world'),
serialize.L('zones', set(), 'the zones in this world'),
serialize.L('unzoned_actors', set(), 'the actors not in any zone in this world'),
)
def __init__(self, name):
"""Initialise the World"""
self.addLogger()
self.initEvents()
self.name = name
self.engine = None
self.zones = set()
self.unzoned_actors = set() # Actors get put here if then end up in no zone
self.event_handlers = {}
self.init()
### Serializing ###
[docs] def init(self):
"""Initialise from serialized state"""
self.addLogger()
self.initEvents()
self.log.info('Initializing world %s' % self.name)
super(World, self).__init__()
self.engine = None
#
# This list is used to order the processing of actors in rendering. The
# flag is used to tell us when we need to resort them
self._sorted_actors = []
self._actors_need_resorting = False
self._scheduled_deletions = set()
#
# Now process actors
for zone in self.zones:
zone.init()
for actor in self.unzoned_actors:
actor.init()
### Zones ###
[docs] def addZone(self, zone):
"""Add a zone to the world"""
if zone in self.zones:
raise DuplicateZone('The zone %s is already in the world' % zone)
else:
self.zones.add(zone)
self._actors_need_resorting = True
[docs] def clearZones(self):
"""Remove all the zones"""
self.zones = set()
### Main ###
[docs] def updateWorld(self, interval):
"""Update the objects in the world"""
for zone in self.zones:
if zone.active:
zone.updateZone(interval, self)
#
# Process any scheduled actor deletions
while self._scheduled_deletions:
try:
self.removeActor(self._scheduled_deletions.pop())
except UnknownActor:
# Ok, the actor must have been removed directly
pass
[docs] def setEngine(self, engine):
"""Set the engine that we are owned by"""
self.engine = engine
[docs] def getEngine(self):
"""Return the engine that we are owned by"""
return self.engine
[docs] def findActorsByTag(self, tag):
"""Return all the actors in all zones based on the tag"""
results = actor.ActorCollection()
for z in self.zones:
results.extend(z.findActorsByTag(tag))
return results
[docs] def findActorByName(self, name):
"""Return the actor with the give name in all zones"""
for z in self.zones:
try:
return z.findActorByName(name)
except zone.ActorNotFound:
pass
else:
raise zone.ActorNotFound('Unable to find actor named "%s" in any zone' % name)
[docs] def findActorsAt(self, x, y):
"""Return the actors at a certain location"""
actors = actor.ActorCollection()
test = geometry.Point(x, y)
for the_actor in self.getActors():
if test.isInside(the_actor):
actors.append(the_actor)
return actors
[docs] def getActors(self):
"""Return all the actors"""
actors = actor.ActorCollection(self.unzoned_actors)
for z in self.zones:
actors.extend(z.getActors())
return actors
[docs] def rezoneActors(self):
"""Move actors to the right zone based on their spatial location"""
#
# Start with a list of actors to find homes for based on any that
# were not in any zones at all
moved = self.unzoned_actors
self.unzoned_actors = set()
#
# Find all the actors that are no longer in the right zone
# and remove them from their current zone
for z in self.zones:
for actor in z.actors.copy():
if not actor.isOverlapping(z):
z.removeActor(actor)
moved.add(actor)
#
# Now find the place for the moved actors
for actor in moved:
self.addActor(actor)
[docs] def clearActors(self):
"""Clear all the actors"""
self.clearActorsExceptTags([])
[docs] def addActor(self, actor):
"""Add an actor to the world"""
#
self.log.debug('Adding %s to world %s' % (actor.getNiceName(), self.name))
#
# Make sure the actor isn't already here
if self.hasActor(actor):
raise DuplicateActor('The actor %s is already in the world' % actor.getNiceName())
#
# Try to put the actor in the right zone
for z in self.zones:
if z.wouldContain(actor):
z.addActor(actor)
break
else:
# The actor is not in any zones, store for later
self.unzoned_actors.add(actor)
#
# Tell the actor about it
actor.addedToWorld(self)
#
self._actors_need_resorting = True
#
return actor
[docs] def removeActor(self, actor):
"""Remove the actor from the world"""
self.log.debug('Removing "%s" actor (%s)' % (actor.tag, actor.getNiceName()))
#
self._actors_need_resorting = True
#
# Try to remove from zones
for z in self.zones:
if z.hasActor(actor):
z.removeActor(actor)
break
else:
#
# We didn't find it in the zone - maybe in the unzoned
if actor in self.unzoned_actors:
self.unzoned_actors.remove(actor)
else:
raise UnknownActor('The actor %s was not found in the world' % actor)
#
# Tell the actor about it
actor.removedFromWorld(self)
[docs] def scheduleActorRemoval(self, actor):
"""Remove an actor at the end of the next update for the world
This method can be used to safely remove an actor from the world
during the execution of the world update. It can sometimes be
useful to do this when inside logic that is iterating over actors
or inside the updateWorld event loop.
"""
self._scheduled_deletions.add(actor)
[docs] def hasActor(self, actor):
"""Return True if this actor is in the world"""
#
# Try to remove from zones
for z in self.zones:
if z.hasActor(actor):
return True
#
# We didn't find it in the zone - maybe in the un-zoned
return actor in self.unzoned_actors
[docs] def requestResortActors(self):
"""Request that actors are resorted the next time we render
Call this if you have adjusted the rendering order of actors
"""
self._actors_need_resorting = True
[docs] def renderTo(self, renderer, interval):
"""Render all of our actors in active zones"""
#
# Watch out in case we need to reorder our actors
if self._actors_need_resorting:
self.log.debug('Sorting actors now')
self._sorted_actors = renderer.orderActors(self.getActors())
self._actors_need_resorting = False
#
camera = renderer.getCamera()
self.processEvent((events.E_BEFORE_RENDER, self))
#
# Render all of the actors
for actor in self._sorted_actors:
if actor.active and actor.visible:
profiler.PROFILER.start(actor, 'renderActor')
try:
actor.renderTo(renderer, interval)
except Exception, err:
self.log.error('Failed rendering "%s" actor "%s": %s' % (actor.tag, actor, err))
raise
profiler.PROFILER.end()
#
self.processEvent((events.E_AFTER_RENDER, self))
[docs] def setZoom(self, zoom, x, y):
"""Set the visual zoom on this world to zoom centered on x, y"""
for actor in self.getActors():
actor.setZoom(zoom)
### Events ###
[docs] def processEvents(self, events):
"""Handle the events"""
inhibited = set()
for (event, obj), actor in events:
if actor.active and not event in inhibited:
# Process the event
new_inhibits = actor.processEvent((event, obj))
# Record if we need to inhibit further events of a certain type
if new_inhibits:
inhibited.update(new_inhibits)
[docs] def activateWorld(self):
"""Called when the world is set as the current world"""
self.processEvent((events.E_ACTIVATE_WORLD, self))
[docs] def deactivateWorld(self):
"""Called when the world is deactivated"""
self.processEvent((events.E_DEACTIVATE_WORLD, self))
### Physics ###
[docs] def setPhysicsStepsize(self, interval):
"""Set the maximum step size for physics calculations"""
for z in self.zones:
z.setPhysicsStepsize(interval)
[docs] def setGlobalForce(self, force):
"""Set the global force for physics"""
for z in self.zones:
z.setGlobalForce(force)
[docs] def sleepPhysicsForActors(self, actors):
"""Tell the actors to go to sleep from a physics perspective
The actors will still be visible and will still be updated but they
will not update their physics. Useful for optimising when an actor
does not need to interact with the physics simulation for a while.
If an actor is unzoned then this will have no impact on them
"""
for actor in actors:
for z in self.zones:
if z.hasActor(actor):
z.sleepActor(actor)
[docs] def wakePhysicsForActors(self, actors):
"""Tell the actors to go to wake up from a physics perspective
Actors that were put to sleep (via sleepPhysicsForActors) will be woken
up and take part in the physics simulation again.
"""
for actor in actors:
for z in self.zones:
if z.hasActor(actor):
z.wakeActor(actor)