base_entity.py

Module for defining and managing game-type entities.

Use subclasses of BaseEntity for agents that need a unique identifier, periodic updates, and messaging functionality. Subclasses need to:

  • Implement update(self); for the general update cycle.

  • Implement receive_msg(self, message); to enable messaging.

  • Call BaseEntity.__init__() during the subclass __init__() method.

BaseEntity uses several static methods for manger-type functionality; these are called on BaseEntity and NOT on subclasses. Most commonly-needed are:

BaseEntity.by_id(id_string) # To get an entity from its identifier.
BaseEntity.update_all()     # Call entity.update() on every entity.
BaseEntity.call_on_all(f, *a, **k)  # Call entity.f(*a, **k) on every entity.

The PostOffice class is used for message handling; only one instance should be needed. Instantiating a PostOffice() will afterwards allow an entity to use:

entity.send_msg(recv, tag, extra, **kwargs)

without needing to call the PostOffice explicitly. It is possible [but not yet tested] to use multiple instances of PostOffice, in which case only the most recently instantiated will have this automatic functionality. See send_msg() for further details and available keyword arguments.

To send a messages directly, without using a BaseEntity:

po = PostOffice(...)
po.post_msg(send, recv, tag, extra, **kwargs)

The documenation for PostOffice.post_msg() has a complete explanation of usage and keyword arguments.

class base_entity.BaseEntity(id_string)

Abstract Base Class for objects with an ID, update, and messaging.

Parameters

id_string (string) – An identifier for this entity; must be unique.

Raises

ValueError – If id_string has already been used by some entity.

Example

>>> goat = BaseEntity('THIS_GOAT')
>>> another_goat = BaseEntity('THIS_GOAT')
Traceback (most recent call last):
ValueError: EntityID THIS_GOAT has already been used.
>>> goat.update()
Traceback (most recent call last):
NotImplementedError: THIS_GOAT of <class 'aiboids.base_entity.BaseEntity'> has undefined update().
>>> goat.receive_msg('Hey, goat!')
Traceback (most recent call last):
NotImplementedError: THIS_GOAT of <class 'aiboids.base_entity.BaseEntity'> has undefined receive_msg().
update()

Generic update hook; must be implemented by subclasses/instances.

receive_msg(message)

Generic message hook; must be implemented by subclasses/instances.

property idstr

Get the user-supplied ID string of this entity.

Example

>>> ent = BaseEntity('SOME_PIG')
>>> print(ent.idstr)
SOME_PIG
static by_id(id_str)

Get an entity from its ID string; use BaseEntity.by_id().

>>> cmnsm = BaseEntity('ANY_FISH')
>>> cmnsm = BaseEntity('RED_HERRING')
>>> cmnsm == BaseEntity.by_id('ANY_FISH')
False
>>> cmnsm == BaseEntity.by_id('RED_HERRING')
True
>>> print(BaseEntity.by_id('NO_SUCH_ENTITY'))
None
Returns

The given entity, or None if no such ID is registered.

static update_all()

Call the update() method of every registered entity.

Syntax for this is BaseEntity.update_all().

static call_on_all(func, *args, **kwargs)

Call a function on every registered entity.

>>> entlist = []
>>> getid  = lambda entity: entlist.append(entity.idstr)
>>> BaseEntity.call_on_all(getid)
>>> sorted(entlist)
['ANY_FISH', 'RED_HERRING', 'THIS_GOAT']
static remove(id_str, delete=True)

Remove an entity’s from the registry using its ID String.

Parameters
  • id_str (string) – The ID string of the entity.

  • delete (boolean) – If True (default), the entity with this ID is deleted after unregistering.

Note

To avoid reference issues, an ID string that gets removed will not be available for later use

>>> BaseEntity.remove('RED_HERRING')
>>> print(BaseEntity.by_id('RED_HERRING'))
None
>>> BaseEntity('RED_HERRING')
Traceback (most recent call last):
ValueError: EntityID RED_HERRING has already been used.
send_msg(recv_id, msg_tag, extra=None, **kwargs)

