Main Page   Namespace List   Class Hierarchy   Alphabetical List   Compound List   File List   Namespace Members   Compound Members   File Members   Related Pages   Examples  

vpr::sim::Controller Class Reference

Socket simulation controller. More...

#include <Controller.h>

Collaboration diagram for vpr::sim::Controller:

Collaboration graph
[legend]
List of all members.

Public Methods

 Controller ()
 Initializes the socket simulation. More...

 ~Controller ()
 Resets the Sim Socket Manager's clock to 0. More...

vpr::ReturnStatus constructNetwork (const std::string &graph_file)
void destroyNetworkGraph ()
bool isRunning ()
 Queries the running state of this socket simulation. More...

void setSimulationPauseTime (const vpr::Uint32 sleep_time)
 Sets the amount of time (in microseconds) to sleep after processing a single event. More...

void addMessageEvent (const vpr::Interval &event_time, const NetworkGraph::net_edge_t edge, const NetworkLine::LineDirection dir)
 Adds an event scheduled to occur at the given time to the queue of upcoming events. More...

void addConnectionEvent (const vpr::Interval &event_time, vpr::SocketImplSIM *acceptor_sock)
void addConnectionCompletionEvent (const vpr::Interval &event_time, vpr::SocketImplSIM *connector_sock)
void addLocalhostDeliveryEvent (const vpr::Interval &event_time, vpr::SocketImplSIM *connector_sock)
void flushPath (const vpr::SocketImplSIM *sock, vpr::sim::NetworkGraph::VertexListPtr path)
 Flushes all messages (and associated events) on the given path destined for the given socket. More...

void processNextEvent (vpr::SocketImplSIM **recvSocket=NULL)
 Processes the next event in the event queue no matter how far into the (simulated) future it occurs. More...

void processEvents (const vpr::Interval &time_step)
 Limits the time frame for the occurrence of the next events to the given time step. More...

vpr::Uint32 getNumPendingEvents ()
 Return the number of events pending in the system. More...

const vpr::sim::ClockgetClock () const
vpr::sim::SocketManagergetSocketManager ()
vpr::sim::NetworkGraphgetNetworkGraph ()

Static Public Methods

void setInstance (Controller *c)
 Set the instance to be returned in this thread to the given object pointer. More...

Controller * instance ()
 Returns an instance of this thread-specific singleton class. More...


Protected Types

typedef std::multimap< vpr::Interval,
EventData
event_map_t

Protected Methods

 Controller (const Controller &o)
void operator= (const Controller &o)

Protected Attributes

vpr::sim::Clock mClock
 The global clock that we are using. More...

vpr::sim::SocketManager mSocketManager
 The socket manager that we are using. More...

vpr::sim::NetworkGraph mGraph
 The network graph used for the simulation. More...

event_map_t mEvents
vpr::Uint32 mSleepTime

Detailed Description

Socket simulation controller.

This is used to step through a simulation being controlled by the Sim Socket Manager. This class is a thread-specific singleton so that each running thread can have its own unique instance. It can also be used as a traditional global singleton. To use it as a thread-specific singleton, call setInstance() as the first step of a newly spawned thread. This is crucial to its functionality.

Definition at line 77 of file Controller.h.


Member Typedef Documentation

typedef std::multimap<vpr::Interval, EventData> vpr::sim::Controller::event_map_t [protected]
 

Definition at line 330 of file Controller.h.


Constructor & Destructor Documentation

vpr::sim::Controller::Controller  
 

Initializes the socket simulation.

This should only be called when a thread-specific instance is needed. Otherwise, use the static instance() method to get a thread-specific or global instance depending on what is available.

Postcondition:
An instance of the Sim Socket Manager is retrieved, and the simulation state is set to not started.

Definition at line 68 of file Controller.cpp.

00069    : mSleepTime(0)
00070 {
00071    /* Do nothing. */ ;
00072 }

vpr::sim::Controller::~Controller   [inline]
 

Resets the Sim Socket Manager's clock to 0.

Postcondition:
The Sim Socket Manager's clock is reset to 0 so that the same instance may be used by another simulation.

Definition at line 97 of file Controller.h.

00098    {
00099       /* Do nothing. */ ;
00100    }

vpr::sim::Controller::Controller const Controller &    o [inline, protected]
 

Definition at line 276 of file Controller.h.

