steps

package module
v1.0.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 24, 2026 License: MIT Imports: 3 Imported by: 0

README

steps

GoDoc

"Steps" is a simple discrete event simulator in Go. It's useful for simulations of systems that are driven by events, such as queues, workflows, etc.

See the documentation for API and examples.

Example

sim := NewSimulation()

sim.Schedule(Event{When: sim.Now.Add(10 * time.Second), Action: func(s *Simulation) {
  fmt.Println("Actor 1:", sim.Now)
}})
sim.Schedule(Event{When: sim.Now.Add(time.Second), Action: func(s *Simulation) {
  fmt.Println("Actor 2:", sim.Now)
}})

sim.RunUntilDone()

// Output:
// Actor 2: 0001-01-01 00:00:01 +0000 UTC
// Actor 1: 0001-01-01 00:00:10 +0000 UTC

See here for more examples.

Why write yet another discrete event simulator?

  • simgo was too slow for my needs. I needed to run simulations in an inner loop.
  • godes was too heavyweight and complex for my needs. I just needed a simple performant scheduler (without any goroutines).
  • SimPy was in Python and I wanted something in Go. Love this library, though!

Contact & support

This library was coded up by Jens Rantil. Do not hesitate to contact Sweet Potato Tech for support.

Documentation

Overview

Package steps provides a simple, fast, discrete event simulator in Go.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Ticker

func Ticker(sim *Simulation, start time.Time, duration time.Duration, f Action)

Ticker schedules an event to run at a regular interval. For more complex cronjobs etc., have a look at something like 1.

Example

ExampleTicker shows how to use the Ticker helper to schedule events at regular intervals.

sim := NewSimulation()

Ticker(sim, sim.Now, 3*time.Second, func(s *Simulation) {
	fmt.Println("Actor:", sim.Now)
})

whenToStop := sim.Now.Add(15 * time.Second)
sim.RunUntil(whenToStop)
Output:

Actor: 0001-01-01 00:00:00 +0000 UTC
Actor: 0001-01-01 00:00:03 +0000 UTC
Actor: 0001-01-01 00:00:06 +0000 UTC
Actor: 0001-01-01 00:00:09 +0000 UTC
Actor: 0001-01-01 00:00:12 +0000 UTC
Actor: 0001-01-01 00:00:15 +0000 UTC

Types

type Action

type Action func(*Simulation)

type BinarySemaphore

type BinarySemaphore struct {
	// contains filtered or unexported fields
}

BinarySemaphore is a semaphore that can be used to synchronize actions. It is a counting semaphore with a maximum of 1. Since a simulation can only run one action at a time, this library does not implement any mutex[1]

[1] https://en.wikipedia.org/wiki/Lock_(computer_science)#Mutexes_vs._semaphores

Example

ExampleBinarySemaphore demonstrates how to use the BinarySemaphore to synchronize actions. It simulates processing ten (10) items, one (1) at a time.

sim := NewSimulation()
sem := NewBinarySemaphore(sim)

// Simulate processing ten (10) items, one (1) at a time.
timeToProcess := 10 * time.Second
for i := range 10 {
	sem.Acquire(func(sim *Simulation) {
		// We have now acquired the semaphore and can start processing.
		fmt.Println(sim.Now, "Processing item", i)

		sim.Schedule(Event{When: sim.Now.Add(timeToProcess), Action: func(sim *Simulation) {
			fmt.Println(sim.Now, "Done processing item", i)
			sem.Release()
		}})
	})
}
sim.RunUntilDone()
Output:

0001-01-01 00:00:00 +0000 UTC Processing item 0
0001-01-01 00:00:10 +0000 UTC Done processing item 0
0001-01-01 00:00:10 +0000 UTC Processing item 1
0001-01-01 00:00:20 +0000 UTC Done processing item 1
0001-01-01 00:00:20 +0000 UTC Processing item 2
0001-01-01 00:00:30 +0000 UTC Done processing item 2
0001-01-01 00:00:30 +0000 UTC Processing item 3
0001-01-01 00:00:40 +0000 UTC Done processing item 3
0001-01-01 00:00:40 +0000 UTC Processing item 4
0001-01-01 00:00:50 +0000 UTC Done processing item 4
0001-01-01 00:00:50 +0000 UTC Processing item 5
0001-01-01 00:01:00 +0000 UTC Done processing item 5
0001-01-01 00:01:00 +0000 UTC Processing item 6
0001-01-01 00:01:10 +0000 UTC Done processing item 6
0001-01-01 00:01:10 +0000 UTC Processing item 7
0001-01-01 00:01:20 +0000 UTC Done processing item 7
0001-01-01 00:01:20 +0000 UTC Processing item 8
0001-01-01 00:01:30 +0000 UTC Done processing item 8
0001-01-01 00:01:30 +0000 UTC Processing item 9
0001-01-01 00:01:40 +0000 UTC Done processing item 9

