|
package main |
|
|
|
import ( |
|
"fmt" |
|
"math/rand" |
|
"time" |
|
) |
|
|
|
const ( |
|
simulationDurationSeconds = 10 |
|
autoCapacity = 12 |
|
autoDispatchMaxMilliseconds = 10 |
|
maxPassengersWaiting = 5000 |
|
passengerMaxWaitDuration = time.Duration(50) * time.Millisecond |
|
numPassengers = 5000 |
|
) |
|
|
|
type bufferItem struct { |
|
val *int |
|
done chan<- bool |
|
} |
|
|
|
type bufferBatchItem []bufferItem |
|
|
|
type sendResult string |
|
|
|
// makePassenger makes a passenger at some random point |
|
// during the simulation. |
|
func makePassenger(val int, buffer chan<- bufferItem, result chan<- sendResult) { |
|
time.Sleep(time.Duration(rand.Intn(simulationDurationSeconds)) * time.Second) |
|
// Adding a buffer size of 1 makes the send non-blocking. |
|
done := make(chan bool, 1) |
|
buffer <- bufferItem{&val, done} |
|
result <- "sent" |
|
// Block until done; only exit on confirmation of buffering. |
|
<-done |
|
result <- "done" |
|
} |
|
|
|
// gatherPassengers waits for 10 passengers or a timeout |
|
// and sends whoever it has at that point. |
|
func gatherPassengers(buffer <-chan bufferItem) { |
|
passengers := make([]bufferItem, autoCapacity) |
|
var count uint |
|
for i := 1; ; i++ { |
|
// Wait for the first passenger. |
|
count = 0 |
|
item := <-buffer |
|
//fmt.Printf("Received first passenger: %d\n", p.val) |
|
passengers[count] = item |
|
count++ |
|
|
|
// Start the timer. |
|
fmt.Printf("Will wait for 50ms before leaving...\n") |
|
timeout := time.After(passengerMaxWaitDuration) |
|
|
|
// Collect messages until batch complete, or timeout. |
|
waitForPassengers: |
|
for ; count < autoCapacity; count++ { |
|
select { |
|
case <-timeout: |
|
fmt.Printf("Time to leave!\n") |
|
break waitForPassengers |
|
case item = <-buffer: |
|
//fmt.Printf("Received another passenger: %d\n", p.val) |
|
passengers[count] = item |
|
} |
|
} |
|
|
|
arrived := make([]bufferItem, count) |
|
copy(arrived, passengers[:count]) |
|
go dispatchAuto(i, arrived) |
|
} |
|
} |
|
|
|
// dispatchAuto will put a batch of passengers into an auto and leave. |
|
func dispatchAuto(autoId int, passengers bufferBatchItem) { |
|
// Leave. |
|
fmt.Printf("Honk Honk! Auto %d leaving with %d passengers.\n", autoId, len(passengers)) |
|
time.Sleep(time.Duration(rand.Intn(autoDispatchMaxMilliseconds)) * time.Millisecond) |
|
|
|
// Notify. |
|
for _, p := range passengers { |
|
p.done <- true |
|
} |
|
} |
|
|
|
// main runs the shareAuto simulation. |
|
// |
|
// The shareAuto concept is simple - the shareAuto (tuk-tuk) can hold |
|
// up to 10 passengers (seated on park benches - very safe!) and drivers |
|
// really want to do trips with as many passengers as possible. |
|
// However, it's not comfortable waiting inside the auto (it's ok if |
|
// it's moving though!) so passengers will leave if they done for too |
|
// long. |
|
// |
|
// Our well-natured dispatcher promises that no passenger will ever done |
|
// more than a set amount of time; at that point, the auto will leave |
|
// regardless of how full it is. This, our dispatcher assures, is the way |
|
// to the optimal blend of ₹₹₹ as well as Net Promoter Score (NPS). |
|
func main() { |
|
fmt.Printf("Starting %d sec shareAuto simulation for %d passengers...\n", simulationDurationSeconds, numPassengers) |
|
rand.Seed(time.Now().Unix()) |
|
|
|
buffer := make(chan bufferItem, maxPassengersWaiting) |
|
go gatherPassengers(buffer) |
|
results := make(chan sendResult, numPassengers) |
|
for i := 0; i < numPassengers; i++ { |
|
go makePassenger(i, buffer, results) |
|
} |
|
|
|
var sentCount, doneCount uint = 0, 0 |
|
exitTimer := time.After(time.Duration(simulationDurationSeconds+1) * time.Second) |
|
countUntilExit: |
|
for { |
|
select { |
|
case r := <-results: |
|
if r == "sent" { |
|
sentCount++ |
|
} else if r == "done" { |
|
doneCount++ |
|
} |
|
case <-exitTimer: |
|
fmt.Println("Time's up!") |
|
break countUntilExit |
|
} |
|
} |
|
|
|
fmt.Printf("Dispatched %d/%d passengers\n", doneCount, sentCount) |
|
fmt.Println("Bye bye!") |
|
} |