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

High_Res_Timer.cpp

Go to the documentation of this file.
00001 #include "ace_pch.h"
00002 // $Id: High_Res_Timer.cpp,v 1.1.1.4.2.1 2003/03/13 19:44:21 chad Exp $
00003 
00004 // Be very carefull before changing the calculations inside
00005 // ACE_High_Res_Timer.  The precision matters and we are using integer
00006 // calculations not floating point.  Also look good at the emulated 64
00007 // bit int class (inside Basic_Types{h,i,cpp} before changing
00008 // anything.  It's operator/ only returns 32 bits not 64 bits, among
00009 // other things.
00010 
00011 #include "ace/High_Res_Timer.h"
00012 
00013 #if !defined (__ACE_INLINE__)
00014 #include "ace/High_Res_Timer.i"
00015 #endif /* __ACE_INLINE__ */
00016 
00017 #include "ace/Stats.h"
00018 
00019 ACE_RCSID(ace, High_Res_Timer, "$Id: High_Res_Timer.cpp,v 1.1.1.4.2.1 2003/03/13 19:44:21 chad Exp $")
00020 
00021 ACE_ALLOC_HOOK_DEFINE(ACE_High_Res_Timer)
00022 
00023 // For Intel platforms, a scale factor is required for
00024 // ACE_OS::gethrtime.  We'll still set this to one to prevent division
00025 // by zero errors.
00026 #if (defined (ACE_WIN32) || defined (ACE_HAS_POWERPC_TIMER) || \
00027      defined (ACE_HAS_PENTIUM) || defined (ACE_HAS_ALPHA_TIMER)) && \
00028     !defined (ACE_HAS_HI_RES_TIMER)
00029 
00030 # include "ace/Synch.h"
00031 # include "ace/Object_Manager.h"
00032 
00033   // Initialize the global_scale_factor_ to 1.  The first
00034   // ACE_High_Res_Timer instance construction will override this
00035   // value.
00036   /* static */
00037   ACE_UINT32 ACE_High_Res_Timer::global_scale_factor_ = 1u;
00038 
00039 #else  /* ! (ACE_WIN32 || ACE_HAS_POWERPC_TIMER || \
00040              ACE_HAS_PENTIUM || ACE_HAS_ALPHA_TIMER)  ||
00041           ACE_HAS_HI_RES_TIMER */
00042   // A scale_factor of 1000 converts nanosecond ticks to microseconds.
00043   // That is, on these platforms, 1 tick == 1 nanosecond.
00044   /* static */
00045   ACE_UINT32 ACE_High_Res_Timer::global_scale_factor_ = 1000u;
00046 #endif /* ! (ACE_WIN32 || ACE_HAS_POWERPC_TIMER || \
00047              ACE_HAS_PENTIUM || ACE_HAS_ALPHA_TIMER)  ||
00048           ACE_HAS_HI_RES_TIMER */
00049 
00050 // This is used to tell if the global_scale_factor_ has been
00051 // set, and if high resolution timers are supported.
00052 /* static */
00053 int ACE_High_Res_Timer::global_scale_factor_status_ = 0;
00054 
00055 
00056 #if defined (linux)
00057 // Determine the apparent CPU clock speed from /proc/cpuinfo
00058 ACE_UINT32
00059 ACE_High_Res_Timer::get_cpuinfo (void)
00060 {
00061   ACE_UINT32 scale_factor = 1u;
00062 
00063   // Get the BogoMIPS from /proc/cpuinfo.  It works fine on Alpha and
00064   // Pentium Pro.  For other CPUs, it will be necessary to interpret
00065   // the BogoMips, as described in the BogoMips mini-HOWTO.  Note that
00066   // this code assumes an order to the /proc/cpuinfo contents.  The
00067   // BogoMips rating had better come after CPU type and model info.
00068 #if !defined (__alpha__)
00069   int supported = 0;
00070 #endif /* __alpha__ */
00071 
00072   FILE *cpuinfo = ACE_OS::fopen ("/proc/cpuinfo", "r");
00073 
00074   if (cpuinfo != 0)
00075     {
00076       ACE_TCHAR buf[128];
00077 
00078       // ACE_DEBUG ((LM_DEBUG, ACE_LIB_TEXT ("\nReading /proc/cpuinfo...")));
00079 
00080       while (ACE_OS::fgets (buf, sizeof buf, cpuinfo))
00081         {
00082 #if defined (__alpha__)
00083           ACE_UINT32 whole;
00084           ACE_UINT32 fractional;
00085           if (::sscanf (buf,
00086                         "BogoMIPS : %d.%d\n",
00087                         &whole,
00088                         &fractional) == 2
00089               || ::sscanf (buf,
00090                            "bogomips : %d.%d\n",
00091                            &whole,
00092                            &fractional) == 2)
00093             {
00094               scale_factor = whole;
00095               break;
00096             }
00097 #else
00098           double mhertz = 1;
00099           double bmips = 1;
00100           ACE_TCHAR arg[128];
00101 
00102           // CPU type?
00103           if (::sscanf (buf, "cpu : %s\n", arg) == 1)
00104             {
00105               // If this is an Alpha chip, then the BogoMips rating is
00106               // usable...
00107               if (ACE_OS::strncmp (arg,
00108                                    "Alpha",
00109                                    5) == 0)
00110                 {
00111                   supported = 1;
00112                   // ACE_DEBUG ((LM_DEBUG, ACE_LIB_TEXT (" recognized Alpha chip...")));
00113                 }
00114             }
00115           // Pentium CPU model?
00116           else if (supported == 0
00117                    && ::sscanf (buf, "model name : Pentium %s\n", arg) == 1)
00118             {
00119               // But if we don't have the right kind of Intel chip,
00120               // just quit.
00121               if (ACE_OS::strcmp (arg, "II") == 0
00122                   || ACE_OS::strcmp (arg, "III") == 0
00123                   || ACE_OS::strcmp (arg, "IV") == 0
00124                   || ACE_OS::strcmp (arg, "Pro") == 0)
00125                 {
00126                   supported = 1;
00127                   // ACE_DEBUG ((LM_DEBUG, ACE_LIB_TEXT (" recognized Pentium Pro/II chip...")));
00128                 }
00129             }
00130           else if (::sscanf (buf, "cpu MHz : %lf\n", &mhertz) == 1)
00131             {
00132               // If the line "cpu MHz : xxx" is present, then it's a
00133               // reliable measure of the CPU speed - according to the
00134               // kernel-source.
00135               scale_factor = (ACE_UINT32) (mhertz + 0.5);
00136               break;
00137             }
00138           else if (::sscanf (buf, "bogomips : %lf\n", &bmips) == 1
00139                    || ::sscanf (buf, "BogoMIPS : %lf\n", &bmips) == 1)
00140             {
00141               if (supported)
00142                 {
00143                   scale_factor = (ACE_UINT32) (bmips + 0.5);
00144                   // ACE_DEBUG ((LM_DEBUG, ACE_LIB_TEXT (" setting the clock scale factor to %u"), scale_factor));
00145                 }
00146 #if 0
00147               else
00148                 {
00149                   ACE_DEBUG ((LM_DEBUG,
00150                               ACE_LIB_TEXT ("\nThe BogoMIPS metric is not supported on this platform"
00151                                          "\n\tReport the results of the clock calibration and"
00152                                          "\n\tthe contents of /proc/cpuinfo to the ace-users mailing list")));
00153                 }
00154 #endif /* 0 */
00155               break;
00156             }
00157 #endif /* __alpha__ */
00158         }
00159 
00160       // ACE_DEBUG ((LM_DEBUG, ACE_LIB_TEXT (" (done)\n")));
00161 
00162       ACE_OS::fclose (cpuinfo);
00163     }
00164 
00165   return scale_factor;
00166 }
00167 #endif /* linux */
00168 
00169 ACE_UINT32
00170 ACE_High_Res_Timer::global_scale_factor (void)
00171 {
00172 #if (defined (ACE_WIN32) || defined (ACE_HAS_POWERPC_TIMER) || \
00173      defined (ACE_HAS_PENTIUM) || defined (ACE_HAS_ALPHA_TIMER)) && \
00174     !defined (ACE_HAS_HI_RES_TIMER) && \
00175     ((defined (ACE_WIN32) && !defined (ACE_HAS_WINCE)) || \
00176      defined (ghs) || defined (__GNUG__) || defined (__KCC) || \
00177      defined (__INTEL_COMPILER))
00178   // Check if the global scale factor needs to be set, and do if so.
00179   if (ACE_High_Res_Timer::global_scale_factor_status_ == 0)
00180     {
00181       // Grab ACE's static object lock.  This doesn't have anything to
00182       // do with static objects; it's just a convenient lock to use.
00183       ACE_MT (ACE_GUARD_RETURN (ACE_Recursive_Thread_Mutex, ace_mon,
00184                                 *ACE_Static_Object_Lock::instance (), 0));
00185 
00186       // Double check
00187       if (ACE_High_Res_Timer::global_scale_factor_status_ == 0)
00188         {
00189 #         if defined (ACE_WIN32)
00190             LARGE_INTEGER freq;
00191             if (::QueryPerformanceFrequency (&freq))
00192               {
00193                 // We have a high-res timer
00194 #             if defined (ACE_LACKS_LONGLONG_T)
00195                 ACE_UINT64 uint64_freq(freq.u.LowPart, (ACE_UINT32) freq.u.HighPart);
00196                 ACE_High_Res_Timer::global_scale_factor
00197                   (uint64_freq / (ACE_UINT32) ACE_ONE_SECOND_IN_USECS);
00198 #             else
00199                 ACE_High_Res_Timer::global_scale_factor
00200                   (ACE_static_cast (unsigned int,
00201                                     freq.QuadPart / ACE_HR_SCALE_CONVERSION));
00202 #             endif // (ACE_LACKS_LONGLONG_T)
00203 
00204                 ACE_High_Res_Timer::global_scale_factor_status_ = 1;
00205               }
00206             else
00207               // High-Res timers not supported
00208               ACE_High_Res_Timer::global_scale_factor_status_ = -1;
00209 
00210             return ACE_High_Res_Timer::global_scale_factor_;
00211 
00212 #         elif defined (linux)
00213             ACE_High_Res_Timer::global_scale_factor (ACE_High_Res_Timer::get_cpuinfo ());
00214 #         endif /* ! ACE_WIN32 && ! (linux && __alpha__) */
00215 
00216 #         if !defined (ACE_WIN32)
00217           if (ACE_High_Res_Timer::global_scale_factor_ == 1u)
00218             // Failed to retrieve CPU speed from system, so calculate it.
00219             ACE_High_Res_Timer::calibrate ();
00220 #         endif // (ACE_WIN32)
00221         }
00222     }
00223 
00224   ACE_High_Res_Timer::global_scale_factor_status_ = 1;
00225 #endif /* (ACE_WIN32 || ACE_HAS_POWERPC_TIMER || \
00226            ACE_HAS_PENTIUM || ACE_HAS_ALPHA_TIMER) && \
00227           ! ACE_HAS_HIGH_RES_TIMER &&
00228           ((WIN32 && ! WINCE) || ghs || __GNUG__) */
00229 
00230   return ACE_High_Res_Timer::global_scale_factor_;
00231 }
00232 
00233 ACE_High_Res_Timer::ACE_High_Res_Timer (void)
00234 {
00235   ACE_TRACE ("ACE_High_Res_Timer::ACE_High_Res_Timer");
00236 
00237   this->reset ();
00238 
00239   // Make sure that the global scale factor is set.
00240   (void) global_scale_factor ();
00241 }
00242 
00243 ACE_UINT32
00244 ACE_High_Res_Timer::calibrate (const ACE_UINT32 usec,
00245                                const u_int iterations)
00246 {
00247   const ACE_Time_Value sleep_time (0, usec);
00248   ACE_Stats delta_hrtime;
00249   // In units of 100 usec, to avoid overflow.
00250   ACE_Stats actual_sleeps;
00251 
00252   for (u_int i = 0;
00253        i < iterations;
00254        ++i)
00255     {
00256       const ACE_Time_Value actual_start =
00257         ACE_OS::gettimeofday ();
00258       const ACE_hrtime_t start =
00259         ACE_OS::gethrtime ();
00260       ACE_OS::sleep (sleep_time);
00261       const ACE_hrtime_t stop =
00262         ACE_OS::gethrtime ();
00263       const ACE_Time_Value actual_delta =
00264         ACE_OS::gettimeofday () - actual_start;
00265 
00266       // Store the sample.
00267       delta_hrtime.sample (ACE_U64_TO_U32 (stop - start));
00268       actual_sleeps.sample (actual_delta.msec () * 100u);
00269     }
00270 
00271   // Calculate the mean value of the samples, with no fractional
00272   // precision.  Use it for the global scale factor.
00273   ACE_Stats_Value ticks (0);
00274   delta_hrtime.mean (ticks);
00275 
00276   ACE_Stats_Value actual_sleep (0);
00277   actual_sleeps.mean (actual_sleep);
00278 
00279   // The addition of 5 below rounds instead of truncates.
00280   const ACE_UINT32 scale_factor =
00281     (ticks.whole () / actual_sleep.whole () + 5) /
00282     10u /* usec/100 usec */;
00283   ACE_High_Res_Timer::global_scale_factor (scale_factor);
00284 
00285   return scale_factor;
00286 }
00287 
00288 void
00289 ACE_High_Res_Timer::dump (void) const
00290 {
00291   ACE_TRACE ("ACE_High_Res_Timer::dump");
00292 
00293   ACE_DEBUG ((LM_DEBUG, ACE_BEGIN_DUMP, this));
00294   ACE_DEBUG ((LM_DEBUG, ACE_LIB_TEXT ("\nglobal_scale_factor_: %u\n"),
00295              global_scale_factor ()));
00296 #if defined (ACE_LACKS_LONGLONG_T)
00297   ACE_DEBUG ((LM_DEBUG,
00298              ACE_LIB_TEXT (":\nstart_.hi ():     %8x; start_.lo ():      %8x;\n")
00299              ACE_LIB_TEXT ("end_.hi ():       %8x; end_.lo ():        %8x;\n")
00300              ACE_LIB_TEXT ("total_.hi ():     %8x; total_.lo ():      %8x;\n")
00301              ACE_LIB_TEXT ("start_incr_.hi () %8x; start_incr_.lo (): %8x;\n"),
00302              start_.hi (), start_.lo (),
00303              end_.hi (), end_.lo (),
00304              total_.hi (), total_.lo (),
00305              start_incr_.hi (), start_incr_.lo ()));
00306 #else  /* ! ACE_LACKS_LONGLONG_T */
00307   ACE_DEBUG ((LM_DEBUG,
00308              ACE_LIB_TEXT (":\nstart_.hi ():     %8x; start_.lo ():      %8x;\n")
00309              ACE_LIB_TEXT ("end_.hi ():       %8x; end_.lo ():        %8x;\n")
00310              ACE_LIB_TEXT ("total_.hi ():     %8x; total_.lo ():      %8x;\n")
00311              ACE_LIB_TEXT ("start_incr_.hi () %8x; start_incr_.lo (): %8x;\n"),
00312              ACE_CU64_TO_CU32 (start_ >> 32),
00313              ACE_CU64_TO_CU32 (start_ & 0xfffffffful),
00314              ACE_CU64_TO_CU32 (end_ >> 32),
00315              ACE_CU64_TO_CU32 (end_ & 0xfffffffful),
00316              ACE_CU64_TO_CU32 (total_ >> 32),
00317              ACE_CU64_TO_CU32 (total_ & 0xfffffffful),
00318              ACE_CU64_TO_CU32 (start_incr_ >> 32),
00319              ACE_CU64_TO_CU32 (start_incr_ & 0xfffffffful)));
00320 #endif /* ! ACE_LACKS_LONGLONG_T */
00321   ACE_DEBUG ((LM_DEBUG, ACE_END_DUMP));
00322 }
00323 
00324 void
00325 ACE_High_Res_Timer::reset (void)
00326 {
00327   ACE_TRACE ("ACE_High_Res_Timer::reset");
00328 
00329   start_ = 0;
00330   end_ = 0;
00331   total_ = 0;
00332   start_incr_ = 0;
00333 }
00334 
00335 void
00336 ACE_High_Res_Timer::elapsed_time (ACE_Time_Value &tv) const
00337 {
00338   hrtime_to_tv (tv, end_ - start_);
00339 }
00340 
00341 #if defined (ACE_HAS_POSIX_TIME)
00342 // Note... Win32 does not have ACE_HAS_POSIX_TIME, so the scale factor
00343 // does not need to take into account the different units on Win32.
00344 
00345 void
00346 ACE_High_Res_Timer::elapsed_time (struct timespec &elapsed_time) const
00347 {
00348   // This implementation should be cleaned up.
00349 
00350   // Just grab the nanoseconds.  That is, leave off all values above
00351   // microsecond.  This equation is right!  Don't mess with me!  (It
00352   // first strips off everything but the portion less than 1 usec.
00353   // Then it converts that to nanoseconds by dividing by the scale
00354   // factor to convert to usec, and multiplying by 1000.)  The cast
00355   // avoids a MSVC 4.1 compiler warning about narrowing.
00356   u_long nseconds = ACE_static_cast (u_long,
00357                                      (this->end_ - this->start_) %
00358                                        global_scale_factor () * 1000u /
00359                                        global_scale_factor ());
00360 
00361   // Get just the microseconds (dropping any left over nanoseconds).
00362   ACE_UINT32 useconds = (ACE_UINT32) ((this->end_ - this->start_) / global_scale_factor ());
00363 
00364 #if ! defined(ACE_HAS_BROKEN_TIMESPEC_MEMBERS)
00365   elapsed_time.tv_sec = (time_t) (useconds / ACE_ONE_SECOND_IN_USECS);
00366   // Transforms one second in microseconds into nanoseconds.
00367   elapsed_time.tv_nsec = (time_t) ((useconds % ACE_ONE_SECOND_IN_USECS) * 1000u + nseconds);
00368 #else
00369   elapsed_time.ts_sec = (time_t) (useconds / ACE_ONE_SECOND_IN_USECS);
00370   // Transforms one second in microseconds into nanoseconds.
00371   elapsed_time.ts_nsec = (time_t) ((useconds % ACE_ONE_SECOND_IN_USECS) * 1000u + nseconds);
00372 #endif /* ACE_HAS_BROKEN_TIMESPEC_MEMBERS */
00373 }
00374 #endif /* ACE_HAS_POSIX_TIME */
00375 
00376 void
00377 ACE_High_Res_Timer::elapsed_time_incr (ACE_Time_Value &tv) const
00378 {
00379   hrtime_to_tv (tv, total_);
00380 }
00381 
00382 void
00383 ACE_High_Res_Timer::elapsed_time (ACE_hrtime_t &nanoseconds) const
00384 {
00385   // Please do _not_ rearrange this equation.  It is carefully
00386   // designed and tested to avoid overflow on machines that don't have
00387   // native 64-bit ints. In particular, division can be a problem.
00388   // For more background on this, please see bugzilla #1024.
00389 #if defined (ACE_WIN32)
00390   nanoseconds = (this->end_ - this->start_)
00391             * (1024000000u / ACE_High_Res_Timer::global_scale_factor());
00392 #else
00393   nanoseconds = (this->end_ - this->start_)
00394             * (1024000u / ACE_High_Res_Timer::global_scale_factor ());
00395 #endif /* ACE_WIN32 */
00396   // Caution - Borland has a problem with >>=, so resist the temptation.
00397   nanoseconds = nanoseconds >> 10;
00398   // Right shift is implemented for non native 64-bit ints
00399   // operator/ only for a 32 bit result !
00400 }
00401 
00402 void
00403 ACE_High_Res_Timer::elapsed_time_incr (ACE_hrtime_t &nanoseconds) const
00404 {
00405   // Same as above.
00406 #if defined (ACE_WIN32)
00407   nanoseconds = this->total_
00408             * (1024000000u / ACE_High_Res_Timer::global_scale_factor());
00409 #else
00410   nanoseconds = this->total_
00411             * (1024000u / ACE_High_Res_Timer::global_scale_factor ());
00412 #endif
00413   // Caution - Borland has a problem with >>=, so resist the temptation.
00414   nanoseconds = nanoseconds >> 10;
00415 }
00416 
00417 #if !defined (ACE_HAS_WINCE)
00418 void
00419 ACE_High_Res_Timer::print_ave (const ACE_TCHAR *str,
00420                                const int count,
00421                                ACE_HANDLE handle) const
00422 {
00423   ACE_TRACE ("ACE_High_Res_Timer::print_ave");
00424 
00425   // Get the total number of nanoseconds elapsed.
00426   ACE_hrtime_t total_nanoseconds;
00427   this->elapsed_time (total_nanoseconds);
00428 
00429   // Separate to seconds and nanoseconds.
00430   u_long total_secs =
00431     ACE_static_cast (u_long,
00432                      total_nanoseconds / (ACE_UINT32) ACE_ONE_SECOND_IN_NSECS);
00433   ACE_UINT32 extra_nsecs =
00434     ACE_static_cast (ACE_UINT32,
00435                      total_nanoseconds % (ACE_UINT32) ACE_ONE_SECOND_IN_NSECS);
00436 
00437   ACE_TCHAR buf[100];
00438   if (count > 1)
00439     {
00440       ACE_hrtime_t avg_nsecs = total_nanoseconds / (ACE_UINT32) count;
00441       ACE_OS::sprintf (buf,
00442                        ACE_LIB_TEXT (" count = %d, total (secs %lu, usecs %u), avg usecs = %lu\n"),
00443                        count,
00444                        total_secs,
00445                        (extra_nsecs + 500u) / 1000u,
00446                        (u_long) ((avg_nsecs + 500u) / 1000u));
00447     }
00448   else
00449     ACE_OS::sprintf (buf,
00450                      ACE_LIB_TEXT (" total %3lu.%06lu secs\n"),
00451                      total_secs,
00452                      (extra_nsecs + 500lu) / 1000lu);
00453 
00454   ACE_OS::write (handle,
00455                  str,
00456                  ACE_OS::strlen (str));
00457   ACE_OS::write (handle,
00458                  buf,
00459                  ACE_OS::strlen (buf));
00460 }
00461 
00462 void
00463 ACE_High_Res_Timer::print_total (const ACE_TCHAR *str,
00464                                  const int count,
00465                                  ACE_HANDLE handle) const
00466 {
00467   ACE_TRACE ("ACE_High_Res_Timer::print_total");
00468 
00469   // Get the total number of nanoseconds elapsed.
00470   ACE_hrtime_t total_nanoseconds;
00471   this->elapsed_time (total_nanoseconds);
00472 
00473   // Separate to seconds and nanoseconds.
00474   u_long total_secs =
00475     (u_long) (total_nanoseconds / (ACE_UINT32) ACE_ONE_SECOND_IN_NSECS);
00476   ACE_UINT32 extra_nsecs =
00477     (ACE_UINT32) (total_nanoseconds % (ACE_UINT32) ACE_ONE_SECOND_IN_NSECS);
00478 
00479   ACE_TCHAR buf[100];
00480   if (count > 1)
00481     {
00482       ACE_hrtime_t avg_nsecs = this->total_ / (ACE_UINT32) count;
00483 
00484       ACE_OS::sprintf (buf,
00485                        ACE_LIB_TEXT (" count = %d, total (secs %lu, usecs %u), avg usecs = %lu\n"),
00486                        count,
00487                        total_secs,
00488                        (extra_nsecs + 500u) / 1000u,
00489                        (u_long) ((avg_nsecs + 500u) / 1000u));
00490     }
00491   else
00492     ACE_OS::sprintf (buf,
00493                      ACE_LIB_TEXT (" total %3lu.%06u secs\n"),
00494                      total_secs,
00495                      (extra_nsecs + 500u) / 1000u);
00496 
00497   ACE_OS::write (handle,
00498                  str,
00499                  ACE_OS::strlen (str));
00500   ACE_OS::write (handle,
00501                  buf,
00502                  ACE_OS::strlen (buf));
00503 }
00504 #endif /* !ACE_HAS_WINCE */
00505 
00506 int
00507 ACE_High_Res_Timer::get_env_global_scale_factor (const ACE_TCHAR *env)
00508 {
00509 #if !defined (ACE_HAS_WINCE)
00510   if (env != 0)
00511     {
00512       const ACE_TCHAR *env_value = ACE_OS::getenv (env);
00513       if (env_value != 0)
00514         {
00515           int value = ACE_OS::atoi (env_value);
00516           if (value > 0)
00517             {
00518               ACE_High_Res_Timer::global_scale_factor (value);
00519               return 0;
00520             }
00521         }
00522     }
00523 #else
00524   ACE_UNUSED_ARG (env);
00525 #endif /* !ACE_HAS_WINCE */
00526   return -1;
00527 }

Generated on Mon Jun 16 11:19:52 2003 for ACE by doxygen1.2.14 written by Dimitri van Heesch, © 1997-2002