/* * CS 70, Fall 2002, Assignment 8 * * Implementation of the BarRequester and related classes. * * These classes take care of direct and indirect bar customers: * individual drinkers and table servers. See the associated header * file for a specification of the external behavior of the classes. */ #include "barrequester.hh" #include "randistrs.h" #include "simulationconstants.hh" #include "simulationtime.hh" #include #include #include #include using namespace std; /* * Allocation and initialization of static member variables. */ double BarRequester::serviceTime = SERVICE_TIME; // Mean service time for mixing a drink mt_distribution BarRequester::servicePRNG; // PRNG for generating service times SimulationTime BarRequester::suspensionPenalty = SUSPENSION_PENALTY; // (Constant) time penalty for // ..suspending service in the middle // ..of mixing a drink. int Drinker::totalServices = 0; // Total number of individual drinkers // ..served TimeStatistics Drinker::serviceStats; // General statistics on individual // ..drinkers int TableServer::totalServices = 0; // Total number of table servers // ..serviced by bartender int TableServer::totalDrinksServed = 0; // Total number of drinks mixed on // ..behalf of table servers TimeStatistics TableServer::serviceStats; // General statistics on table servers /* * Default constructor for TimeStatistics */ TimeStatistics::TimeStatistics() : totalTime(0), sumSquares(0), minTime(0), maxTime(0), count(0) { } /* * Destructor for TimeStatistics */ TimeStatistics::~TimeStatistics() { } /* * Record a time event, collecting the information needed to generate * statistics later: * * 1. The time is added to totalTime for use in calculating the mean. * 2. The square of the time is added to sumSquares for use in * calculating the standard deviation. * 3. minTime and maxTime are set if either (a) count was zero, * indicating that this is the first time that we've seen, or (b) * the value given is a new maximum or minimum. * 4. The count is incremented for use in calculating the mean and * standard deviation. */ void TimeStatistics::recordTime( SimulationTime time) // Observed time to be added to // ..statistics { assert(time >= 0); totalTime += time; sumSquares += (double)time.toInt() * time.toInt(); if (count == 0 || time < minTime) minTime = time; /* * Since times can only be positive, we don't need to check the * count. The first nonzero time we see will reset the maximum. */ if (time > maxTime) maxTime = time; /* * We save incrementing the count until last, so that the check * above for a zero count will work properly. */ count++; } /* * Report collected statistics to a stream. The statistics reported * include the mean, standard deviation (unbiased estimator), minimum, * and maximum. No statistics are output if none have been collected, * and the standard deviation is not produced if only one sample was * seen. * * For convenience in formatting the output, the caller can specify a * string prefix that is prepended to every line generated by this * routine. * * We finish our output lines with endl rather than '\n' so that we * will be able to see partial output if we crash. This is a minor * tradeoff of performance for debuggability. */ void TimeStatistics::reportStatistics( ostream& stream, // Stream to report to const string& prefix) // Prefix to prepend to every line const { if (count == 0) // Don't even try if nothing was seen return; /* * Produce the (rounded) mean. */ double mean = (double)totalTime.toInt() / count; stream << prefix << "mean time " << SimulationTime((int)(mean + 0.5)) << endl; /* * Produce the standard deviation, if we have enough samples to * calculate it. */ if (count > 1) { double variance = sumSquares - count * mean * mean; variance /= count - 1; stream << prefix << "standard deviation " << SimulationTime((int)(sqrt(variance) + 0.5)) << endl; } /* * Finally, do the minimum and maximum. */ stream << prefix << "minimum time " << minTime << endl; stream << prefix << "maximum time " << maxTime << endl; } /* * Default constructor for BarRequester. We set actualStartTime to * -1 so we can distinguish an unset start time from a start time of * zero. */ BarRequester::BarRequester() : scheduledStartTime(0), scheduledServiceTime(0), remainingServiceTime(0), actualStartTime(-1) { } /* * From-parameters constructor for BarRequester. */ BarRequester::BarRequester( SimulationTime scheduledStartTime_, // Time requester "arrives" SimulationTime serviceTime) // Service time needed to mix drink : scheduledStartTime(scheduledStartTime_), scheduledServiceTime(serviceTime), remainingServiceTime(serviceTime), actualStartTime(-1) { } /* * Copy constructor for BarRequester. */ BarRequester::BarRequester( const BarRequester& source) // Requester to copy : scheduledStartTime(source.scheduledStartTime), scheduledServiceTime(source.scheduledServiceTime), remainingServiceTime(source.remainingServiceTime), actualStartTime(source.actualStartTime) { } /* * Destructor for BarRequester. */ BarRequester::~BarRequester() { } /* * Assignment operator for BarRequester. */ BarRequester& BarRequester::operator=( const BarRequester& source) // Requester to assign from { if (this != &source) { scheduledStartTime = source.scheduledStartTime; scheduledServiceTime = source.scheduledServiceTime; remainingServiceTime = source.remainingServiceTime; actualStartTime = source.actualStartTime; } return *this; } /* * Return the time that a BarRequester is scheduled to begin service. */ SimulationTime BarRequester::dueTime() const { return scheduledStartTime; } /* * Begin service to a BarRequester. This is a virtual function that * is overridden by the derived classes. The base functionality * merely records the time when service began. Note that it is * possible to start service several times (if service to an * individual drinker was interrupted by the arrival of a table * server), so we must be careful to record only the first start. We * do that by taking advantage of the fact that the constructor * initializes actualStartTime to -1. */ void BarRequester::start( ostream& , // Stream to report activity to SimulationTime when) // Time when service was started { assert(when >= scheduledStartTime); if (actualStartTime < 0) actualStartTime = when; } /* * Finish service to a BarRequester. This is a virtual function that * is overridden by the derived classes. The base functionality * merely reports the end of service. If the service took longer than * the scheduled service time, the amount of the excess is also * reported. * * We finish our output lines with endl rather than '\n' so that we * will be able to see partial output if we crash. This is a minor * tradeoff of performance for debuggability. */ void BarRequester::finish( ostream& stream, // Stream to report activity to SimulationTime when) // Time when service was finished { /* * Report type of requester, intended start time, and actual * service time. */ stream << type() << " scheduled for " << scheduledStartTime << " serviced in " << actualServiceTime(when); /* * If the actual service time is excessive (due to queueing or * suspensions), report the excess. */ if (actualServiceTime(when) > scheduledServiceTime) stream << " (" << actualServiceTime(when) - scheduledServiceTime << " extra)"; stream << endl; } /* * Provide service to a BarRequester. We simply update the amount of * service remaining to be provided. */ void BarRequester::giveService( SimulationTime howMuchService) // Amount of service to provide { assert(howMuchService > 0); remainingServiceTime -= howMuchService; } /* * Find out if a BarRequester is done receiving service, which is * indicated by a remainingServiceTime of 0. */ bool BarRequester::isDone() const { assert(remainingServiceTime >= 0); return remainingServiceTime == 0; } /* * Suspend service to a BarRequester, and report that fact to a * stream. Suspension is handled by adding a penalty to the remaining * service time. The remainder of suspension (i.e., putting the * requester back on the queue) is done by the simulation loop. * * We finish our output lines with endl rather than '\n' so that we * will be able to see partial output if we crash. This is a minor * tradeoff of performance for debuggability. */ void BarRequester::suspend( ostream& stream, // Stream to report suspension to SimulationTime when) // When suspension happened { remainingServiceTime += suspensionPenalty; stream << "Suspending " << type() << " scheduled for " << dueTime() << " at " << when << endl; } /* * Set the static parameters for the BarRequester class. These * parameters are used to control the behavior of the simulation. */ void BarRequester::setParameters( double serviceTime_, // Mean default service time const mt_distribution& servicePRNG_, // PRNG to use for service times SimulationTime suspensionPenalty_) // Penalty for suspending service { serviceTime = serviceTime_; servicePRNG = servicePRNG_; suspensionPenalty = suspensionPenalty_; } /* * Calculate the actual amount of time needed to service a request. * This is defined as the difference between the scheduled start time * and the current time (which is presumably the completion time). */ SimulationTime BarRequester::actualServiceTime( SimulationTime when) // Time when service was completed const { assert (when >= scheduledStartTime); return when - scheduledStartTime; } /* * Default constructor for Drinker. */ Drinker::Drinker() : BarRequester() { } /* * From-parameters constructor for Drinker. The scheduled start time * is provided by the caller; the service time is calculated as an * exponentially-distributed random number. */ Drinker::Drinker( SimulationTime scheduledStartTime_) // Time drinker "arrives" : BarRequester(scheduledStartTime_, (int)(servicePRNG.exponential(serviceTime) + 0.5)) { } /* * Copy constructor for Drinker. */ Drinker::Drinker( const Drinker& source) // Drinker to copy : BarRequester(source) { } /* * Destructor for Drinker. */ Drinker::~Drinker() { } /* * Assignment operator for Drinker. */ Drinker& Drinker::operator=( const Drinker& source) // Drinker to assign from { /* * Since we have no additional data, the BarRequester assignment * operator does all the work. */ (BarRequester&)*this = source; return *this; } /* * Return a string indicating the type of a Drinker. This is a pure * virtual function overridden from the BarRequester class. */ string Drinker::type() const { return "drinker"; } /* * Begin service to a Drinker, and report that fact to a stream. This * is a virtual function that is overridden from the base class. Note * that we still use the base functionality. * * We finish our output lines with endl rather than '\n' so that we * will be able to see partial output if we crash. This is a minor * tradeoff of performance for debuggability. */ void Drinker::start( ostream& stream, // Stream to report activity to SimulationTime when) // Time when service was started { BarRequester::start(stream, when); // Do the common part stream << "Starting drinker scheduled for " << dueTime() << " at " << when << endl; } /* * Finish service to a Drinker. This is a virtual function that * is overridden from the base class. Note that we still use the base * functionality. */ void Drinker::finish( ostream& stream, // Stream to report activity to SimulationTime when) // Time when service was completed { BarRequester::finish(stream, when); // Do the common part /* * Record statistics about the service provided. */ totalServices++; serviceStats.recordTime(actualServiceTime(when)); } /* * Report statistics regarding the overall service provided to all * Drinkers. This is a static function that is called once at the end * of the simulation. * * We finish our output lines with endl rather than '\n' so that we * will be able to see partial output if we crash. This is a minor * tradeoff of performance for debuggability. */ ostream& Drinker::reportStatistics( ostream& stream) // Stream to report statistics to { stream << "Drinker statistics:\n"; stream << '\t' << totalServices << " customers served" << endl; serviceStats.reportStatistics(stream, "\t"); return stream; } /* * Default constructor for TableServer. */ TableServer::TableServer() : BarRequester() { } /* * From-parameters constructor for TableServer. The scheduled start * time is provided by the caller. The service time is calculated as * the sum of nDrinks exponentially-distributed random numbers; for * convenience this calculation is done by a private member function. */ TableServer::TableServer( SimulationTime scheduledStartTime_, int nDrinks_) : BarRequester(scheduledStartTime_, drinkServiceTime(nDrinks_)), nDrinks(nDrinks_) { } /* * Copy constructor for TableServer. */ TableServer::TableServer( const TableServer& source) // TableServer to copy : BarRequester(source), nDrinks(source.nDrinks) { } /* * Destructor for TableServer. */ TableServer::~TableServer() { } /* * Assignment operator for TableServer. */ TableServer& TableServer::operator=( const TableServer& source) { /* * The BarRequester assignment operator does nearly all of the work. */ (BarRequester&)*this = source; nDrinks = source.nDrinks; return *this; } /* * Return the number of drinks requested by a TableServer. */ int TableServer::drinkCount() const { return nDrinks; } /* * Return a string indicating the type of a TableServer. This is a * pure virtual function overridden from the BarRequester class. */ string TableServer::type() const { return "server"; } /* * Begin service to a TableServer, and report that fact to a stream. * This is a virtual function that is overridden from the base class. * Note that we still use the base functionality. * * We finish our output lines with endl rather than '\n' so that we * will be able to see partial output if we crash. This is a minor * tradeoff of performance for debuggability. */ void TableServer::start( ostream& stream, // Stream to report activity to SimulationTime when) // Time when service was started { BarRequester::start(stream, when); stream << "Starting server scheduled for " << dueTime() << " with " << drinkCount() << " drink(s) at " << when << endl; } /* * Finish service to a TableServer. This is a virtual function that * is overridden from the base class. Note that we still use the base * functionality. */ void TableServer::finish( ostream& stream, // Stream to report activity to SimulationTime when) // Time when service was completed { BarRequester::finish(stream, when); /* * Record statistics about the service provided. */ totalServices++; serviceStats.recordTime(actualServiceTime(when)); totalDrinksServed += nDrinks; } /* * Report statistics regarding the overall service provided to all * TableServers. This is a static function that is called once at the * end of the simulation. */ ostream& TableServer::reportStatistics( ostream& stream) { stream << "Table server statistics:\n"; stream << '\t' << totalServices << " servers sold " << totalDrinksServed << " drinks" << endl; serviceStats.reportStatistics(stream, "\t"); return stream.flush(); } /* * Calculate the total amount of time that will be needed to provide * service to a TableServer. This time is calculated as the sum of * the service times needed to mix each of the individual drinks. * (Note that, for even moderately large numbers of drinks, the * Central Limit Theorem tells us that the total service time * approaches a Gaussian distribution rather than the exponential * distribution of the individual-drink service time. */ SimulationTime TableServer::drinkServiceTime( int nDrinks) // Number of drinks to calculate the // ..service time for { SimulationTime totalServiceTime = 0; for (int i = 0; i < nDrinks; i++) totalServiceTime += (SimulationTime)(int)(servicePRNG.exponential(serviceTime) + 0.5); assert (totalServiceTime >= 0); return totalServiceTime; }