func NewBinarySemaphore

func NewBinarySemaphore(sim *Simulation) *BinarySemaphore

NewBinarySemaphore creates a new binary semaphore.

func (*BinarySemaphore) Acquire

func (s *BinarySemaphore) Acquire(a Action)

Acquire acquires the semaphore. If the semaphore is already acquired, the action will be scheduled to run when the semaphore is released. Do not forget to call Release() when the action is done (unless you want to hold the semaphore for longer).

func (*BinarySemaphore) Release

func (s *BinarySemaphore) Release()

Release releases the semaphore.

type Condition

type Condition struct {
	// contains filtered or unexported fields
}

Condition is a condition that can be used to synchronize actions. Multiple actions can be waiting for the same condition, and be triggered by a Signal or Broadcast, similarly to sync.Cond.

Example

ExampleCondition demonstrates how to use the Condition to synchronize actions. It simulates processing ten (10) items, rate-limited to one item per second.

sim := NewSimulation()
c := NewCondition(sim)

itemsToProcess := 10
for range itemsToProcess {
	c.Wait(func(sim *Simulation) {
		fmt.Println(sim.Now, "Processing...")
	})
}
Ticker(sim, sim.Now, time.Second, func(sim *Simulation) {
	c.Signal()
})

// Deliberately not using sim.RunUntilDone() here since the Ticker will run indefinitely.
sim.RunUntil(sim.Now.Add(time.Duration(2*itemsToProcess) * time.Second))
Output:

0001-01-01 00:00:00 +0000 UTC Processing...
0001-01-01 00:00:01 +0000 UTC Processing...
0001-01-01 00:00:02 +0000 UTC Processing...
0001-01-01 00:00:03 +0000 UTC Processing...
0001-01-01 00:00:04 +0000 UTC Processing...
0001-01-01 00:00:05 +0000 UTC Processing...
0001-01-01 00:00:06 +0000 UTC Processing...
0001-01-01 00:00:07 +0000 UTC Processing...
0001-01-01 00:00:08 +0000 UTC Processing...
0001-01-01 00:00:09 +0000 UTC Processing...

func NewCondition

func NewCondition(sim *Simulation) *Condition

NewCondition creates a new condition.

func (*Condition) Broadcast

func (c *Condition) Broadcast()

Broadcast wakes up all actions waiting for this condition. Actions are woken up in the order they were waiting.

func (*Condition) Cancel

func (c *Condition) Cancel(id ConditionActionID) bool

Cancel cancels an action waiting for this condition. Returns true if the action was found and removed, false otherwise (e.g. it was already executed, never existed, or was previously cancelled).

func (*Condition) Signal

func (c *Condition) Signal()

Signal wakes up one action waiting for this condition. Actions are woken up in the order they were waiting.

func (*Condition) Wait

func (c *Condition) Wait(a Action) ConditionActionID

type ConditionActionID

type ConditionActionID int

type CountingSemaphore

type CountingSemaphore struct {
	// contains filtered or unexported fields
}

CountingSemaphore is a semaphore that can be used to synchronize actions and limit the number of concurrent actions (in the simulation sense). It is a counting semaphore with a maximum of count.

Example

ExampleCountingSemaphore demonstrates how to use the CountingSemaphore to synchronize actions. It simulates processing ten (10) items, three (3) at a time.

sim := NewSimulation()
sem := NewCountingSemaphore(sim, 3)

// Simulate processing 10 items, 3 at a time.
timeToProcess := 10 * time.Second
itemsToProcess := 10
for i := range itemsToProcess {
	sem.Acquire(func(sim *Simulation) {
		// We have now acquired the semaphore and can start processing.
		fmt.Println(sim.Now, "Processing item", i)

		sim.Schedule(Event{When: sim.Now.Add(timeToProcess), Action: func(sim *Simulation) {
			fmt.Println(sim.Now, "Done processing item", i)
			sem.Release()
		}})
	})
}
sim.RunUntilDone()
Output:

