//---------------------------------------------------------------------------- // Copyright (C) 2002, 2003 Humboldt-Universitaet zu Berlin // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // //---------------------------------------------------------------------------- // // Includes // // We include the main ODEMx header file odemx.h which in // turn includes the other header files of ODEMx. All // header files of ODEMx define a macro when included, // to prevent repetitive inclusion. That means it doesn't // hurt to include a header file more than ones, because // subsequent inclusions are ignored. #include <odemx/odemx.h> // Apart from ODEMx we use some C++ standard features in // this simulation. #include <iostream> #include <vector> #include <string> #include <list> // // Namespaces // // All classes of ODEMx are placed in the namespace odemx // to prevent conflicts. In this simulation it is save to // use the whole odemx namespace. using namespace odemx; using namespace std; // // Global parameters // // To simplify matters we use global parameters in this example. // That is however not recommended in general. A better place // for simulation parameters is the specific simulation class. // // GOODS contains the different commodities sold in the shop. static const char* GOODS[] = {"Apple", "Bananas", "Bread", "Butter", "Candies", "Cheese", "Chips", "Chocolate", "Mixed-pickles", "Fish", "Jam", "Juice", "Lemon", "Lemonade", "Marmalade", "Milk", "Orange", "Pepper", "Pineapples", "Pork", "Salami", "Salt", "Sugar", "Tomatoes", "Water"}; // NUMBER_OF_GOODS is used in many for-loops as the end-condition. static const int NUMBER_OF_GOODS = sizeof(GOODS) / sizeof(const char*); // INIT_STOCK is the initial amount of each good hold in the // store room. static const int INIT_STOCK = 50; // INIT_TILLS is the initial number of tills available. static const int INIT_TILLS = 5; // INIT_PERSONAL is the initial number of shop-assistants // employed by the shopkeeper. static const int INIT_PERSONAL = 8; // SHELF_CAPACITY defines the capacity of a shelf. The capacity // is measured in items, not in size. That means every shelf // holds up to SHELF_CAPACITY items of its commodity. static const int SHELF_CAPACITY = 15; // ACCEPTED_QUEUE_LENGTH defines the queue length after // which a shop-assistant will help out at the tills. static const int ACCEPTED_QUEUE_LENGTH = 5; // ACCEPTED_SHELF_STOCK defines the number of items in // a shelf after which the shelf has to be refilled. static const int ACCEPTED_SHELF_STOCK = 5; // The following parameters define delays for payment, // for refilling shelves, for taking goods from a shelf, for moving // between shelves, and they define the time after which the shopkeeper // is checking the store room of the shop again. static const SimTime PAYMENT_DELAY = 5.0; static const SimTime RESTOCK_DELAY = 15.0; static const SimTime TAKE_DELAY = 1.0; static const SimTime MOVE_DELAY = 1.0; static const SimTime STOCKKEEPER_DELAY = 60.0; // // Pre-declarations // class Shelf; class Tills; class Shopkeeper; class Assistant; class Customer; class CustomerGen; // // Classes // // // The class ShopSim is derived from Simulation and provides // access to some important objects of this simulation. All // classes which have a pointer to ShopSim can use the get* // methods to get these objects. In addition we could move // the global parameters to this class. ShopSim also // does some initialisations in its constructor and the // initSimulation() function. // class ShopSim : public Simulation { // Shelves in the shop, one for each commodity vector<Shelf*> shelves; // Back-store for refilling shelves vector<int> stock; // Customers pay at one of the tills Tills* tills; Shopkeeper* boss; CustomerGen* generator; // RNGs Randint *rng5, *rng10, *rng; Negexp* delay; // Assistant-management Condq* work; // Output HtmlReport* report; HtmlTrace* tillTrace; public: // Construction and initialisation ShopSim(); virtual void initSimulation(); // get*() methods vector<Shelf*>& getShelves() {return shelves;} vector<int>& getStock() {return stock;} Tills* getTills() {return tills;} Shopkeeper* getBoss() {return boss;} CustomerGen* getCustomerGen() {return generator;} Idist* getRNG() {return rng;} Idist* getRNG5() {return rng5;} Idist* getRNG10() {return rng10;} Rdist* getDelay() {return delay;} Condq* getWork() {return work;} Report* getReport() {return report;} }; // // Shelf represents a shelf in our shop. Every shelf // contains one commodity and provides methods for // customers to take goods. The shop-assistants refill // a shelf if its getting out of stock. // The management of commodity items hold in a shelf // is realised with the ODEMx standard component Res. // class Shelf { // Pointer to ShopSim ShopSim* sim; // The type of commodity presented in this shelf int type; // The amount of goods left in this shelf Res commodity; public: // Construction Shelf(ShopSim* s, int t); // Type of commodity presented in this shelf int getType() const {return type;} // Customers use this method to take goods. int takeCommodity(int n); // Assistants use this method to restock the shelf. int fillShelf(int n); // Assistants use this method to check the stock. int checkStock() const {return commodity.getTokenNumber();} }; // // After taking their goods customers go to the tills // to pay. There are several tills available but a till // has also to be handled by a shop-assistant to be active. // The payment is managed with the ODEMx standard component // Waitq which realises the synchronisation between customers // giving the money and shop-assistants taking it. The Tills // also run a statistic about the number of assistants using // the ODEMx class Accum. // class Tills { // Pointer to ShopSim ShopSim* sim; // Customers wait in a Waitq for service. Waitq queue; // Tills runs statistics about the assistants. Accum* statistic; // The number of assistants handling the tills. int cashier; // The number of tills available. int tills; public: // Construction Tills(ShopSim* s); // Customers use the method pay() to enter the queue (enqueue) at the // tills and to pay. void pay(); // Assistants use take to get the next customer. Customer* take(); // The number of shop-assistants who take the payment changes, // updated with updatedCashier(). void updateCashier(int n); // get* methods int getQueueLength() const {return queue.getWaitingSlaves().size();} int getCashier() const {return cashier;} int getTills() const {return tills;} }; // // The shopkeeper is responsible for the employment of shop-assistants // and the ordering of goods. The class Shopkeeper is derived from // Process and defines the behaviour of the shopkeeper by implementing // the function main(). // class Shopkeeper : public Process { public: // Construction Shopkeeper(ShopSim* sim); // Behaviour virtual int main(); }; // // Shop-assistants represent the work-force driving the shop. They // refill the shelves or handle a till. The class Assistant defines // the behaviour of the shop-assistants by implementing main(). // Shop-assistants decide on their own which task they fulfil // next (handle a till or refill shelves). If neither is required // they wait for work in a conditional queue provided by ODEMx and // kept in the ShopSim class (ShopSim::work). // class Assistant : public Process { public: // Construction Assistant(ShopSim* sim); // Behaviour virtual int main(); // chooseWork and work are used to find the next task for an // assistant or to wait for work. int chooseWork(); bool work(); }; // // This class represents the customers in this simulation. A // customer has (random) demand which he/she tries to fulfil // by taking goods from the shelves. Finally, a customer pays // the taken goods at the tills. // class Customer : public Process { // The customers demand. vector<int> demand; // The goods in the customers basket. vector<int> basket; public: // Construction Customer(ShopSim* sim); // Behaviour virtual int main(); }; // // The Customers are randomly created by CustomerGen. By this means // we achieve an approximation of the customer-flow in our shop. // class CustomerGen : public Process { public: // Construction CustomerGen(ShopSim* sim); // Behaviour virtual int main(); }; // // Implementations of member functions // // // Construction of a ShopSim object itself and some other major // objects. // ShopSim::ShopSim() : Simulation("ShopSim"), shelves(NUMBER_OF_GOODS), stock(NUMBER_OF_GOODS) { // HtmlReport and HtmlTrace are used to gather the results // of a simulation. The data are written in the two files // 'ShopSim-Report.html' and 'ShopSim-TillTrace.html'. report = new HtmlReport(this, "ShopSim-Report.html"); tillTrace = new HtmlTrace(this, "ShopSim-TillTrace.html"); // HtmlTrace fortunately provides a filter to reduce the // output. We are just taking logging information from the tills. // Without such precautions we would get a 24 Mbytes trace. tillTrace->setFilter("none; sl:Tills"); // To receive the simulation events we have to register our // trace to the simulation. addConsumer(tillTrace); // Create and initialise shelves, one shelf for each commodity. for (int i=0; i<NUMBER_OF_GOODS; ++i) { shelves[i] = new Shelf(this, i); stock[i] = INIT_STOCK; } // Create the tills, the boss, and the customer generator. tills = new Tills(this); boss = new Shopkeeper(this); generator = new CustomerGen(this); // Create the random number generators. // Randint and Negexp are provided by ODEMx. rng = new Randint(this, "RNG", 0, NUMBER_OF_GOODS); rng5 = new Randint(this, "RNG", 0, 5); rng10 = new Randint(this, "RNG", 0, 10); delay = new Negexp(this, "Delay", 0.125); // We also want to have a look at the statistics from our RNGs. // To get these we have to add them to the HtmlReport object. report // will gather information only from registered objects. report->addProducer(rng); report->addProducer(rng5); report->addProducer(rng10); report->addProducer(delay); // Finally, the Condq work is created and added to the report. work = new Condq(this, "Work"); report->addProducer(work); } // // initSimulation is called before the simulation starts. We // use this function to activate the boss and the customer // generator. They in turn create the other processes. // The initSimulation function is some sort of an entry-point // of a simulation. It is called before the simulation // starts and is the best place to activate the first processes // of a simulation. // void ShopSim::initSimulation() { boss->activate(); generator->activate(); } // // Construction of a Shelf object. // Shelf::Shelf(ShopSim* s, int t) : sim(s), type(t), commodity(s, (string("Shelf_") + GOODS[t]).c_str(), 0, SHELF_CAPACITY) { // Shelf is using a Res object to manage the amount of goods // available. Res is providing some report about its usage. // To add this report to our HtmlReport we register the Res // object. s->getReport()->addProducer(&commodity); } // // fillShelf is used by the shop-assistants to restock the shelf. // In this version the function is only a wrapper to the release // method of the Res object which is actually managing the available // goods. // int Shelf::fillShelf(int n) { commodity.release(n); return commodity.getTokenNumber(); } // // takeCommodity is used by the customers to get the goods. We // could just relay the call to commodity.acquire(). But than a // customer would wait until there are enough items available. // Instead we signal the shop-assistants the need to refill a // shelf and give the customer only the items available. // int Shelf::takeCommodity(int n) { int avail = commodity.getTokenNumber(); if (avail > n) { commodity.acquire(n); return n; } else { commodity.acquire(avail); sim->getWork()->signal(); return avail; } } // // Construction of the Tills object. // Tills::Tills(ShopSim* s) : sim(s), queue(s, "Tills"), cashier(0), tills(INIT_TILLS) { // Tills is providing some additional statistics about // the number of assistants handling the tills. statistic = new Accum(sim, "Tills statistics"); // We add these statistics along with the information from // the utilised Waitq object to the report. s->getReport()->addProducer(&queue); s->getReport()->addProducer(statistic); } // // Payment is done by the functions pay and take. There are // two objects working together to handle payment. A customer // giving the money and a shop-assistant taking it. Both have to // be synchronised. Tills uses the standard ODEMx synchronisation // object Waitq to do this. // void Tills::pay() { // Every time a customer wants to pay, waiting assistants // are signalled. sim->getWork()->signal(); // The synchronisation between a customer (who wants // to pay) and an assistant is realised with Waitq. Waitq // manages not only the synchronisation but also provides // statistics. The wait() function used here adds the // current process (the one that has just called pay()) to the // queue. queue.wait(); } // // Assistants use take to get the next customer. // Customer* Tills::take() { Process* p = 0; // The coopt() function of Waitq returns the next Customer // in the queue(sorted according to priority and time of arrival). // If there is no customer left the current process // (the one that has just called take()) waits. p = queue.coopt(); return (Customer*) p; } // // The number of assistants working at the tills is updated // with this function. In this function the statistic about // assistants handling tills is also updated. // void Tills::updateCashier(int n) { cashier += n; statistic->update(cashier); } // // Construction of Assistant objects. // Assistant::Assistant(ShopSim* sim) : Process(sim, "Assistant") {} // // Assistant is derived from Process. That means it has to // provide its own main() function, where it defines the actions // done by an Assistant. // int Assistant::main() { // // At first we cache pointers to our simulation // and to the tills. // ShopSim* sim = (ShopSim*)getSimulation(); Tills* tills = sim->getTills(); while (true) { // // Wait for something to do: // Assistants have to do two different tasks. // If required they handle a till to take the // money from the customers. Otherwise they go // through the shop and refill empty shelves. // If neither is required they wait for work. // ShopSim uses the ODEMx synchronisation object // Condq to keep track of waiting Assistants. // Condq allows processes to wait for certain // conditions defined by a condition call-back // function. Assistant's member function work() // was designed to define the condition when an // Assistant has to act. // The first real action of an assistant is to // wait for a task. // sim->getWork()->wait((Condition)&Assistant::work); // // Choose your work: // The function chooseWork() determines what has // to be done. It returns 1 if an assistant is // needed at the tills and 2 if a shelf has to be // refilled. if (chooseWork()==1) { // // Cashier task // // update number of assistants at the tills tills->updateCashier(1); // serve customers as long as there are some while (tills->getQueueLength()>0) { // get the next customer in queue Customer* c = tills->take(); // payment does take some time holdFor(PAYMENT_DELAY); // finally, activate the customer c->activate(); } // update number of assistants at the tills tills->updateCashier(-1); } else { // // Restock task // // The assistant goes through all shelves and // refills if necessary and possible. vector<Shelf*>::iterator i; for (i=sim->getShelves().begin(); i!=sim->getShelves().end(); ++i) { // We cache some pointer. Shelf* shelf = *i; vector<int>& stock = sim->getStock(); int type = shelf->getType(); if (shelf->checkStock() < ACCEPTED_SHELF_STOCK && stock[type] > 0) { // A shelf is filled up to its capacity // if possible. int n = SHELF_CAPACITY - shelf->checkStock(); if (stock[type] < n) n = stock[type]; shelf->fillShelf(n); stock[type] -= n; // refilling takes time holdFor(RESTOCK_DELAY); } } } } // This one will never be reached. return 0; } // // chooseWork() decides which task should be done. // int Assistant::chooseWork() { ShopSim* sim = (ShopSim*)getSimulation(); vector<Shelf*>::iterator i; // If the queue at the tills has exceeded an acceptable // length and we have a free till without an assistant, // the assistant should work at it. if (sim->getTills()->getQueueLength() >= ACCEPTED_QUEUE_LENGTH && sim->getTills()->getCashier() < sim->getTills()->getTills()) return 1; // Otherwise, if there is a shelf out of stock while we have // still goods left, the assistant should go on the refill-route. for (i=sim->getShelves().begin(); i!=sim->getShelves().end(); ++i) { if ((*i)->checkStock()<ACCEPTED_SHELF_STOCK && (sim->getStock())[(*i)->getType()] > 0) return 2; } // If neither is required the assistant should wait. return 0; } // // The condition call-back work() uses the function chooseWork() // to find something to do. // bool Assistant::work() { return chooseWork()!=0; } // // Construction of the Shopkeeper object. // Shopkeeper::Shopkeeper(ShopSim* sim) : Process(sim, "Shopkeeper") {} // // Like Assistant Shopkeeper is a Process and has to // define its behaviour in the main() function. // int Shopkeeper::main() { ShopSim* sim = (ShopSim*)getSimulation(); Tills* tills = sim->getTills(); // // The first task of the shopkeeper is to employ // some shop-assistants. // int i; for (i=0; i<INIT_PERSONAL; ++i) { Assistant* person = new Assistant(sim); person->activate(); } // // Afterwards the only thing the shopkeeper has to do // is to look after the stock of goods in his shop. // while (true) { // // Order goods: // We check all goods; if something runs out // we fill it up again. // vector<int>& stock = sim->getStock(); for (i=0; i<NUMBER_OF_GOODS; ++i) { if (stock[i]==0) stock[i]=INIT_STOCK; } // // Wait some time // holdFor(STOCKKEEPER_DELAY); } return 0; } // // Construction of the Customer objects. // Customer::Customer(ShopSim* sim) : Process(sim , "Customer"), demand(NUMBER_OF_GOODS), basket(NUMBER_OF_GOODS) {} // // The Customer wants a random amount of a random number of // commodities. The customer tries to take the desired items // from the shelves, pays at the tills, and finally, leaves the // simulation. // int Customer::main() { // We cache some pointer. ShopSim* sim = (ShopSim*)getSimulation(); vector<Shelf*>& shelves = sim->getShelves(); Tills* tills = sim->getTills(); Idist* rng = sim->getRNG(); Idist* rng5 = sim->getRNG5(); Idist* rng10 = sim->getRNG10(); // // Init wish-list // int i; // A customer wants up to 10 different types of goods. int numberOfItems = rng10->sample(); for (i=0; i<numberOfItems; ++i) { int type = rng->sample(); int amount = rng5->sample(); demand[type] = amount; } // // Get goods from shelves // for (i=0; i<NUMBER_OF_GOODS; ++i) { if (demand[i]==0) { holdFor(MOVE_DELAY); continue; } Shelf* shelf = shelves[i]; basket[i] = shelf->takeCommodity(demand[i]); holdFor(TAKE_DELAY + MOVE_DELAY); } // // Pay at a till // tills->pay(); return 0; } // // Construction of the CustomerGen object. // CustomerGen::CustomerGen(ShopSim* sim) : Process(sim, "CustomerGen") {} // // CustomerGen creates Customers. // int CustomerGen::main() { // Cash the ShopSim pointers. ShopSim* sim = (ShopSim*)getSimulation(); Customer* c = 0; SimTime delay; // CustomerGen adds another statistic to our report. // It uses the ODEMx class Histo to fill a histogram // about the actual delays. It doesn't matter to the // report when another participant is added. The // content of a report is defined by all registered // report producers at the moment the function // generateReport() is called. (In this example we // don't care about memory leaks.) Histo* histo = new Histo(sim,"CustomerGen Delays", 0.0, 60.0, 10); sim->getReport()->addProducer(histo); while (true) { // Create and activate a new Customer. c = new Customer(sim); c->activate(); // Wait a random time and update statistic. delay = sim->getDelay()->sample(); histo->update(delay); holdFor(delay); } return 0; } // // Main: // In the global main() function we finally create an object // of the type ShopSim, start the trace, run the simulation for // two simulated days, stop trace and generate the report. // int main(int argc, char* argv[]) { // // Using a local variable to hold the ShopSim object is // save because the stack of the global main() function // won't be touched during a simulation. The stacks of // the Process main() functions however are not available // to each other or to the global main(). You can of course // use dynamic allocation as seen in CustomerGen::main(). // ShopSim sim; // // Logging of simulation events is not started before // startTrace() is called. If you want to break the // logging temporarily use pauseTrace() and continueTrace(). // sim.startTrace(); // // The Simulation class provides different functions to // compute the simulation. runUntil() starts computation // until the given simulation time is reached. We have to // use this method because our simulation does not terminate // on its own. Which would be the case, if a Process calls // exitSimulation() at ShopSim or if there would be no // process scheduled for execution. // sim.runUntil(60 * 24 * 2); // // The stopTrace() function is terminating the trace. It // cannot be started again afterwards! // sim.stopTrace(); // // The report is generated at this moment. All registered // objects must be alive and ready to add their parts to // the report. // sim.getReport()->generateReport(); return 0; }