Login light
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/

// See page 234.

// Package cake provides a simulation of
// a concurrent cake shop with numerous parameters.
//
// Use this command to run the benchmarks:
//     $ go test -bench=. gopl.io/ch8/cake
package cake

import (
    "fmt"
    "math/rand"
    "time"
)

type Shop struct {
    Verbose        bool
    Cakes          int           // number of cakes to bake
    BakeTime       time.Duration // time to bake one cake
    BakeStdDev     time.Duration // standard deviation of baking time
    BakeBuf        int           // buffer slots between baking and icing
    NumIcers       int           // number of cooks doing icing
    IceTime        time.Duration // time to ice one cake
    IceStdDev      time.Duration // standard deviation of icing time
    IceBuf         int           // buffer slots between icing and inscribing
    InscribeTime   time.Duration // time to inscribe one cake
    InscribeStdDev time.Duration // standard deviation of inscribing time
}

type cake int

func (s *Shop) baker(baked chan<- cake) {
    for i := 0; i < s.Cakes; i++ {
        c := cake(i)
        if s.Verbose {
            fmt.Println("baking", c)
        }
        work(s.BakeTime, s.BakeStdDev)
        baked <- c
    }
    close(baked)
}

func (s *Shop) icer(iced chan<- cake, baked <-chan cake) {
    for c := range baked {
        if s.Verbose {
            fmt.Println("icing", c)
        }
        work(s.IceTime, s.IceStdDev)
        iced <- c
    }
}

func (s *Shop) inscriber(iced <-chan cake) {
    for i := 0; i < s.Cakes; i++ {
        c := <-iced
        if s.Verbose {
            fmt.Println("inscribing", c)
        }
        work(s.InscribeTime, s.InscribeStdDev)
        if s.Verbose {
            fmt.Println("finished", c)
        }
    }
}

// Work runs the simulation 'runs' times.
func (s *Shop) Work(runs int) {
    for run := 0; run < runs; run++ {
        baked := make(chan cake, s.BakeBuf)
        iced := make(chan cake, s.IceBuf)
        go s.baker(baked)
        for i := 0; i < s.NumIcers; i++ {
            go s.icer(iced, baked)
        }
        s.inscriber(iced)
    }
}

// work blocks the calling goroutine for a period of time
// that is normally distributed around d
// with a standard deviation of stddev.
func work(d, stddev time.Duration) {
    delay := d + time.Duration(rand.NormFloat64()*float64(stddev))
    time.Sleep(delay)
}