0001-01-01 00:00:00 +0000 UTC Processing item 0
0001-01-01 00:00:00 +0000 UTC Processing item 1
0001-01-01 00:00:00 +0000 UTC Processing item 2
0001-01-01 00:00:10 +0000 UTC Done processing item 0
0001-01-01 00:00:10 +0000 UTC Processing item 3
0001-01-01 00:00:10 +0000 UTC Done processing item 1
0001-01-01 00:00:10 +0000 UTC Processing item 4
0001-01-01 00:00:10 +0000 UTC Done processing item 2
0001-01-01 00:00:10 +0000 UTC Processing item 5
0001-01-01 00:00:20 +0000 UTC Done processing item 3
0001-01-01 00:00:20 +0000 UTC Processing item 6
0001-01-01 00:00:20 +0000 UTC Done processing item 4
0001-01-01 00:00:20 +0000 UTC Processing item 7
0001-01-01 00:00:20 +0000 UTC Done processing item 5
0001-01-01 00:00:20 +0000 UTC Processing item 8
0001-01-01 00:00:30 +0000 UTC Done processing item 6
0001-01-01 00:00:30 +0000 UTC Processing item 9
0001-01-01 00:00:30 +0000 UTC Done processing item 7
0001-01-01 00:00:30 +0000 UTC Done processing item 8
0001-01-01 00:00:40 +0000 UTC Done processing item 9

func NewCountingSemaphore

func NewCountingSemaphore(sim *Simulation, count int) *CountingSemaphore

NewCountingSemaphore creates a new counting semaphore.

func (*CountingSemaphore) Acquire

func (s *CountingSemaphore) Acquire(a Action)

Acquire acquires the semaphore. If the semaphore is already acquired, the action will be scheduled to run when the semaphore is released. Do not forget to call Release() when the action is done (unless you want to hold the semaphore for longer).

func (*CountingSemaphore) Release

func (s *CountingSemaphore) Release()

Release releases the semaphore.

type Event

type Event struct {
	// When is the time at which the event should be processed as measured from the start of the simulation.
	When time.Time

	// Action is the function to call when the event is to be processed.
	Action Action
}

Event represents an event that will be processed as soon as possible after a specific time.

type EventID

type EventID int

EventID is the ID of a scheduled event. It is mostly used if you need to cancel a scheduled event before it is executed.

type Simulation

type Simulation struct {
	// Now represents the current point in time in the simulation. It is not recommended to modify this value during a simulation.
	Now time.Time
	// contains filtered or unexported fields
}

Simulation runs a discrete event simulation.

Example

ExampleSimulation shows how to use the Simulation type to schedule a single event and run a simulation until a given time.

sim := NewSimulation()

sim.Schedule(Event{When: sim.Now.Add(10 * time.Second), Action: func(s *Simulation) {
	fmt.Println("Actor 1:", sim.Now)
}})
sim.Schedule(Event{When: sim.Now.Add(time.Second), Action: func(s *Simulation) {
	fmt.Println("Actor 2:", sim.Now)
}})

sim.RunUntilDone()
Output:

Actor 2: 0001-01-01 00:00:01 +0000 UTC
Actor 1: 0001-01-01 00:00:10 +0000 UTC

func NewSimulation

func NewSimulation() *Simulation

NewSimulation creates a new simulation.

func (*Simulation) Cancel

func (s *Simulation) Cancel(id EventID) bool

Cancel cancels an event scheduled to the simulation. Returns true if the event was found and cancelled, false if the event was not found (never scheduled, or it was already executed).

func (*Simulation) RunUntil

func (s *Simulation) RunUntil(until time.Time)

RunUntil runs the simulation until the given time or there are no more events to process.

func (*Simulation) RunUntilDone

func (s *Simulation) RunUntilDone()

RunUntilDone runs the simulation until there are no more events to process.

func (*Simulation) Schedule

func (s *Simulation) Schedule(e Event) EventID

Schedule schedules an event to be executed at the given time by the simulator. It returns the ID of the event, which can be used to cancel the event before it is executed.

func (*Simulation) Step

func (s *Simulation) Step() bool

Step advances the simulation by one event. It returns true if the simulation advanced, false if there were no events to process.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL