forked from steger/pr3-sose2026
365 lines
9.6 KiB
Go
365 lines
9.6 KiB
Go
package airport
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"time"
|
|
)
|
|
|
|
var distantFuture = time.Unix(1<<63-1, 0) // Max value for Unix timestamp
|
|
|
|
type GateNumber int
|
|
|
|
type Gate struct {
|
|
id GateNumber
|
|
walkingTimeFromSecurity time.Duration
|
|
taxiTimeToRunway time.Duration
|
|
flights []Flight
|
|
passengerQueues map[FlightNumber]chan passengerGateData
|
|
baggageQueues map[FlightNumber]chan baggageRequest
|
|
aircraftQueues map[FlightNumber]chan *Aircraft
|
|
departSignals map[FlightNumber]chan struct{}
|
|
}
|
|
|
|
var _ BaggageProcessor = &Gate{}
|
|
|
|
func NewGate(id GateNumber, walkingTimeFromSecurity time.Duration, taxiTimeToRunway time.Duration) Gate {
|
|
|
|
return Gate{
|
|
id: id,
|
|
walkingTimeFromSecurity: walkingTimeFromSecurity,
|
|
taxiTimeToRunway: taxiTimeToRunway,
|
|
flights: []Flight{},
|
|
passengerQueues: make(map[FlightNumber]chan passengerGateData),
|
|
baggageQueues: make(map[FlightNumber]chan baggageRequest),
|
|
aircraftQueues: make(map[FlightNumber]chan *Aircraft),
|
|
departSignals: make(map[FlightNumber]chan struct{}),
|
|
}
|
|
}
|
|
|
|
func NewGates(count int) map[GateNumber]*Gate {
|
|
gates := make(map[GateNumber]*Gate)
|
|
for i := range count {
|
|
walkingTime := time.Duration(5+rand.Intn(6)) * time.Second
|
|
taxiTime := time.Duration(5+rand.Intn(6)) * time.Second
|
|
|
|
gate := NewGate(GateNumber(i+1), walkingTime, taxiTime)
|
|
gates[GateNumber(i+1)] = &gate
|
|
}
|
|
return gates
|
|
}
|
|
|
|
func (g *Gate) SetFlights(flights []Flight) {
|
|
g.flights = flights
|
|
if g.passengerQueues == nil {
|
|
g.passengerQueues = make(map[FlightNumber]chan passengerGateData)
|
|
}
|
|
if g.baggageQueues == nil {
|
|
g.baggageQueues = make(map[FlightNumber]chan baggageRequest)
|
|
}
|
|
if g.aircraftQueues == nil {
|
|
g.aircraftQueues = make(map[FlightNumber]chan *Aircraft)
|
|
}
|
|
if g.departSignals == nil {
|
|
g.departSignals = make(map[FlightNumber]chan struct{})
|
|
}
|
|
|
|
for _, f := range flights {
|
|
if _, ok := g.passengerQueues[f.Id]; !ok {
|
|
g.passengerQueues[f.Id] = make(chan passengerGateData, 16)
|
|
}
|
|
if _, ok := g.baggageQueues[f.Id]; !ok {
|
|
g.baggageQueues[f.Id] = make(chan baggageRequest, 16)
|
|
}
|
|
if _, ok := g.aircraftQueues[f.Id]; !ok {
|
|
g.aircraftQueues[f.Id] = make(chan *Aircraft, 1)
|
|
}
|
|
if _, ok := g.departSignals[f.Id]; !ok {
|
|
g.departSignals[f.Id] = make(chan struct{}, 1)
|
|
}
|
|
}
|
|
}
|
|
|
|
type passengerGateData struct {
|
|
p Passenger
|
|
err chan error
|
|
arrival time.Time
|
|
}
|
|
|
|
type baggageRequest struct {
|
|
b Baggage
|
|
err chan error
|
|
}
|
|
|
|
// Blocks for the walking distance to the gate + the time until the passenger has boarded
|
|
func (g *Gate) Process(passenger Passenger) error {
|
|
if passenger.BoardingPass == nil {
|
|
return fmt.Errorf("no boarding pass")
|
|
}
|
|
if passenger.BoardingPass.Gate != g.id {
|
|
return fmt.Errorf("invalid gate: %v", passenger.BoardingPass.Gate)
|
|
}
|
|
|
|
fn := passenger.BoardingPass.FlightNumber
|
|
q, ok := g.passengerQueues[fn]
|
|
if !ok {
|
|
return fmt.Errorf("invalid flight %v", fn)
|
|
}
|
|
|
|
// find the flight to determine departure time
|
|
var flight Flight
|
|
found := false
|
|
for _, f := range g.flights {
|
|
if f.Id == fn {
|
|
flight = f
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return fmt.Errorf("invalid flight %v", fn)
|
|
}
|
|
|
|
errc := make(chan error)
|
|
|
|
// compute intended arrival time (start + walking), record before sleeping
|
|
intendedArrival := time.Now().Add(g.walkingTimeFromSecurity)
|
|
fmt.Printf("[gate %d] Process start for %s now=%v intendedArrival=%v departure=%v\n", g.id, passenger.Name, time.Now(), intendedArrival, flight.DepartureTime)
|
|
time.Sleep(g.walkingTimeFromSecurity)
|
|
fmt.Printf("[gate %d] after walking for %s now=%v intendedArrival=%v departure=%v\n", g.id, passenger.Name, time.Now(), intendedArrival, flight.DepartureTime)
|
|
|
|
// if already departed, return immediately
|
|
if intendedArrival.After(flight.DepartureTime) {
|
|
return fmt.Errorf("flight already departed")
|
|
}
|
|
|
|
// attempt to enqueue but don't block past departure time
|
|
data := passengerGateData{p: passenger, err: errc, arrival: intendedArrival}
|
|
// allow waiting until departure relative to the intended arrival time
|
|
sendTimeout := flight.DepartureTime.Sub(intendedArrival)
|
|
if sendTimeout < 0 {
|
|
sendTimeout = 0
|
|
}
|
|
|
|
fmt.Printf("[gate %d] enqueuing passenger %s for flight %s (sendTimeout=%v)\n", g.id, passenger.Name, fn, sendTimeout)
|
|
select {
|
|
case q <- data:
|
|
// sent
|
|
case <-time.After(sendTimeout):
|
|
return fmt.Errorf("flight already departed")
|
|
}
|
|
|
|
res := <-errc
|
|
fmt.Printf("[gate %d] passenger %s processed with err=%v\n", g.id, passenger.Name, res)
|
|
return res
|
|
}
|
|
|
|
// Blocks until departure (includes taxi time to runway)
|
|
func (g *Gate) ProcessAircraft(ac *Aircraft) {
|
|
fn := ac.Flight.Id
|
|
q, ok := g.aircraftQueues[fn]
|
|
if !ok {
|
|
// nothing to do
|
|
return
|
|
}
|
|
|
|
q <- ac
|
|
|
|
// wait until depart signal
|
|
if sig, ok := g.departSignals[fn]; ok {
|
|
<-sig
|
|
}
|
|
}
|
|
|
|
func (g *Gate) ProcessBaggage(fn FlightNumber, b Baggage) error {
|
|
q, ok := g.baggageQueues[fn]
|
|
if !ok {
|
|
return fmt.Errorf("invalid flight %v", fn)
|
|
}
|
|
// find flight for departure time
|
|
var flight Flight
|
|
found := false
|
|
for _, f := range g.flights {
|
|
if f.Id == fn {
|
|
flight = f
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return fmt.Errorf("invalid flight %v", fn)
|
|
}
|
|
|
|
errc := make(chan error)
|
|
tLeft := time.Until(flight.DepartureTime)
|
|
if tLeft <= 0 {
|
|
return fmt.Errorf("flight already departed")
|
|
}
|
|
|
|
select {
|
|
case q <- baggageRequest{b: b, err: errc}:
|
|
// sent
|
|
case <-time.After(tLeft):
|
|
return fmt.Errorf("flight already departed")
|
|
}
|
|
return <-errc
|
|
}
|
|
|
|
func (g Gate) Start() {
|
|
// start a handler for each configured flight
|
|
for _, f := range g.flights {
|
|
f := f
|
|
pCh := g.passengerQueues[f.Id]
|
|
bCh := g.baggageQueues[f.Id]
|
|
acCh := g.aircraftQueues[f.Id]
|
|
depart := g.departSignals[f.Id]
|
|
|
|
go func() {
|
|
// wait until boarding time
|
|
fmt.Printf("[gate %d] handler starting for %s: boarding=%v departure=%v now=%v\n", g.id, f.Id, f.BoardingTime, f.DepartureTime, time.Now())
|
|
if time.Until(f.BoardingTime) > 0 {
|
|
time.Sleep(time.Until(f.BoardingTime))
|
|
}
|
|
|
|
// boarding open
|
|
departureTimer := time.NewTimer(time.Until(f.DepartureTime))
|
|
|
|
waitingPassengers := []passengerGateData{}
|
|
waitingBaggage := []Baggage{}
|
|
var aircraft *Aircraft
|
|
|
|
boarding := true
|
|
for boarding {
|
|
select {
|
|
case pg := <-pCh:
|
|
fmt.Printf("[gate %d] handler received passenger %s, aircraft=%v arrival=%v departure=%v\n", g.id, pg.p.Name, aircraft != nil, pg.arrival, f.DepartureTime)
|
|
if pg.arrival.After(f.DepartureTime) {
|
|
pg.err <- fmt.Errorf("flight already departed")
|
|
continue
|
|
}
|
|
if aircraft != nil {
|
|
if err := aircraft.Process(pg.p); err != nil {
|
|
pg.err <- err
|
|
} else {
|
|
pg.err <- nil
|
|
}
|
|
} else {
|
|
waitingPassengers = append(waitingPassengers, pg)
|
|
}
|
|
case bd := <-bCh:
|
|
fmt.Printf("[gate %d] handler received baggage for %s, aircraft=%v\n", g.id, f.Id, aircraft != nil)
|
|
if aircraft != nil {
|
|
if err := aircraft.ProcessBaggage(f.Id, bd.b); err != nil {
|
|
bd.err <- err
|
|
} else {
|
|
bd.err <- nil
|
|
}
|
|
} else {
|
|
waitingBaggage = append(waitingBaggage, bd.b)
|
|
bd.err <- nil
|
|
}
|
|
case ac := <-acCh:
|
|
fmt.Printf("[gate %d] handler received aircraft %s\n", g.id, ac.Flight.Id)
|
|
aircraft = ac
|
|
// deliver waiting passengers
|
|
for _, wp := range waitingPassengers {
|
|
if time.Now().After(f.DepartureTime) {
|
|
wp.err <- fmt.Errorf("flight already departed")
|
|
continue
|
|
}
|
|
if err := aircraft.Process(wp.p); err != nil {
|
|
wp.err <- err
|
|
} else {
|
|
wp.err <- nil
|
|
}
|
|
}
|
|
waitingPassengers = nil
|
|
// deliver waiting baggage
|
|
for _, wb := range waitingBaggage {
|
|
aircraft.ProcessBaggage(f.Id, wb)
|
|
}
|
|
waitingBaggage = nil
|
|
case <-departureTimer.C:
|
|
fmt.Printf("[gate %d] handler departure timer fired for %s\n", g.id, f.Id)
|
|
boarding = false
|
|
}
|
|
}
|
|
|
|
// boarding closed: drain any buffered messages and process those
|
|
for {
|
|
processed := false
|
|
select {
|
|
case pg := <-pCh:
|
|
processed = true
|
|
if pg.arrival.After(f.DepartureTime) {
|
|
pg.err <- fmt.Errorf("flight already departed")
|
|
continue
|
|
}
|
|
if aircraft != nil {
|
|
if err := aircraft.Process(pg.p); err != nil {
|
|
pg.err <- err
|
|
} else {
|
|
pg.err <- nil
|
|
}
|
|
} else {
|
|
waitingPassengers = append(waitingPassengers, pg)
|
|
}
|
|
case bd := <-bCh:
|
|
processed = true
|
|
if aircraft != nil {
|
|
if err := aircraft.ProcessBaggage(f.Id, bd.b); err != nil {
|
|
bd.err <- err
|
|
} else {
|
|
bd.err <- nil
|
|
}
|
|
} else {
|
|
waitingBaggage = append(waitingBaggage, bd.b)
|
|
bd.err <- nil
|
|
}
|
|
case ac := <-acCh:
|
|
processed = true
|
|
aircraft = ac
|
|
// deliver waiting passengers
|
|
for _, wp := range waitingPassengers {
|
|
if wp.arrival.After(f.DepartureTime) {
|
|
wp.err <- fmt.Errorf("flight already departed")
|
|
continue
|
|
}
|
|
if err := aircraft.Process(wp.p); err != nil {
|
|
wp.err <- err
|
|
} else {
|
|
wp.err <- nil
|
|
}
|
|
}
|
|
waitingPassengers = nil
|
|
// deliver waiting baggage
|
|
for _, wb := range waitingBaggage {
|
|
aircraft.ProcessBaggage(f.Id, wb)
|
|
}
|
|
waitingBaggage = nil
|
|
default:
|
|
}
|
|
|
|
if !processed {
|
|
break
|
|
}
|
|
}
|
|
|
|
// notify remaining waiting passengers that the flight is gone
|
|
for _, wp := range waitingPassengers {
|
|
wp.err <- fmt.Errorf("flight already departed")
|
|
}
|
|
|
|
// After boarding closed, wait taxi time then signal departure
|
|
time.Sleep(g.taxiTimeToRunway)
|
|
|
|
// signal any ProcessAircraft waiting on depart
|
|
select {
|
|
case depart <- struct{}{}:
|
|
default:
|
|
}
|
|
close(depart)
|
|
}()
|
|
}
|
|
}
|