Generic FSM Modules

Modules in this section are not specific to WestWorld2; they can be used anywhere we need an FSM.


base_entity.py:

Module for defining managing game-type entities.

Use the BaseEntity class for agents that need a unique ID as well as periodic updating and messaging functionality. The EntityManager class provides a simple interface for automatic management.

Messages are sent via an instance of the MessageDispatcher class. This works with an EntityManager in order to serve as a kind of post office. Both immediate and delayed messages are possible; see the class description.

class base_entity.BaseEntity(myID, postoffice)

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

Parameters:
  • myID (int) – The unique ID assigned to this entity.
  • postoffice (MessageDispatcher) – Where this entity will send its messages.
Raises:

ValueError – If the requested ID is invalid.

Notes

Because of how messaging is implemented, each entity needs a unique ID. We use a private class variable to make sure that ID’s are not repeated. Since ID’s aren’t recycled, we can’t accidentally send a message or otherwise refer to an entity that is no longer valid.

get_id()

Returns the ID of this entity.

update()

Update method that will be called each step.

Note

This must be implemented by subclasses.

receive_msg(message)

Message handler; must be implemented my subclasses.

Parameters:message (tuple) – A message constructed using the telegram() function.

Note

This must be implemented by subclasses.

class base_entity.EntityManager

Manager class for objects of type BaseEntity.

register(entity)

Add an instance of BaseEntity to this manager.

Parameters:entity (BaseEntity) – An entity that has been instantiated outside of this class.
remove(entity)

Remove an instance of BaseEntity from this manager.

Notes

Since BaseEntity’s are instantiated/deleted outside of this class, removing only affects this manager’s behavior. This function checks whether entity has the correct type, so deleting entity before removing it from the manager shouldn’t be an issue.

get_entity_from_id(ent_id)

Returns an entity object from its ID.

Returns:The entity corresponding to ent_id. If this ID isn’t registered, returns None.
Return type:BaseEntity
update()

Calls the update() method of all registered entities.

Note

The order in which entities are called is arbitrary.

start_all_fsms()

Starts the FSM for each entity that has one.

TODO: Perhaps state_machine.py is a better place for this code?

class base_entity.EntityMessage

An envelope/message for sending information between entities.

This is called by the MessageDispatcher class and should not be used directly. To create the actual message, use MessageDispatcher.post_msg().

TODO: Consider changing DELAY to some kind of timestamp.

class base_entity.MessageDispatcher(clock_now, ent_mgr)

Class for posting/handling messages between entities.

Parameters:
  • clock_now (function()) – A function that returns a numerical value. This is used to represent the current time to control delivery of delayed messages.
  • ent_mgr (EntityManager) – Used by this class to lookup an entity, given its ID.
discharge(receiver, message)

Helper function for sending messages; internal use only.

post_msg(delay, send_id, rec_id, msg_type, extra=None)

Add a message to the queue for immediate or delayed dispatch.

Parameters:
  • delay (float) – Time (from now) at which to send the message. If zero/negative, the message will be dispatched immediately.
  • send_id (int) – The ID of the BaseEntity sending the message.
  • recv_id (int) – The ID of the BaseEntity that will receive the message.
  • msg_type (int) – A tag that identifies the general type of message being sent.
  • extra (anytype) – Optional information assumed to be handled by the recipient.
dispatch_delayed()

Dispatches messages from the delayed queue; internal use only.


state_machine.py

Module containing basic FSM functionality.

All states should be derived from the State class, see its documentation.

Use STATE_NONE as a concrete null state. We need only a single instance.

An instance of BaseEntity can be given FSM functionality as follows:

  • fsm = StateMachine(entity)
  • fsm.set_state(current, global, previous), the last two are optional
  • entity.fsm = fsm
  • In entity’s update() method, call self.fsm.update()

In entity’s receive_msg() method, calling entity.fsm.handle_msg(message) will allow the FSM to route messages to the appropriate state logic: first to the current state, then to the global state.

class state_machine.State

Base class for all states.

States derived from this base class should override the methods below, though all of them are optional. Each method takes a parameter, agent, which is the BaseEntity that is using that state. This allows multiple entities to reuse the same state logic.

enter(agent)

Code to execute immediately when changing to this state.

execute(agent)

Code to execute each time this state is executed.

leave(agent)

Code to execute just before changing from this state.

on_msg(agent, message)

Code to execute when a message is received.

Note

When overriding this method, we need to return a boolean that indicates if the message was succesfully handled. The messaging functions use this boolean to redirect the message elsewhere if a given state is unable to handle it.

state_machine.STATE_NONE = <state_machine.State object>

Use this as a concrete null state; we need only a single instance.

class state_machine.StateMachine(owner)

Finite State Machine with messaging capability.

Parameters:owner (BaseEntity) – The entity using this instance of the FSM.

Notes

After instantiating a new StateMachine, use the set_state() method below in order to explicity initialize the states. Otherwise, this FSM will sit around and do nothing on update.

set_state(cur, glo=None, pre=None)

Manually set owner’s states without triggering state change logic.

Parameters:
  • cur (State) – Current State of the FSM. Use NullState here if you don’t need to explictly set an actual State.
  • glo (State) – Global State (executed each update) of the FSM.
  • pre (State) – Previous State (used by revert_state) of the FSM. Defaults to NullState if not specified or invalid.
start()

Start the FSM by executing global & current state’s enter() methods.

Note

This is an attempt to fix the issue of BaseEntities not having access to messaging during their __init__() functions. This calls the enter() methods of the global state first, then the FSM’s current state.

update()

Execute the owner’s global state (if any), then current state.

change_state(newstate)

Switches owner to a new state, calling leave/enter methods.

Parameters:
  • newstate (State) – The FSM will switch to this state.
  • Note (Both the current and new states must be valid, otherwise nothing) –
  • happen and we'll stay in the current state. (will) –
revert_state()

Reverts owner to its previous state; useful for state blips.

handle_msg(message)

Used by the FSM to route received messages.

The message is first passed to the current state, which tries to handle it. If the current state fails to do so, the message is then passed to the global state, if one exists.

Parameters:message (tuple) – A message constructed using the telegram() function.
Returns:True if the message was handled by either the current or global state; False otherwise.
Return type:bool