module: airport Author: Neal Feinberg, Sonya E. Keene, Robert O. Mathews, P. Tucker Withington Synopsis: The implementation of the airport example scheduling system for the airport library. Copyright: N Feinberg/S E Keene/R Mathews/P Tucker Withington, DYLAN PROGRAMMING, Copyright (c) 1997-2000 Functional Objects, Inc. Reproduced by permission of Addison-Wesley Longman Publishing Company, Inc. All rights reserved. No further copying, downloading or transmitting of this material is allowed without the prior written permission of the publisher. // The following generic functions comprise the essential protocol for // interaction between containers and vehicles. // Returns true if container is available for aircraft in direction. define generic available? (vehicle, container, direction) => (available? :: ) ; // Moves vehicle into container in the given direction. define generic move-in-vehicle (vehicle, container, direction); // Moves vehicle out of container in the given direction. define generic move-out-vehicle (vehicle, container, direction); // Returns the aircraft next in line to move out of container in direction. define generic next-out (container, direction); // Returns the next class of container to move vehicle into, and how long // it will take to get there. define generic next-landing-step (container, vehicle); // A single storage container is available if the aircraft fits into the // the container, and there isn't already a vehicle in the container. define method available? (vehicle :: , container :: , direction :: ) => (container-available? :: ) object-fits?(vehicle, container) & ~ (container.vehicle-currently-occupying); end method available?; // A multiple storage container is available if the aircraft fits into the // the container, and there are not too many aircraft already queued in the // container for the specified direction. define method available? (vehicle :: , container :: , direction :: ) => (container-available? :: ) object-fits?(vehicle, container) & size(container.vehicles-by-direction[direction]) < container.maxima-by-direction[direction]; end method available?; // Avoids jamming the runway with inbound traffic which would prevent // outbound aircraft from taking off. The runway is only clear to inbound // traffic if there is space in the next container inbound from the runway. define method available? (vehicle :: , container :: , direction :: ) => (container-available? :: ) next-method() & select (direction) #"outbound" => #t; #"inbound" => let (class) = next-landing-step(container, vehicle); if (class) find-available-connection(container, class, vehicle) & #t; end if; end select; end method available?; // A slot is used to keep track of which aircraft is in a single // storage container. define method move-in-vehicle (vehicle :: , container :: , direction :: ) => () container.vehicle-currently-occupying := vehicle; values(); end method move-in-vehicle; // A deque is used to keep track of which aircraft are traveling in a // particular direction in a multiple storage container. define method move-in-vehicle (vehicle :: , container :: , direction :: ) => () let vehicles = container.vehicles-by-direction[direction]; push-last(vehicles, vehicle); values(); end method move-in-vehicle; // When an aircraft reaches the gate, it then begins its outbound journey. define method move-in-vehicle (vehicle :: , container :: , direction :: ) => () next-method(); vehicle.direction := #"outbound"; values(); end method move-in-vehicle; define method move-out-vehicle (vehicle :: , container :: , direction :: ) => () container.vehicle-currently-occupying := #f; values(); end method move-out-vehicle; define method move-out-vehicle (vehicle :: , container :: , direction :: ) => () let vehicles = container.vehicles-by-direction[direction]; // Assumes aircraft always exit container in order, and this aircraft // is next. pop(vehicles); values(); end method move-out-vehicle; // Determines what vehicle, if any, could move to the next container. If // there is such a vehicle, then this method returns the vehicle, // the next container in the direction of travel, and the time it // would take to make that transition. define method next-out (container :: , direction :: ) => (next-vehicle :: false-or(), next-storage :: false-or(), time-to-execute :: false-or()); let next-vehicle = next-out-internal(container, direction); if (next-vehicle) let (class, time) = next-landing-step(container, next-vehicle); if (class) let next-container = find-available-connection(container, class, next-vehicle); if (next-container) values(next-vehicle, next-container, time); end if; end if; end if; end method next-out; // This is just a helper method for the next-out method. We need different // methods based on the class of container. define method next-out-internal (container :: , desired-direction :: ) => (vehicle :: false-or()) let vehicle = container.vehicle-currently-occupying; if (vehicle & vehicle.direction == desired-direction) vehicle; end; end method next-out-internal; define method next-out-internal (container :: , desired-direction :: ) => (vehicle :: false-or()) let vehicle-queue = container.vehicles-by-direction[desired-direction]; if (vehicle-queue.size > 0) vehicle-queue[0]; end; end method next-out-internal; // These methods return the next class of container a vehicle may // move to from a particular container. They also return an estimate // of how long that transition will take. define method next-landing-step (storage :: , aircraft :: ) => (next-class :: false-or(), duration :: false-or()) if (aircraft.direction == #"inbound") values(, flying-time(aircraft, storage.airport-below)); end if; end method next-landing-step; define method next-landing-step (storage :: , aircraft :: ) => (next-class :: , duration :: ) select (aircraft.direction) #"inbound" => values(, brake-time(aircraft, storage)); #"outbound" => values(, takeoff-time(aircraft, storage)); end select; end method next-landing-step; define method next-landing-step (storage :: , aircraft :: ) => (next-class :: , duration :: ) select (aircraft.direction) #"inbound" => values(, gate-time(aircraft, storage)); #"outbound" => values(, runway-time(aircraft, storage)); end select; end method next-landing-step; define method next-landing-step (storage :: , aircraft :: ) => (next-class :: , duration :: ) values(, gate-turnaround(aircraft, storage)); end method next-landing-step; // Searches all of the vehicle storage of class class-of-next which is // connected to container and which has some room for aircraft. define method find-available-connection (storage :: , class-of-next :: , aircraft :: ) => (next-container :: false-or ()) block (return) for (c in storage.connected-to) if (instance?(c, class-of-next) & available?(aircraft, c, aircraft.direction)) return(c); end if; end for; end block; end method find-available-connection; // Generate new transitions to be considered for the next move. // The transitions will be placed in the sorted sequence, which will order // them by earliest arrival time. define method generate-new-transitions (container :: , active-transitions :: , containers-visited :: ) => () unless(element(containers-visited, container, default: #f)) // Keep track of which containers we have searched for new possible // transitions. We avoid looping forever by checking each container just // once. containers-visited[container] := #t; local method consider-transition (direction) // See if any vehicle is ready to transition out of a container. let (vehicle, next-container, time) = next-out(container, direction); unless (vehicle == #f | vehicle.next-transition.pending?) // If there is a vehicle ready, and it isn't already in the // sorted sequence of pending transitions, then prepare the // transition instance associated with the vehicle. let transition = vehicle.next-transition; transition.from-container := container; transition.to-container := next-container; // The vehicle may have been waiting. Take this into account when // computing the earliest arrival into the next container. transition.earliest-arrival := transition.earliest-arrival + time; // Flag the vehicle as pending to save searching through the // active-transitions sorted sequence later. transition.pending? := #t; // Add the transition to the set to be considered. add!(active-transitions, transition); end unless; end method consider-transition; // Consider both inbound and outbound traffic. consider-transition(#"outbound"); consider-transition(#"inbound"); // Make sure every container connected to this one is checked. for (c in container.connected-to) generate-new-transitions(c, active-transitions, containers-visited); end for; end unless; end method generate-new-transitions; // Main loop of the program. See what possible transitions exist, then // execute the earliest transitions which may be completed. Returns the time // of the last transition. define method process-aircraft (airport :: , #key time = $midnight) => (time :: ) format-out("Detailed aircraft schedule for "); say(airport); format-out("\n\n"); let sky = airport.sky-above; let containers-visited = make(); let active-transitions = make(, value-function: earliest-arrival); // We don't have to always name the exit procedure return. block (done) while (#t) // Each time through, start by considering every container. fill!(containers-visited, #f); // For every container, see if any vehicles are ready to transition. // If so, add transition instances to the active-transitions // sorted sequence. generate-new-transitions(sky, active-transitions, containers-visited); // If there are no more transitions, we have completed our task. if (empty?(active-transitions)) done(); end; // Find the earliest transition which can complete because there is // still room available in the destination container. let transition-index = find-key(active-transitions, method (transition) available?(transition.transition-aircraft, transition.to-container, transition.transition-aircraft.direction); end); // If none can complete, there is a problem with the simulation. This // should never happen, but is useful for debugging incorrect // container configurations. if (transition-index == #f) error("Pending transitions but none can complete."); end if; // Otherwise, the earliest transition that can complete has been // found. Execute the transition. let transition = active-transitions[transition-index]; let vehicle = transition.transition-aircraft; let vehicle-direction = vehicle.direction; move-out-vehicle(vehicle, transition.from-container, vehicle-direction); move-in-vehicle(vehicle, transition.to-container, vehicle-direction); // This transition is complete. Remove it from consideration. transition.pending? := #f; remove!(active-transitions, transition); // Compute the actual time of arrival at the next container, and / // display the message. time := (transition.earliest-arrival := max(time, transition.earliest-arrival)); say(transition); format-out("\n"); end while; end block; time; end method process-aircraft;