Main Page   Namespace List   Class Hierarchy   Alphabetical List   Compound List   File List   Namespace Members   Compound Members   File Members  

thread.h

Go to the documentation of this file.
00001 // Copyright (C) 1999-2001 Open Source Telecom Corporation.
00002 //  
00003 // This program is free software; you can redistribute it and/or modify
00004 // it under the terms of the GNU General Public License as published by
00005 // the Free Software Foundation; either version 2 of the License, or
00006 // (at your option) any later version.
00007 // 
00008 // This program is distributed in the hope that it will be useful,
00009 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00010 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00011 // GNU General Public License for more details.
00012 // 
00013 // You should have received a copy of the GNU General Public License
00014 // along with this program; if not, write to the Free Software 
00015 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
00016 // 
00017 // As a special exception to the GNU General Public License, permission is 
00018 // granted for additional uses of the text contained in its release 
00019 // of Common C++.
00020 // 
00021 // The exception is that, if you link the Common C++ library with other
00022 // files to produce an executable, this does not by itself cause the
00023 // resulting executable to be covered by the GNU General Public License.
00024 // Your use of that executable is in no way restricted on account of
00025 // linking the Common C++ library code into it.
00026 //
00027 // This exception does not however invalidate any other reasons why
00028 // the executable file might be covered by the GNU General Public License.
00029 // 
00030 // This exception applies only to the code released under the 
00031 // name Common C++.  If you copy code from other releases into a copy of
00032 // Common C++, as the General Public License permits, the exception does
00033 // not apply to the code that you add in this way.  To avoid misleading
00034 // anyone as to the status of such modified files, you must delete
00035 // this exception notice from them.
00036 // 
00037 // If you write modifications of your own for Common C++, it is your choice
00038 // whether to permit this exception to apply to your modifications.
00039 // If you do not wish that, delete this exception notice.  
00040 
00041 #ifndef CCXX_THREAD_H_
00042 #define CCXX_THREAD_H_
00043 
00044 #ifndef WIN32
00045 #define CCXX_POSIX
00046 #endif // !WIN32
00047 
00048 #ifndef CCXX_CONFIG_H_
00049 #include <cc++/config.h>
00050 #endif
00051 
00052 #ifndef CCXX_EXCEPTION_H_
00053 #include <cc++/exception.h>
00054 #endif
00055 
00056 #ifndef WIN32
00057 #if (defined(__FreeBSD__) && __FreeBSD__ <= 3) || defined(_AIX)
00058 #define CCXX_SYSV_SEMAPHORES
00059 #endif
00060 
00061 #ifndef HAVE_PTHREAD_H
00062 #include <pthread.h>
00063 #ifndef CCXX_SYSV_SEMAPHORES
00064 #include <semaphore.h>
00065 #endif
00066 #endif
00067 #endif // !WIN32
00068 
00069 #include <setjmp.h> // for jmp_buf, longjmp
00070 
00071 #ifndef WIN32
00072 #include <time.h>
00073 #include <signal.h>
00074 #include <unistd.h>
00075 
00076 #ifdef  __linux__
00077 #define CCXX_SIG_THREAD_ALARM
00078 // NOTE: Comment this line to test Resume/Signal using one signal method
00079 #define CCXX_SIG_THREAD_STOPCONT 
00080 #endif
00081 
00082 #ifdef  _THR_UNIXWARE
00083 #undef  PTHREAD_MUTEXTYPE_RECURSIVE
00084 #endif
00085 
00086 typedef pthread_t       cctid_t;
00087 typedef unsigned long   timeout_t;
00088 #else // WIN32
00089 typedef DWORD   cctid_t;
00090 typedef DWORD   timeout_t;
00091 
00092 #define MAX_SEM_VALUE   1000000
00093 #if defined(__CYGWIN32__) 
00094 #include <Windows32/CommonFunctions.h>
00095 #else
00096 __declspec(dllimport) long __stdcall InterlockedIncrement(long *);
00097 __declspec(dllimport) long __stdcall InterlockedDecrement(long *);
00098 __declspec(dllimport) long __stdcall InterlockedExchange(long *, long);
00099 #endif
00100 
00101 #endif // !WIN32
00102 
00103 #ifdef  CCXX_NAMESPACES
00104 namespace ost {
00105 #endif
00106 
00107 enum throw_t {
00108         THROW_NOTHING, 
00109         THROW_OBJECT, 
00110         THROW_EXCEPTION
00111 };
00112 typedef enum throw_t throw_t;
00113 class Thread;
00114 
00115 #define TIMEOUT_INF ~((timeout_t) 0)
00116 
00117 #define ENTER_CRITICAL  EnterMutex();
00118 #define LEAVE_CRITICAL  LeaveMutex();
00119 #define ENTER_DEFERRED  setCancel(THREAD_CANCEL_DEFERRED);
00120 #define LEAVE_DEFERRED  setCancel(THREAD_CANCEL_IMMEDIATE);
00121 
00122 enum thread_cancel_t
00123 {
00124         THREAD_CANCEL_INITIAL=0,
00125         THREAD_CANCEL_DEFERRED=1,
00126         THREAD_CANCEL_IMMEDIATE,
00127         THREAD_CANCEL_DISABLED,
00128         THREAD_CANCEL_MANUAL,
00129         THREAD_CANCEL_DEFAULT=THREAD_CANCEL_DEFERRED
00130 };
00131 typedef enum thread_cancel_t thread_cancel_t;
00132 
00133 enum thread_suspend_t
00134 {
00135         THREAD_SUSPEND_ENABLE,
00136         THREAD_SUSPEND_DISABLE
00137 };
00138 typedef enum thread_suspend_t thread_suspend_t;
00139 
00140 #ifndef WIN32
00141 // These macros override common functions with thread-safe versions. In
00142 // particular the common "libc" sleep() has problems since it normally
00143 // uses SIGARLM (as actually defined by "posix").  The pthread_delay and
00144 // usleep found in libpthread are gaurenteed not to use SIGALRM and offer
00145 // higher resolution.  psleep() is defined to call the old process sleep.
00146 
00147 #undef  sleep
00148 #define psleep(x)       (sleep)(x)
00149 
00150 #ifndef CCXX_SIG_THREAD_STOPCONT
00151 #ifndef _THR_SUNOS5
00152 #ifndef HAVE_PTHREAD_SUSPEND
00153 static RETSIGTYPE ccxx_sigsuspend(int);
00154 #endif
00155 #endif
00156 #endif
00157 extern "C" void ccxx_exec_handler(Thread *th);
00158 extern "C" void ccxx_thread_cleanup(void* arg);
00159 
00160 #endif // !WIN32
00161 
00162 
00163 CCXX_EXPORT(Thread*) getThread(void);
00164 CCXX_EXPORT(throw_t) getException(void);
00165 CCXX_EXPORT(void) setException(throw_t mode);
00166 CCXX_EXPORT(void) ccxx_sleep(timeout_t msec);
00167 CCXX_EXPORT(void) ccxx_yield(void);
00168 
00169 #undef Yield
00170 #define sleep(x)        ccxx_sleep((x) * 1000)
00171 #define yield()         ccxx_yield()
00172 
00173 #ifdef WIN32
00174 CCXX_EXPORT(DWORD) waitThread(HANDLE hRef, timeout_t timeout);
00175 #endif
00176 
00177 class Conditional;
00178 class CCXX_CLASS_EXPORT Event;
00179 
00223 class CCXX_CLASS_EXPORT Mutex
00224 {
00225         friend class Conditional;
00226         friend class Event;
00227 private:
00228 #ifndef WIN32
00229 #ifndef PTHREAD_MUTEXTYPE_RECURSIVE
00230         volatile int _level;
00231         volatile Thread *_tid;
00232 #endif
00233 
00241         pthread_mutex_t _mutex;
00242 #else
00243         HANDLE mutex;
00244 #endif
00245 
00246 public:
00250         Mutex();
00251 
00257         virtual ~Mutex();
00258 
00266         void EnterMutex(void);
00267 
00278         bool TryEnterMutex(void);
00279 
00290         void LeaveMutex(void);
00291 };
00292 
00314 class MutexLock
00315 {
00316 private:
00317         Mutex& mutex;
00318 public:
00322         MutexLock( Mutex& _mutex ) : mutex( _mutex ) 
00323                 { mutex.EnterMutex(); }
00327         // this should be not-virtual
00328         ~MutexLock()
00329                 { mutex.LeaveMutex(); }
00330 };
00331 
00340 class CCXX_CLASS_EXPORT ThreadLock
00341 {
00342 private:
00343 #ifdef HAVE_PTHREAD_RWLOCK
00344         pthread_rwlock_t _lock;
00345 #else
00346         Mutex mutex;
00347 #endif
00348 
00349 public:
00353         ThreadLock();
00354 
00358         virtual ~ThreadLock();
00359 
00363         void ReadLock(void);
00364 
00368         void WriteLock(void);
00369 
00375         bool TryReadLock(void);
00376 
00382         bool TryWriteLock(void);
00383 
00387         void Unlock(void);
00388 };
00389 
00399 class CCXX_CLASS_EXPORT MutexCounter : public Mutex
00400 {
00401 private:
00402         int     counter;
00403 
00404 public:
00405         MutexCounter();
00406         MutexCounter(int initial);
00407 
00408         friend CCXX_EXPORT(int) operator++(MutexCounter &mc);
00409         friend CCXX_EXPORT(int) operator--(MutexCounter &mc);
00410 };
00411 
00422 class CCXX_CLASS_EXPORT AtomicCounter
00423 {
00424 #ifndef WIN32
00425 private:
00426 #ifdef  HAVE_ATOMIC
00427         atomic_t atomic;
00428 #else
00429         int counter;
00430         Mutex lock;
00431 #endif
00432 
00433 public:
00437         AtomicCounter();
00438 
00444         AtomicCounter(int value);
00445 
00446         int operator++(void);
00447         int operator--(void);
00448         int operator+=(int change);
00449         int operator-=(int change);
00450         int operator+(int change);
00451         int operator-(int change);
00452         int operator=(int value);
00453         bool operator!(void);
00454         operator int();
00455 #else
00456 private:
00457         long atomic;
00458 
00459 public:
00460         inline AtomicCounter()
00461                 {atomic = 0;};
00462 
00463         inline AtomicCounter(int value)
00464                 {atomic = value;};
00465 
00466         inline int operator++(void)
00467                 {return InterlockedIncrement(&atomic);};
00468 
00469         inline int operator--(void)
00470                 {return InterlockedDecrement(&atomic);};
00471 
00472         int operator+=(int change);
00473 
00474         int operator-=(int change);
00475 
00476         inline int operator+(int change)
00477                 {return atomic + change;};
00478 
00479         inline int operator-(int change)
00480                 {return atomic - change;};
00481         
00482         inline int operator=(int value)
00483                 {return InterlockedExchange(&atomic, value);};
00484 
00485         inline bool operator!(void)
00486                 {return (atomic == 0) ? true : false;};
00487 
00488         inline operator int()
00489                 {return atomic;};
00490 #endif
00491 };
00492 
00493 // FIXME: implement Conditional class for win32
00494 #ifndef WIN32
00495 
00505 class Conditional : public Mutex
00506 {
00507 private:
00508         pthread_cond_t _cond;
00509 
00510 public:
00514         Conditional();
00515 
00519         virtual ~Conditional();
00520 
00526         void Signal(bool broadcast);
00527 
00531         void Wait(timeout_t timer = 0); 
00532 };
00533 #endif
00534 
00552 class CCXX_CLASS_EXPORT Semaphore
00553 {
00554 private:
00555 #ifndef WIN32
00556 #ifdef  CCXX_SYSV_SEMAPHORES
00557         int _semaphore;
00558 #else
00559         sem_t _semaphore;
00560 #endif
00561 #else // WIN32
00562         HANDLE  semObject;
00563 #endif // !WIN32
00564 
00565 public:
00574         Semaphore(size_t resource = 0);
00575 
00582         virtual ~Semaphore();
00583 
00597         void Wait(void);
00598 
00610         bool TryWait(void);
00611 
00623         void Post(void);
00624 
00625         // FIXME: how implement getValue for posix compatibility ?
00631 #ifndef WIN32
00632 #ifndef __CYGWIN32__
00633         int getValue(void);
00634 #endif
00635 #endif
00636 };
00637 
00651 class CCXX_CLASS_EXPORT Event
00652 {
00653 private:
00654 #ifndef WIN32
00655         Mutex mutex;
00656         pthread_cond_t _cond;
00657         bool _signaled;
00658         int _count;
00659 #else
00660         HANDLE cond;
00661 #endif
00662 
00663 public:
00664         Event();
00665 
00666         virtual ~Event();
00667 
00674         void Reset(void);
00675 
00679         void Signal(void);
00680 
00689         bool Wait(timeout_t timer);
00690         bool Wait(void);
00691 };
00692 
00714 class CCXX_CLASS_EXPORT Buffer
00715 {
00716 private:
00717         Mutex lock_head, lock_tail;
00718         Semaphore size_head, size_tail;
00719         size_t _size;
00720         size_t _used;
00721 
00722 protected:
00728         virtual int OnPeek(void *buf) = 0;
00734         virtual int OnWait(void *buf) = 0;
00740         virtual int OnPost(void *buf) = 0;
00741 
00742 public:
00747         Buffer(size_t capacity);
00752         virtual ~Buffer()
00753                 {return;};
00754 
00759         inline size_t getSize(void)
00760                 {return _size;};
00761         
00768         inline size_t getUsed(void)
00769                 {return _used;};
00770 
00779         int Wait(void *buf);
00780 
00788         int Post(void *buf);
00789 
00796         int Peek(void *buf);
00797 
00802         virtual bool isValid(void)
00803                 {return true;};
00804 };
00805 
00813 class CCXX_CLASS_EXPORT FixedBuffer : public Buffer
00814 {
00815 private:
00816         char *buf, *head, *tail;
00817         size_t objsize;
00818 
00819 protected:
00825         int OnPeek(void *buf);
00826 
00832         int OnWait(void *buf);
00833 
00839         int OnPost(void *buf);  
00840 
00841 public:
00849         FixedBuffer(size_t capacity, size_t objsize);
00850 
00857         FixedBuffer(const FixedBuffer &fb);
00858 
00862         virtual ~FixedBuffer();
00863 
00864         FixedBuffer &operator=(const FixedBuffer &fb);
00865 
00866         bool isValid(void);
00867 };
00868 
01016 class Thread
01017 {
01018 #ifndef WIN32
01019 friend class PosixThread;
01020 friend RETSIGTYPE ccxx_sigsuspend(int);
01021 friend void ccxx_exec_handler(Thread *th);
01022 friend void ccxx_thread_cleanup(void* arg);
01023 #endif
01024 private:
01025         friend class Slog;
01026 
01027         static Thread *_main;
01028 
01029         Thread *_parent;
01030 #ifndef WIN32
01031         pthread_t _tid;
01032         pthread_attr_t _attr;
01033         AtomicCounter _suspendcount;
01034 #else
01035         DWORD _tid;
01036         HANDLE _cancellation;
01037 #endif
01038         thread_cancel_t _cancel;
01039 //      jmp_buf _env;
01040         Semaphore *_start;
01041 
01042         // log information
01043         int _msgpos;
01044         char _msgbuf[128];
01045         throw_t _throw;
01046 
01047 #ifndef WIN32
01048         friend Thread *getThread(void);
01049         volatile bool _suspendEnable:1;
01050         unsigned int _type:2;
01051 #else
01052         bool _active:1;
01053         bool _suspendEnable:1;
01054         static unsigned __stdcall Execute(Thread *th);
01055         HANDLE  _hThread;
01056 #endif
01057         // close current thread, free all and call Notify
01058         void Close();
01059 
01060 protected:
01070         virtual void Run(void) = 0;
01071 
01088         CCXX_MEMBER_EXPORT(virtual void) Final(void)
01089                 {return;};
01090 
01102         CCXX_MEMBER_EXPORT(virtual void) Initial(void)
01103                 {return;};
01104 
01114         CCXX_MEMBER_EXPORT(virtual void*) getExtended(void)
01115                 {return NULL;};
01116 
01124         CCXX_MEMBER_EXPORT(virtual void) Notify(Thread *th)
01125                 {return;};
01126 
01136         CCXX_MEMBER_EXPORT(void) Sleep(timeout_t msec)
01137 #ifndef WIN32
01138                 {ccxx_sleep(msec);}
01139 #endif
01140         ;
01141 
01147         CCXX_MEMBER_EXPORT(void) Exit(void);
01148 
01153         CCXX_MEMBER_EXPORT(void) Yield(void);
01154 
01158         CCXX_MEMBER_EXPORT(bool) testCancel(void);
01159 
01169         CCXX_MEMBER_EXPORT(void) setCancel(thread_cancel_t mode);
01170 
01178         CCXX_MEMBER_EXPORT(void) setSuspend(thread_suspend_t mode);
01179 
01188         CCXX_MEMBER_EXPORT(void) Terminate(void);
01189 
01193         inline void clrParent(void)
01194                 {_parent = NULL;};
01195 
01196 #ifdef WIN32
01197         // FIXME: should be private
01198         CCXX_MEMBER_EXPORT(DWORD) WaitHandle(HANDLE obj, timeout_t timeout);
01199 #endif
01200 
01201 public:
01210         CCXX_MEMBER_EXPORT(CCXX_EMPTY) Thread(bool isMain);
01211 
01224         CCXX_MEMBER_EXPORT(CCXX_EMPTY) Thread(int pri = 0, size_t stack = 0);
01225 
01226         // FIXME: win32 lack copy constructor
01227 #ifndef WIN32
01228 
01235         Thread(const Thread &th);
01236 #endif
01237 
01244         CCXX_MEMBER_EXPORT(virtual) ~Thread();
01245 
01258         CCXX_MEMBER_EXPORT(int) Start(Semaphore *start = 0);
01259 
01268         CCXX_MEMBER_EXPORT(int) Detach(Semaphore *start = 0);
01269 
01276         inline Thread *getParent(void)
01277                 {return _parent;};
01278 
01285         CCXX_MEMBER_EXPORT(void) Suspend(void);
01286 
01290         CCXX_MEMBER_EXPORT(void) Resume(void);
01291 
01298         inline thread_cancel_t getCancel(void)
01299                 {return _cancel;};
01300 
01307         bool isRunning(void)
01308 #ifdef WIN32
01309                 {return (_tid != 0) ? true : false;}
01310 #endif
01311         ;
01312 
01319         bool isThread(void)
01320 #ifdef WIN32
01321                 {return ((_tid == GetCurrentThreadId())) ? true : false;}
01322 #endif
01323         ;
01324 
01330         friend CCXX_EXPORT(throw_t) getException(void);
01331 
01337         friend CCXX_EXPORT(void) setException(throw_t mode);
01338 
01339         // FIXME: _start can be NULL
01346         friend inline void operator++(Thread &th)
01347                 {th._start->Post();};
01348 
01349         friend inline void operator--(Thread &th)
01350                 {th._start->Wait();};
01351 
01357         friend CCXX_EXPORT(void) ccxx_sleep(timeout_t msec);
01358 
01362         friend CCXX_EXPORT(void) ccxx_yield(void);
01363 
01364 #ifdef WIN32
01365         // FIXME: not defined in posix
01366         inline bool isCancelled(void)
01367                 {return waitThread(_cancellation, 0) == WAIT_OBJECT_0; };
01368 
01369         inline bool isCancelled(timeout_t timer)
01370                 {return waitThread(_cancellation, timer) == WAIT_OBJECT_0; };
01371                 
01372         friend CCXX_EXPORT(DWORD) waitThread(HANDLE hRef, timeout_t timeout);
01373 #endif
01374 };
01375 
01376 #if !defined(WIN32) && !defined(__CYGWIN32__)  && !defined(__MINGW32__)
01377 extern "C" void sigHandler(int signo);
01378 
01379 typedef int             signo_t;
01380 
01381 class PosixThread: public Thread
01382 {
01383 private:
01384 #ifndef WIN32
01385         friend void ccxx_exec_handler(Thread *th);
01386         friend class Thread;
01387 #endif
01388 #ifndef CCXX_SIG_THREAD_ALARM
01389         static PosixThread *_timer;
01390         static Mutex _arm;
01391 #endif
01392         
01393         time_t  _alarm;
01394         friend void sigHandler(int signo);
01395         inline static void SignalThread(Thread* th,signo_t signo)
01396                 {pthread_kill(th->_tid, signo);};
01397 protected:
01398                 
01405         inline void SignalParent(signo_t signo)
01406                 { SignalThread(_parent,signo); };
01407         
01414         inline void SignalMain(signo_t signo)
01415                 { SignalThread(_main,signo);};
01416 
01421         virtual void OnTimer(void)
01422                 {return;};
01423 
01428         virtual void OnHangup(void)
01429                 {return;};
01430 
01435         virtual void OnException(void)
01436                 {return;};
01437 
01442         virtual void OnDisconnect(void)
01443                 {return;};
01444 
01449         virtual void OnPolling(void)
01450                 {return;};
01451 
01458         virtual void OnSignal(int signo)
01459                 {return;};
01460         
01470         void setTimer(timeout_t timer);
01471         
01478         timeout_t getTimer(void);
01479         
01485         void endTimer(void);
01486         
01493         void WaitSignal(signo_t signo);
01494         
01501         void setSignal(int signo, bool mode);
01502 public:
01503         
01509         inline void SignalThread(int signo)
01510                 {SignalThread(this, signo);};
01511 
01518         friend void siginstall(int signo);
01519 };
01520 #endif
01521 
01536 class CCXX_CLASS_EXPORT ThreadKey
01537 {
01538 private:
01539 #ifndef WIN32
01540         pthread_key_t key;
01541 #else
01542         DWORD   key;
01543 #endif
01544 
01545 public:
01549         ThreadKey();
01553         virtual ~ThreadKey();
01561         void *getKey(void);
01569         void setKey(void *);
01570 };
01571 
01582 class CCXX_CLASS_EXPORT TimerPort
01583 {
01584 #ifndef WIN32
01585         struct timeval timer;
01586 #else
01587         DWORD timer;
01588 #endif
01589         bool active;
01590 
01591 public:
01598         TimerPort();
01599 
01608         void setTimer(timeout_t timeout = 0);
01609 
01619         void incTimer(timeout_t timeout);
01620 
01626         void endTimer(void);
01627 
01638         timeout_t getTimer(void);
01639 
01648         timeout_t getElapsed(void);
01649 };
01650 
01651 inline int get(Buffer &b, void *o)
01652         {return b.Wait(o);};
01653 
01654 inline int put(Buffer &b, void *o)
01655         {return b.Post(o);};
01656 
01657 inline int peek(Buffer &b, void *o)
01658         {return b.Peek(o);};
01659 
01660 
01661 // FIXME: not in win32 implementation
01662 #if !defined(WIN32)
01663 
01664 // FIXME: private declaration ???
01665 struct  timespec *gettimeout(struct timespec *spec, timeout_t timeout); 
01666 
01667 #if !defined(__CYGWIN32__) && !defined(__MINGW32__)
01668 void    wait(signo_t signo);
01669 #endif
01670 
01679 void    pdetach(void);
01680 #endif // !WIN32
01681 
01682 // FIXME: no way to implement in win32
01683 #ifndef WIN32
01684 #if defined(HAVE_POLL_H) || defined(HAVE_SYS_POLL_H)
01685 #if defined(HAVE_SYS_STREAM_H)
01686 #if defined(__linux__)
01687 #define CCXX_USE_POLL 1
01688 #endif
01689 #else
01690 #define CCXX_USE_POLL 1
01691 #endif
01692 #endif
01693 
01694 #ifdef CCXX_USE_POLL
01695 
01703 class Poller 
01704 {
01705 private:
01706         int nufds;
01707         pollfd *ufds;
01708 
01709 public:
01710         Poller();
01711 
01712         virtual ~Poller();
01713 
01721         pollfd *getList(int cnt);
01722 
01728         inline  pollfd *getList(void)
01729                 {return ufds;};
01730 };
01731 #endif
01732 #endif // !WIN32
01733 
01734 #ifdef  COMMON_STD_EXCEPTION
01735 
01741 class ThrException : public Exception
01742 {
01743 public:
01744         ThrException(const std::string &what_arg) : Exception(what_arg) {};
01745 };
01746 
01753 class SyncException : public ThrException
01754 {
01755 public:
01756         SyncException(const std::string &what_arg) : ThrException(what_arg) {};
01757 };
01758 #endif
01759 
01760 #ifdef  CCXX_NAMESPACES
01761 };
01762 #endif
01763 
01764 #endif
01765 

Generated at Thu Jan 24 11:06:02 2002 for CommonC++ by doxygen1.2.10 written by Dimitri van Heesch, © 1997-2001