// -------------------------------------------------- // --- 159.341 Assignment 2 - Lift Simulator --- // -------------------------------------------------- // Written by M. J. Johnson // Updated by D. P. Playne - 2019 #include #include #include #include "lift.h" // -------------------------------------------------- // Define Problem Size // -------------------------------------------------- #define NLIFTS 4 // The number of lifts in the building #define NFLOORS 20 // The number of floors in the building #define NPEOPLE 20 // The number of people in the building #define MAXNOINLIFT 10 // Maximum number of people in a lift // -------------------------------------------------- // Define delay times (in milliseconds) // -------------------------------------------------- //#define SUPERSLOW //#define SLOW //#define FAST #define SUPERFAST #if defined(SUPERSLOW) #define LIFTSPEED 500 // The time it takes for the lift to move one floor #define GETINSPEED 250 // The time it takes to get into the lift #define GETOUTSPEED 250 // The time it takes to get out of the lift #define PEOPLESPEED 10000 // The maximum time a person spends on a floor #elif defined(SLOW) #define LIFTSPEED 250 // The time it takes for the lift to move one floor #define GETINSPEED 100 // The time it takes to get into the lift #define GETOUTSPEED 100 // The time it takes to get out of the lift #define PEOPLESPEED 100 // The maximum time a person spends on a floor #elif defined(FAST) #define LIFTSPEED 25 // The time it takes for the lift to move one floor #define GETINSPEED 10 // The time it takes to get into the lift #define GETOUTSPEED 10 // The time it takes to get out of the lift #define PEOPLESPEED 10 // The maximum time a person spends on a floor #elif defined(SUPERFAST) #define LIFTSPEED 0 // The time it takes for the lift to move one floor #define GETINSPEED 0 // The time it takes to get into the lift #define GETOUTSPEED 0 // The time it takes to get out of the lift #define PEOPLESPEED 1 // The maximum time a person spends on a floor #endif // -------------------------------------------------- // Define lift directions (UP/DOWN) // -------------------------------------------------- #define UP 1 #define DOWN -1 // Define the lower-case true so that this compiles directly in Windows (GCC 9.2) #if defined(_WIN32) || defined(WIN32) #define true 1 #endif // Print the number of people in the lifts & on the floors //#define POPULATIONS // -------------------------------------------------- // Information about a floor in the building // -------------------------------------------------- typedef struct { int waitingtogoup; // The number of people waiting to go up int waitingtogodown; // The number of people waiting to go down semaphore up_arrow; // People going up wait on this semaphore down_arrow; // People going down wait on this semaphore queueInteraction; // People can't all press a button at the same time } floor_info; // -------------------------------------------------- // Information about a lift // -------------------------------------------------- typedef struct { int no; // The lift number (id) int position; // The floor it is on int direction; // Which direction it is going (UP/DOWN) int peopleinlift; // The number of people in the lift int stops[NFLOORS]; // How many people are going to each floor semaphore stopsem[NFLOORS]; // People in the lift wait on one of these semaphore addToStop; } lift_info; // -------------------------------------------------- // Some global variables // -------------------------------------------------- floor_info floors[NFLOORS]; lift_info *targetLift[NFLOORS]; // 2-dimensional array of the lift semaphore targetLiftSelect[NFLOORS]; // Bound all printing to this semaphore semaphore printSemaphore; // If printing people counts, int array and semaphore to keep track of them #ifdef POPULATIONS semaphore updateFloorCount; int floorCount[NFLOORS]; // count of times since value was zero, used to help find a stuck person int floorCountZeroInc[NFLOORS]; // -------------------------------------------------- // Print a string on the screen at position (x,y) // -------------------------------------------------- void print_at_xy_fast(int x, int y, const char *s) { // Lock our print to just one thread (stops x,y from changing) semaphore_wait(&printSemaphore); // Move cursor to (x,y) gotoxy(x, y); // Slow things down Sleep(0); // Print the string printf("%s", s); // Move cursor out of the way gotoxy(42, NFLOORS + 2); // flush the stream and release the semaphore //fflush(stdout); semaphore_signal(&printSemaphore); } #endif // -------------------------------------------------- // Print a string on the screen at position (x,y) // -------------------------------------------------- void print_at_xy(int x, int y, const char *s) { // Lock our print to just one thread (stops x,y from changing) semaphore_wait(&printSemaphore); // Move cursor to (x,y) gotoxy(x, y); // Slow things down Sleep(1); // Print the string printf("%s", s); // Move cursor out of the way gotoxy(42, NFLOORS + 2); // flush the stream and release the semaphore //fflush(stdout); semaphore_signal(&printSemaphore); } // -------------------------------------------------- // Function for a lift to pick people waiting on a // floor going in a certain direction // -------------------------------------------------- void get_into_lift(lift_info *lift, int direction) { // Local variables int *waiting; semaphore *s; // Check lift direction if (direction == UP) { // Use up_arrow semaphore s = &floors[lift->position].up_arrow; // Number of people waiting to go up waiting = &floors[lift->position].waitingtogoup; } else { // Use down_arrow semaphore s = &floors[lift->position].down_arrow; // Number of people waiting to go down waiting = &floors[lift->position].waitingtogodown; } // Problems here are primarily state consistency. Between the start of the conditional, the number of waiting people can change, resulting // in the number of waiting people dropping below zero. // For all the people waiting while (*waiting) { // Check if lift is empty if (lift->peopleinlift == 0) { // Set the direction of the lift lift->direction = direction; } // Check there are people waiting and lift isn't full. Ignore in-progress (moving into lift) people // must check here semaphore_wait(&floors[lift->position].queueInteraction); if (lift->peopleinlift < MAXNOINLIFT && *waiting) { // Add person to the lift lift->peopleinlift++; print_at_xy(NLIFTS * 4 + floors[lift->position].waitingtogodown + floors[lift->position].waitingtogoup, NFLOORS - lift->position, " "); (*waiting)--; semaphore_signal(&floors[lift->position].queueInteraction); // Wait for person to get into lift Sleep(GETINSPEED); // Need a sem here // Wait til we can be the lift that is loading semaphore_wait(&targetLiftSelect[lift->position]); targetLift[lift->position] = lift; // lock the addtostop semaphore_wait(&lift->addToStop); semaphore_signal(s); } else { semaphore_signal(&floors[lift->position].queueInteraction); break; } } } // -------------------------------------------------- // Function for the Lift Threads // -------------------------------------------------- void *lift_thread(void *p) { // Local variables lift_info lift; int no = (long long)p; int i; // Set up Lift lift.no = no; // Set lift number lift.position = 0; // Lift starts on ground floor lift.direction = UP; // Lift starts going up lift.peopleinlift = 0; // Lift starts empty // lift is ok with loading a single person semaphore_create(&lift.addToStop, 1); //semaphore_create(&lift.buttonPress, 1); for (i = 0; i < NFLOORS; i++) { lift.stops[i] = 0; // No passengers waiting semaphore_create(&lift.stopsem[i], 0); // Initialise semaphores } // Randomise lift randomise(); // Wait for random start up time (up to a second) Sleep(rnd(1000)); // Loop forever while (true) { // Print current position of the lift print_at_xy(no * 4 + 1, NFLOORS - lift.position, lf); // Wait for a while Sleep(LIFTSPEED); // Print the number of people in the lift #ifdef POPULATIONS char str[12]; sprintf(str, "%d", lift.peopleinlift); print_at_xy_fast(no * 4 + 1, NFLOORS + 5, " "); print_at_xy_fast(no * 4 + 1, NFLOORS + 5, str); #endif // Drop off passengers on this floor while (lift.stops[lift.position] != 0) { // These are sequential, so don't need protecting // One less passenger in lift lift.peopleinlift--; // One less waiting to get off at this floor lift.stops[lift.position]--; // Wait for exit lift delay Sleep(GETOUTSPEED); // Signal passenger to leave lift semaphore_signal(&lift.stopsem[lift.position]); // Check if that was the last passenger waiting for this floor if (!lift.stops[lift.position]) { // Clear the "-" print_at_xy(no * 4 + 1 + 2, NFLOORS - lift.position, " "); } } // Check if lift is going up or is empty if (lift.direction == UP || !lift.peopleinlift) { // Pick up passengers waiting to go up get_into_lift(&lift, UP); } // Check if lift is going down or is empty if (lift.direction == DOWN || !lift.peopleinlift) { // Pick up passengers waiting to go down get_into_lift(&lift, DOWN); } // Erase lift from screen print_at_xy(no * 4 + 1, NFLOORS - lift.position, (lift.direction + 1 ? " " : lc)); // Move lift // Hold the lift while someone is being added, before moving off // this really could go anywhere, but it fits the metaphor to wrap it here (doesn't need to wrap anything) semaphore_wait(&lift.addToStop); lift.position += lift.direction; semaphore_signal(&lift.addToStop); // Check if lift is at top or bottom if (lift.position == 0 || lift.position == NFLOORS - 1) { // Change lift direction lift.direction = -lift.direction; } } return NULL; } // -------------------------------------------------- // Function for the Person Threads // -------------------------------------------------- void *person_thread(void *p) { // Local variables int from = 0, to; // Start on the ground floor lift_info *lift; // Randomise randomise(); // Stay in the building forever while (true) { // Work for a while Sleep(rnd(PEOPLESPEED)); do { // Randomly pick another floor to go to to = rnd(NFLOORS); } while (to == from); semaphore *s; // Check which direction the person is going (UP/DOWN) if (to > from) { // One more person waiting to go up semaphore_wait(&floors[from].queueInteraction); floors[from].waitingtogoup++; print_at_xy(NLIFTS * 4 + floors[from].waitingtogoup + floors[from].waitingtogodown, NFLOORS - from, pr); semaphore_signal(&floors[from].queueInteraction); // Print person waiting s = &floors[from].up_arrow; // Wait for a lift to arrive (going up) } else { semaphore_wait(&floors[from].queueInteraction); // One more person waiting to go down floors[from].waitingtogodown++; print_at_xy(NLIFTS * 4 + floors[from].waitingtogoup + floors[from].waitingtogodown, NFLOORS - from, pr); semaphore_signal(&floors[from].queueInteraction); // Print person waiting // We want this to happen after the semaphore is released, so the person is added after any deletions s = &floors[from].down_arrow; // Wait for a lift to arrive (going down) } // We've pressed our button // waiting for a lift's doors to open semaphore_wait(s); // Which lift we are getting into. The targetLift has locked the variable, so once we have our reference, lift = targetLift[from]; // Realease the targetLift ASAP so another lift can load someone semaphore_signal(&targetLiftSelect[lift->position]); // GetIntoLift() continues its loop, from here (this passenger is signalled to enter; so we definitely need to hold a lock around // this value) // When GetIntoLift() decides that a passenger should enter, it obtains a lock of addToStop. // We release that lock here. Finally, once all passengers have been picked up and the lift decides to move on, if this lock is // still held, then the lift wont change floor (protecting against unloading issues) // We could do this by extending the targetLiftSelect semaphore through the same condition, but that would affect the performance of all lifts on this floor lift->stops[to]++; semaphore_signal(&lift->addToStop); // Press button if we are the first // Technically, the semaphore should extend through this conditional, but as it is only a read operation whose state is should to // be greater than 0 here, it doesn't significantly matter to overwrite the char again (the performance impact is low) if (lift->stops[to] == 1) { // Print light for destination print_at_xy(lift->no * 4 + 1 + 2, NFLOORS - to, "-"); } semaphore_wait(&lift->stopsem[to]); // Wait until we are at the right floor #ifdef POPULATIONS semaphore_wait(&updateFloorCount); floorCount[to]++; floorCount[from]--; semaphore_signal(&updateFloorCount); // Print the current person's floor char str[3]; sprintf(str, "%d", to); print_at_xy_fast((long long)p * 4 + 1, NFLOORS + 8, " "); print_at_xy_fast((long long)p * 4 + 1, NFLOORS + 8, str); #endif // Exit the lift from = to; } return NULL; } // -------------------------------------------------- // Print the building on the screen // -------------------------------------------------- void printbuilding(void) { // Local variables int l, f; // Clear Screen system(clear_screen); // Print the whole building before anything else // Print Roof printf("%s", tl); for (l = 0; l < NLIFTS - 1; l++) { printf("%s%s%s%s", hl, td, hl, td); } printf("%s%s%s%s\n", hl, td, hl, tr); // Print Floors and Lifts for (f = NFLOORS - 1; f >= 0; f--) { for (l = 0; l < NLIFTS; l++) { printf("%s%s%s ", vl, lc, vl); if (l == NLIFTS - 1) { printf("%s\n", vl); } } } // Print Ground printf("%s", bl); for (l = 0; l < NLIFTS - 1; l++) { printf("%s%s%s%s", hl, tu, hl, tu); } printf("%s%s%s%s\n", hl, tu, hl, br); // Print Message printf("Lift Simulation - Press CTRL-Break to exit\n"); #ifdef POPULATIONS gotoxy(0, NFLOORS + 7); printf("People\'s Destination Floors:"); gotoxy(0, NFLOORS + 4); printf("Lift Occupants:"); gotoxy(4 * NLIFTS + NPEOPLE + 5, 0); printf("Number of People on Floor:"); #endif // Ensure buffer is written fflush(stdout); // Once the building has been printed, then we can those waiting to print that they may do so semaphore_signal(&printSemaphore); } #ifdef POPULATIONS // -------------------------------------------------- // Iterate through the floors and update the queue size // -------------------------------------------------- void *update_floor_counts(void *p) { int i; int k; char str2[18] = {';',' ','\t', 'i', 't', 'e', 'r', 's', ' ', 's', 'i', 'n', 'c', 'e', ' ', '0', ':', ' '}; while (true) { for (i = -1; i < NFLOORS; i++) { char str[48]; char str3[12]; if (i != -1) { if (floorCount[i] != 0){ floorCountZeroInc[i]++; } else{ floorCountZeroInc[i] = 0; } sprintf(str, "%d", floorCount[i]); sprintf(str3, "%d", floorCountZeroInc[i]); strcat(str, str2); strcat(str, str3); print_at_xy_fast(4 * NLIFTS + NPEOPLE + 5, NFLOORS - i, " "); print_at_xy_fast(4 * NLIFTS + NPEOPLE + 5, NFLOORS - i, str); } else { int ttl = 0; for (k = 0; k < NFLOORS; k++) { ttl += floorCount[k]; } sprintf(str, "%d", ttl); if (ttl != NPEOPLE) { break; } print_at_xy_fast(4 * NLIFTS + NPEOPLE + 5, NFLOORS - i, str); } } } //Sleep(0); } #endif // -------------------------------------------------- // Main starts the threads and then waits. // -------------------------------------------------- int main() { // Local variables unsigned long i; semaphore_create(&printSemaphore, 0); // Initialise Building for (i = 0; i < NFLOORS; i++) { floors[i].waitingtogoup = 0; floors[i].waitingtogodown = 0; semaphore_create(&floors[i].up_arrow, 0); semaphore_create(&floors[i].down_arrow, 0); semaphore_create(&floors[i].queueInteraction, 1); // This initially has one available button press semaphore_create(&targetLiftSelect[i], 1); // This initially has one available button press #ifdef POPULATIONS if (i == 0) { semaphore_create(&updateFloorCount, 1); // Moved, to save excessive preprocessor tags floorCount[0] = NPEOPLE; floorCountZeroInc[0] = 0; } else { floorCount[i] = 0; floorCountZeroInc[i] = 0; } #endif } // --- Initialise any other semaphores --- // Print Building printbuilding(); // Create Lifts for (i = 0; i < NLIFTS; i++) { // Create Lift Thread create_thread(lift_thread, (void *)i); } // Create People for (i = 0; i < NPEOPLE; i++) { // Create Person Thread create_thread(person_thread, (void *)i); } #ifdef POPULATIONS create_thread(update_floor_counts, (void *)i); #endif // Go to sleep for 86400 seconds (one day) Sleep(86400000ULL); }