/
allocator.go
113 lines (100 loc) · 2.73 KB
/
allocator.go
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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package particle
import (
"github.com/oakmound/oak/v3/event"
)
const (
blockSize = 2048
)
// An Allocator can allocate ids for particles
type Allocator struct {
particleBlocks map[int]event.CID
nextOpenCh chan int
freeCh chan int
allocCh chan event.CID
requestCh chan int
responseCh chan event.CID
stopCh chan struct{}
}
// NewAllocator creates a new allocator
func NewAllocator() *Allocator {
return &Allocator{
particleBlocks: make(map[int]event.CID),
nextOpenCh: make(chan int),
freeCh: make(chan int),
allocCh: make(chan event.CID),
requestCh: make(chan int),
responseCh: make(chan event.CID),
stopCh: make(chan struct{}),
}
}
// Run spins up an allocator to accept allocation requests. It will run until
// Stop is called. This is a blocking call.
func (a *Allocator) Run() {
lastOpen := 0
for {
if _, ok := a.particleBlocks[lastOpen]; !ok {
select {
case <-a.stopCh:
return
case pID := <-a.requestCh:
a.responseCh <- a.particleBlocks[pID/blockSize]
lastOpen--
case i := <-a.freeCh:
opened := a.freereceive(i)
if opened < lastOpen {
lastOpen = opened
}
case a.nextOpenCh <- lastOpen:
a.particleBlocks[lastOpen] = <-a.allocCh
}
}
select {
case <-a.stopCh:
return
case i := <-a.freeCh:
opened := a.freereceive(i)
if opened < lastOpen {
lastOpen = opened
}
default:
}
lastOpen++
}
}
// DefaultAllocator is an allocator that starts running as soon as this package is imported.
var DefaultAllocator = NewAllocator()
// This is an always-called init instead of Init because oak does not import this
// package by default. If this package is not used, it will not run this goroutine.
func init() {
go DefaultAllocator.Run()
}
func (a *Allocator) freereceive(i int) int {
delete(a.particleBlocks, i)
return i - 1
}
// Allocate requests a new block in the particle space for the given cid
func (a *Allocator) Allocate(id event.CID) int {
nextOpen := <-a.nextOpenCh
a.allocCh <- id
return nextOpen
}
// Deallocate requests that the given block be removed from the particle space
func (a *Allocator) Deallocate(block int) {
a.freeCh <- block
}
// LookupSource requests the source that generated a pid
func (a *Allocator) LookupSource(id int) *Source {
a.requestCh <- id
owner := <-a.responseCh
return event.GetEntity(owner).(*Source)
}
// Lookup requests a specific particle in the particle space
func (a *Allocator) Lookup(id int) Particle {
source := a.LookupSource(id)
return source.particles[id%blockSize]
}
// Stop stops the allocator's ongoing Run. Once stopped, allocator may not be reused.
// Stop must not be called more than once.
func (a *Allocator) Stop() {
close(a.stopCh)
}