Send a message now or add to the queue for later delivery.

Parameters
  • recv_id – ID of the recipient, compatible with BaseEntity.by_id().

  • msg_tag (string) – A general tag/type for the message.

  • extra – Optional message information.

Keyword Arguments
  • at_time (float) – Deliver at this absolute time. If given, delay is ignored.

  • delay (float) – Deliver after this amount of time.

This is a convenience function that allows entities to send messages without explicitly calling a PostOffice, altough the PostOffice must already be instantiated. See PostOffice.post_msg() for a complete explanation of the available arguments.

Raises

RuntimeError – If a PostOffice has not yet been instantiated.

class base_entity.PostOffice(scheduler)

Class for posting/handling messages between entities.

Parameters

scheduler (sched.scheduler) – See below.

The PostOffice needs an external scheduler (from sched.py) to manage timestamps and handle delivery of delayed messages. Whenever a PostOffice() is instantiated, the BaseEntity.send_msg() method (described above) is automatically updated to use the new PostOffice().

See the post_msg() method below for full details on sending messages.

class EntityMessage(MSG_TYPE, TIMESTAMP, SEND_ID, RECV_ID, EXTRA)

An envelope/message format; internal use only.

post_msg(send_id, recv_id, msg_tag, extra=None, **kwargs)

Send a message now or add to the queue for later delivery.

Parameters
  • send_id – ID of the sender, compatible with BaseEntity.by_id().

  • recv_id – ID of the recipient, compatible with BaseEntity.by_id().

  • msg_tag (string) – A general tag/type for the message; see below.

  • extra – Optional message information; see below.

Keyword Arguments
  • at_time (float) – Deliver at this absolute time. If given, delay is ignored; see below.

  • delay (float) – Deliver after this amount of time; see below.

The msg_tag is intended for simple messages; in many cases this is sufficient. For more complex messaging needs, the tag can be used as a category, and more specific information can be included using extra. In any case, the receiver has sole responsibility for dealing with any messages; it may even choose to ignore them completely.

Messages with the at_time or delay keyword (the first supercedes the second) use the _scheduler_ to compute delivery times. If neither option is given or the scheduled time is in the past, the message gets delivered as soon as possible.

For now, any message that fails to deliver is just discarded, but this behaviour may change in the future.

update()

Use to force update of the delayed message queue.

class base_entity.DummyClock

A clock-like object for a simulated timeline, starting at 0.0.

tick(increment=1.0)

Advance the clock by the given amount.

time()

The current clock time.

base_entity.sample_run()

Demo showing the various types of messaging.

Output from sample_run()

# Time is now 1
(THIS_ENT): Nothing to see here...
(THAT_ENT): Nothing to see here...
(THIS_ENT) got message: Message(MSG_TYPE='IMMEDIATE', TIMESTAMP=1, SEND_ID='THIS_ENT', RECV_ID='THIS_ENT', EXTRA=None)
# Time is now 2
(THIS_ENT): Nothing to see here...
(THAT_ENT): Nothing to see here...
(THIS_ENT) got message: Message(MSG_TYPE='ABSOLUTE_TIME', TIMESTAMP=2, SEND_ID='THAT_ENT', RECV_ID='THIS_ENT', EXTRA=None)
# Time is now 3
(THIS_ENT): Nothing to see here...
(THAT_ENT): Nothing to see here...
# Time is now 4
(THIS_ENT): Nothing to see here...
(THAT_ENT): Nothing to see here...
(THAT_ENT) got message: Message(MSG_TYPE='RELATIVE_TIME', TIMESTAMP=4, SEND_ID='THIS_ENT', RECV_ID='THAT_ENT', EXTRA=None)
(THIS_ENT) got message: Message(MSG_TYPE='VIA_POSTOFFICE', TIMESTAMP=4, SEND_ID='THAT_ENT', RECV_ID='THIS_ENT', EXTRA=None)