00276 {;}


Member Function Documentation

void vpr::sim::Controller::setInstance Controller *    c [inline, static]
 

Set the instance to be returned in this thread to the given object pointer.

This should be done as the first step by all threads wishing to have a thread-specific instance of this singleton class.

Definition at line 107 of file Controller.h.

00108    {
00109       mInstance->setObject(c);
00110    }

Controller* vpr::sim::Controller::instance   [inline, static]
 

Returns an instance of this thread-specific singleton class.

If a thread-specific instance was set by the currently active thread using setInstance(), that instance is returned. If no instance is associated with the current thread, the primordial (global) instance is returned.

Definition at line 118 of file Controller.h.

References vprASSERT.

Referenced by vpr::SocketStreamImplSIM::accept, vpr::SocketImplSIM::bind, vpr::SocketImplSIM::close, vpr::sim::SocketManager::connect, vpr::SocketImplSIM::connect, vpr::sim::SocketManager::findRoute, vpr::SocketStreamImplSIM::listen, vpr::sim::SocketManager::sendMessage, vpr::sim::SocketManager::sendMessageTo, vpr::SocketDatagramImplSIM::sendto, and vpr::SocketImplSIM::write_i.

00119    {
00120       // WARNING! race condition possibility, creation of static vars
00121       // are not thread safe.  This is only an issue when creating
00122       // your first thread, since it uses a singleton thread manager,
00123       // the two threads might both try to call instance at the same time
00124       // which then the creation of the following mutex would not be certain.
00125       static vpr::Mutex singleton_lock;
00126 
00127       if ( mInstance->getObject() == NULL )
00128       {
00129          vpr::Guard<vpr::Mutex> guard(singleton_lock);
00130 
00131          if ( mPrimordialInstance == NULL )
00132          {
00133             mPrimordialInstance = new Controller;
00134          }
00135 
00136          if ( mInstance->getObject() == NULL )
00137          {
00138             mInstance->setObject(mPrimordialInstance);
00139          }
00140 
00141          vprASSERT(mInstance->getObject() != NULL && "No instance defined");
00142       }
00143 
00144       return mInstance->getObject();
00145    }

vpr::ReturnStatus vpr::sim::Controller::constructNetwork const std::string &    graph_file
 

Definition at line 74 of file Controller.cpp.

References vpr::sim::NetworkGraph::clear, vpr::sim::NetworkGraph::construct, vpr::sim::NetworkGraph::isValid, mGraph, mSocketManager, vpr::sim::SocketManager::setActive, and vpr::ReturnStatus::success.

00075 {
00076    vpr::ReturnStatus status;
00077 
00078    if ( mGraph.isValid() )
00079    {
00080       mGraph.clear();
00081    }
00082 
00083    status = mGraph.construct(graph_file);
00084 
00085    if ( status.success() )
00086    {
00087       mSocketManager.setActive();
00088    }
00089 
00090    return status;
00091 }

void vpr::sim::Controller::destroyNetworkGraph   [inline]
 

Definition at line 149 of file Controller.h.

00150    {
00151       mGraph.clear();
00152    }

bool vpr::sim::Controller::isRunning   [inline]
 

Queries the running state of this socket simulation.

The simulation is considered running if it has been started and if the Sim Socket Manager still has active sockets registered.

Postcondition:
The running state of this simulation is returned to the caller.

Definition at line 161 of file Controller.h.

References vprASSERT.

00162    {
00163       vprASSERT(false && "Not supported right now.  If you want it, then implement it.");
00164       //return mGraph.isValid() && mSocketManager.hasActiveSockets();
00165       return false;
00166    }

void vpr::sim::Controller::setSimulationPauseTime const vpr::Uint32    sleep_time [inline]
 

Sets the amount of time (in microseconds) to sleep after processing a single event.

Definition at line 172 of file Controller.h.

00173    {
00174       mSleepTime = sleep_time;
00175    }

void vpr::sim::Controller::addMessageEvent const vpr::Interval   event_time,
const NetworkGraph::net_edge_t    edge,
const NetworkLine::LineDirection    dir
 

Adds an event scheduled to occur at the given time to the queue of upcoming events.

Definition at line 93 of file Controller.cpp.

References vpr::Interval::getBaseVal, mEvents, vprDBG_HVERB_LVL, vprDBG_SIM, vprDEBUG, and vprDEBUG_FLUSH.

