aboutsummaryrefslogtreecommitdiff
path: root/cardio/cardio.go
blob: c54dd348510cba0fa50acb32033fce06ccf3751d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// Package cardio enables Go programs to speed up and slow down based on demand.
package cardio

import (
	"context"
	"sync"
	"time"
)

// Heartbeat is a function that creates a "heartbeat" channel that you can influence to go faster
// or slower. This is intended to model the behavior of the human heart's ability to slow down and
// speed up based on physical activity.
//
// The basic usage is something like this:
//
//	heartbeat, slower, faster := cardio.Heartbeat(ctx, time.Minute, time.Millisecond)
//
// The min and max arguments control the minimum and maximum heart rate. This returns three things:
//
// - The heartbeat channel that your event loop will poll on
// - A function to influence the heartbeat to slow down (beacuse there isn't work to do)
// - A function to influence the heartbeat to speed up (because there is work to do)
//
// Your event loop should look something like this:
//
//	for range heartbeat {
//	    // do something
//	    if noWork {
//	        slower()
//	    } else {
//	        faster()
//	    }
//	}
//
// This will let you have a dynamically adjusting heartbeat for when your sick, twisted desires
// demand it.
func Heartbeat(ctx context.Context, min, max time.Duration) (<-chan struct{}, func(), func()) {
	heartbeat := make(chan struct{}, 1) // output channel
	currDelay := max                    // start at max speed
	var currDelayLock sync.Mutex

	slower := func() {
		currDelayLock.Lock()
		currDelay = currDelay / 2
		if currDelay < min {
			currDelay = min
		}
		currDelayLock.Unlock()
	}

	faster := func() {
		currDelayLock.Lock()
		currDelay = currDelay * 2
		if currDelay > max {
			currDelay = max
		}
		currDelayLock.Unlock()
	}

	go func() {
		for {
			select {
			case <-ctx.Done():
				close(heartbeat)
				return
			default:
				currDelayLock.Lock()
				toSleep := currDelay
				currDelayLock.Unlock()
				time.Sleep(toSleep)

				select {
				case heartbeat <- struct{}{}:
				default:
					slower() // back off if the channel is full
				}
			}
		}
	}()

	return heartbeat, slower, faster
}