Referenced by vpr::sim::SocketManager::sendMessage.

00096 {
00097    vprDEBUG(vprDBG_SIM, vprDBG_HVERB_LVL)
00098       << "Controller::addMessageEvent(): Adding message event scheduled for time "
00099       << event_time.getBaseVal() << " on edge " << edge << "\n"
00100       << vprDEBUG_FLUSH;
00101    mEvents.insert(std::pair<vpr::Interval, EventData>(event_time, EventData(edge, dir)));
00102 }

void vpr::sim::Controller::addConnectionEvent const vpr::Interval   event_time,
vpr::SocketImplSIM   acceptor_sock
 

Definition at line 104 of file Controller.cpp.

References vpr::Interval::getBaseVal, mEvents, vprDBG_HVERB_LVL, vprDBG_SIM, vprDEBUG, and vprDEBUG_FLUSH.

Referenced by vpr::sim::SocketManager::connect.

00106 {
00107    vprDEBUG(vprDBG_SIM, vprDBG_HVERB_LVL)
00108       << "Controller::addConnectionEvent(): Adding connection request event scheduled for time "
00109       << event_time.getBaseVal() << "\n" << vprDEBUG_FLUSH;
00110    mEvents.insert(std::pair<vpr::Interval, EventData>(event_time, EventData(acceptor_sock, EventData::CONNECTION_INIT)));
00111 }

void vpr::sim::Controller::addConnectionCompletionEvent const vpr::Interval   event_time,
vpr::SocketImplSIM   connector_sock
 

Definition at line 113 of file Controller.cpp.

References vpr::Interval::getBaseVal, mEvents, vprDBG_HVERB_LVL, vprDBG_SIM, vprDEBUG, and vprDEBUG_FLUSH.

Referenced by vpr::SocketStreamImplSIM::accept.

00115 {
00116    vprDEBUG(vprDBG_SIM, vprDBG_HVERB_LVL)
00117       << "Controller::addConnectionCompletionEvent(): Adding connection "
00118       << "completion event scheduled for time " << event_time.getBaseVal()
00119       << "\n" << vprDEBUG_FLUSH;
00120    mEvents.insert(std::pair<vpr::Interval, EventData>(event_time, EventData(connector_sock, EventData::CONNECTION_COMPLETE)));
00121 }

void vpr::sim::Controller::addLocalhostDeliveryEvent const vpr::Interval   event_time,
vpr::SocketImplSIM   connector_sock
 

Definition at line 123 of file Controller.cpp.

References vpr::Interval::getBaseVal, mEvents, vprDBG_HVERB_LVL, vprDBG_SIM, vprDEBUG, and vprDEBUG_FLUSH.

Referenced by vpr::sim::SocketManager::sendMessage.

00125 {
00126    vprDEBUG(vprDBG_SIM, vprDBG_HVERB_LVL)
00127       << "Controller::addConnectionCompletionEvent(): Adding localhost "
00128       << "delivery event scheduled for time " << event_time.getBaseVal()
00129       << "\n" << vprDEBUG_FLUSH;
00130    mEvents.insert(std::pair<vpr::Interval, EventData>(event_time, EventData(connector_sock, EventData::LOCALHOST_DELIVERY)));
00131 }

void vpr::sim::Controller::flushPath const vpr::SocketImplSIM   sock,
vpr::sim::NetworkGraph::VertexListPtr    path
 

Flushes all messages (and associated events) on the given path destined for the given socket.

Definition at line 133 of file Controller.cpp.

References vpr::sim::NetworkGraph::getEdge, vpr::sim::NetworkGraph::getLineProperty, vpr::sim::NetworkGraph::isSource, vpr::sim::NetworkLine::LineDirection, mEvents, mGraph, vpr::sim::NetworkGraph::net_edge_t, vpr::sim::NetworkLine::removeActiveMessages, vpr::sim::NetworkGraph::VertexListPtr, vprDBG_SIM, vprDBG_STATE_LVL, vprDBG_VERB_LVL, vprDEBUG, and vprDEBUG_FLUSH.

00135 {
00136 vpr::DebugOutputGuard dbg_output(vprDBG_SIM, vprDBG_STATE_LVL,
00137                                  std::string("Controller::flushPath()\n"),
00138                                  std::string("End of Controller::flushPath()\n"));
00139 
00140    vpr::sim::NetworkGraph::VertexList::iterator source, dest;
00141    vpr::sim::NetworkGraph::net_edge_t current_line;
00142    bool got_next_line;
00143 
00144    source = dest = path->begin();
00145    ++dest;
00146 
00147    for ( ; dest != path->end(); ++dest )
00148    {
00149       boost::tie(current_line, got_next_line) = mGraph.getEdge(*source, *dest);
00150 
00151       if ( got_next_line )
00152       {
00153          vprDEBUG(vprDBG_SIM, vprDBG_STATE_LVL)
00154             << "Controller::flushPath(): Working on line " << current_line
00155             << std::endl << vprDEBUG_FLUSH;
00156 
00157          vpr::sim::NetworkLine& line_prop = mGraph.getLineProperty(current_line);
00158          vpr::sim::NetworkLine::LineDirection dir;
00159          std::vector<vpr::Interval> event_times;
00160 
00161          dir = mGraph.isSource(*source, current_line) ? NetworkLine::FORWARD
00162                                                       : NetworkLine::REVERSE;
00163 
00164          vprDEBUG(vprDBG_SIM, vprDBG_VERB_LVL)
00165             << "Controller::flushPath(): Removing active messages on line "
00166             << current_line << " in the "
00167             << ((dir == NetworkLine::FORWARD) ? "forward" : "reverse")
00168             << " queue\n" << vprDEBUG_FLUSH;
00169 
00170          line_prop.removeActiveMessages(sock, event_times, dir);
00171 
00172          // If the event_times vector has elements in it, these specify the
00173          // times at which flushed messages were scheduled to arrive.  We must
00174          // remove these events from the mEvents container.
00175          if ( ! event_times.empty() )
00176          {
00177             EventData test_event(current_line, dir);    // Message event
00178             event_map_t::iterator event_iter, end_iter;
00179 
00180             // For each of the scheduled event times, look up all the events
00181             // in the event container with that time and find the one that
00182             // matches the message flushed from the line above.
00183             for ( std::vector<vpr::Interval>::iterator i = event_times.begin();
00184                   i != event_times.end();
00185                   ++i )
00186             {
00187                vprDEBUG(vprDBG_SIM, vprDBG_STATE_LVL)
00188                   << "Controller::flushPath(): Looking for events scheduled to occur at "
00189                   << (*i).getBaseVal() << std::endl << vprDEBUG_FLUSH;
00190 
00191                boost::tie(event_iter, end_iter) = mEvents.equal_range(*i);
00192 
00193                // XXX: I'm not sure if std::remove_if() can be used here
00194                // because mEvents is currently an associative container.
00195                for ( ; event_iter != end_iter; ++event_iter )
00196                {
00197                   if ( test_event == (*event_iter).second )
00198                   {
00199                      vprDEBUG(vprDBG_SIM, vprDBG_VERB_LVL)
00200                         << "Controller::flushPath(): Removing event\n"
00201                         << vprDEBUG_FLUSH;
00202                      mEvents.erase(event_iter);
00203                   }
00204                }
00205             }
00206          }
00207       }
00208 
00209       // Finally, move the source to point to the current destination.  The
00210       // destination will be incremented as part of the for loop's
00211       // iteration.
00212       source = dest;
00213    }
00214 }

void vpr::sim::Controller::processNextEvent vpr::SocketImplSIM **    recvSocket = NULL
 

Processes the next event in the event queue no matter how far into the (simulated) future it occurs.

If there is an event in the queue, it will be processed by this method.

Parameters:
recvSocket  The socket that recv'ed the event (NULL if none)

Definition at line 216 of file Controller.cpp.

References vpr::sim::NetworkLine::FORWARD, vpr::sim::NetworkLine::getArrivedMessage, vpr::Interval::getBaseVal, vpr::sim::Clock::getCurrentTime, vpr::sim::NetworkGraph::getLineProperty, vpr::sim::NetworkLine::getNetworkAddressString, mClock, mEvents, mGraph, mSleepTime, vpr::sim::Clock::setCurrentTime, vpr::ReturnStatus::success, vprASSERT, vprDBG_HVERB_LVL, vprDBG_SIM, vprDBG_STATE_LVL, vprDBG_VERB_LVL, vprDEBUG, and vprDEBUG_FLUSH.

Referenced by processEvents.

00217 {
00218 vpr::DebugOutputGuard dbg_output(vprDBG_SIM, vprDBG_STATE_LVL,
00219                                  std::string("Controller::processNextEvent()\n"),
00220                                  std::string("End of Controller::processNextEvent()\n"));
00221 
00222    if ( recvSocket != NULL )
00223    {
00224       (*recvSocket) = NULL;
00225    }
00226 
00227    event_map_t::iterator cur_event = mEvents.begin();
00228 
00229    if ( cur_event != mEvents.end() )
00230    {
00231       vpr::Interval event_time = (*cur_event).first;
00232 
00233       vprDEBUG(vprDBG_SIM, vprDBG_VERB_LVL)
00234          << "Controller::processNextEvent() [time = "
00235          << mClock.getCurrentTime().getBaseVal()
00236          << "]: Processing event scheduled to occur at time "
00237          << event_time.getBaseVal() << " (moving clock forward)\n"
00238          << vprDEBUG_FLUSH;
00239 
00240       mClock.setCurrentTime(event_time);
00241 
00242       if ( (*cur_event).second.type == EventData::MESSAGE )
00243       {
00244          NetworkGraph::net_edge_t event_edge = (*cur_event).second.edge;
00245          NetworkLine::LineDirection dir      = (*cur_event).second.direction;
00246          vpr::sim::NetworkLine& line         = mGraph.getLineProperty(event_edge);
00247          vpr::sim::MessagePtr msg;
00248          vpr::ReturnStatus status;
00249 
00250          // -------------------------------------------------------------------
00251          // Process event in the line's transmission queue.
00252          // -------------------------------------------------------------------
00253 
00254          status = line.getArrivedMessage(event_time, msg, dir);
00255          vprASSERT(status.success() && "No arrived message at this time");
00256 
00257          vprDEBUG(vprDBG_SIM, vprDBG_HVERB_LVL)
00258             << "Controller::processNextEvent(): Event is the arrival of a message for "
00259             << msg->getDestinationSocket()->getLocalAddr() << " on "
00260             << ((dir == vpr::sim::NetworkLine::FORWARD) ? "forward" : "reverse")
00261             << " queue of line " << line.getNetworkAddressString() << "\n"
00262             << vprDEBUG_FLUSH;
00263 
00264          moveMessage(msg, event_time, recvSocket);
00265       }
00266       else if ( (*cur_event).second.type == EventData::CONNECTION_COMPLETE )
00267       {
00268          vprDEBUG(vprDBG_SIM, vprDBG_HVERB_LVL)
00269             << "Controller::processNextEvent(): Event is a connection completion for "
00270             << (*cur_event).second.socket->getLocalAddr() << "\n"
00271             << vprDEBUG_FLUSH;
00272 
00273          if ( recvSocket != NULL )
00274          {
00275             *recvSocket = (*cur_event).second.socket;
00276          }
00277       }
00278       else if ( (*cur_event).second.type == EventData::CONNECTION_INIT )
00279       {
00280          vprDEBUG(vprDBG_SIM, vprDBG_HVERB_LVL)
00281             << "Controller::processNextEvent(): Event is a connection request to "
00282             << (*cur_event).second.socket->getLocalAddr() << "\n"
00283             << vprDEBUG_FLUSH;
00284 
00285          if ( recvSocket != NULL )
00286          {
00287             *recvSocket = (*cur_event).second.socket;
00288          }
00289       }
00290       else
00291       {
00292          vprDEBUG(vprDBG_SIM, vprDBG_HVERB_LVL)
00293             << "Controller::processNextEvent(): Event is a localhost message delivery to "
00294             << (*cur_event).second.socket->getLocalAddr() << "\n"
00295             << vprDEBUG_FLUSH;
00296 
00297          if ( recvSocket != NULL )
00298          {
00299             *recvSocket = (*cur_event).second.socket;
00300          }
00301       }
00302 
00303       mEvents.erase(cur_event);
00304 
00305       if ( mSleepTime != 0 )
00306       {
00307          vpr::System::usleep(mSleepTime);
00308       }
00309    }
00310 }

void vpr::sim::Controller::processEvents const vpr::Interval   time_step
 

Limits the time frame for the occurrence of the next events to the given time step.

There may not be an event in the queue that occurs between the current time and the time step, and in that case, no event will be processed.

Parameters:
time_step  The maximum time step allowed for the simulation.

Definition at line 312 of file Controller.cpp.

References vpr::Interval::getBaseVal, vpr::sim::Clock::getCurrentTime, mClock, mEvents, processNextEvent, vpr::sim::Clock::setCurrentTime, vprDBG_SIM, vprDBG_STATE_LVL, vprDBG_VERB_LVL, vprDEBUG, and vprDEBUG_FLUSH.

00313 {
00314 vpr::DebugOutputGuard dbg_output(vprDBG_SIM, vprDBG_STATE_LVL,
00315                                  std::string("Controller::processEvents()\n"),
00316                                  std::string("End of Controller::processEvents()\n"));
00317 
00318    vpr::SocketImplSIM* recv_sock;
00319    vpr::Interval event_time = mClock.getCurrentTime() + time_step;
00320    event_map_t::iterator next_event;
00321 
00322    vprDEBUG(vprDBG_SIM, vprDBG_VERB_LVL)
00323       << "Controller::processEvents [time = "
00324       << mClock.getCurrentTime().getBaseVal() << "]: Moving clock ahead "
00325       << time_step.getBaseVal() << " units to be " << event_time.getBaseVal()
00326       << std::endl << vprDEBUG_FLUSH;
00327 
00328    mClock.setCurrentTime(event_time);
00329 
00330    // The current time based on the time step is the same as or greater than
00331    // the time of the next event in the queue.  That means that that event is
00332    // eligible for processing.
00333    while ( (next_event = mEvents.begin()) != mEvents.end() &&
00334            (*next_event).first <= event_time )
00335    {
00336       processNextEvent(&recv_sock);
00337    }
00338 }

vpr::Uint32 vpr::sim::Controller::getNumPendingEvents   [inline]
 

Return the number of events pending in the system.

Definition at line 222 of file Controller.h.

00223    {
00224       return mEvents.size();
00225    }

const vpr::sim::Clock& vpr::sim::Controller::getClock   const [inline]
 

Definition at line 227 of file Controller.h.

Referenced by vpr::SocketStreamImplSIM::accept, vpr::sim::SocketManager::connect, and vpr::sim::SocketManager::sendMessage.

00228    {
00229       return mClock;
00230    }

vpr::sim::SocketManager& vpr::sim::Controller::getSocketManager   [inline]
 

Definition at line 232 of file Controller.h.

Referenced by vpr::SocketStreamImplSIM::accept.

00233    {
00234       return mSocketManager;
00235    }

vpr::sim::NetworkGraph& vpr::sim::Controller::getNetworkGraph   [inline]
 

Definition at line 237 of file Controller.h.

Referenced by vpr::sim::SocketManager::sendMessage.

00238    {
00239       return mGraph;
00240    }

void vpr::sim::Controller::operator= const Controller &    o [inline, protected]
 

Definition at line 277 of file Controller.h.

00277 {;}


Member Data Documentation

vpr::sim::Clock vpr::sim::Controller::mClock [protected]
 

The global clock that we are using.

Definition at line 279 of file Controller.h.

Referenced by processEvents, and processNextEvent.

vpr::sim::SocketManager vpr::sim::Controller::mSocketManager [protected]
 

The socket manager that we are using.

Definition at line 280 of file Controller.h.

Referenced by constructNetwork.

vpr::sim::NetworkGraph vpr::sim::Controller::mGraph [protected]
 

The network graph used for the simulation.

Definition at line 281 of file Controller.h.

Referenced by constructNetwork, flushPath, and processNextEvent.

event_map_t vpr::sim::Controller::mEvents [protected]
 

Definition at line 331 of file Controller.h.

Referenced by addConnectionCompletionEvent, addConnectionEvent, addLocalhostDeliveryEvent, addMessageEvent, flushPath, processEvents, and processNextEvent.

vpr::Uint32 vpr::sim::Controller::mSleepTime [protected]
 

Definition at line 333 of file Controller.h.

Referenced by processNextEvent.


The documentation for this class was generated from the following files:
Generated on Sun May 2 14:47:19 2004 for VR Juggler Portable Runtime by doxygen1.2.14 written by Dimitri van Heesch, © 1997-2002