-
-
Notifications
You must be signed in to change notification settings - Fork 15
/
view.go
208 lines (176 loc) · 5.16 KB
/
view.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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
// Copyright (c) Roman Atachiants and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
package tile
import (
"sync"
)
// Update represents a tile update notification.
type Update struct {
Point // The tile location
Tile // The tile data
}
// View represents a view which can monitor a collection of tiles.
type View struct {
Grid *Grid // The associated map
Inbox chan Update // The update inbox for the view
rect Rect // The view box
}
// Resize resizes the viewport.
func (v *View) Resize(box Rect, fn Iterator) {
owner := v.Grid // The parent map
prev := v.rect // Previous bounding box
v.rect = box // New bounding box
// Unsubscribe from the pages which are not required anymore
if prev.Min.X >= 0 || prev.Min.Y >= 0 || prev.Max.X >= 0 || prev.Max.Y >= 0 {
owner.pagesWithin(prev.Min, prev.Max, func(page *page) {
if bounds := page.Bounds(); !bounds.Intersects(box) {
if owner.observers.Unsubscribe(page.point, v) {
page.SetObserved(false) // Mark the page as not being observed
}
}
})
}
// Subscribe to every page which we have not previously subscribed
owner.pagesWithin(box.Min, box.Max, func(page *page) {
if bounds := page.Bounds(); !bounds.Intersects(prev) {
if owner.observers.Subscribe(page.point, v) {
page.SetObserved(true) // Mark the page as being observed
}
}
// Callback for each new tile in the view
if fn != nil {
page.Each(func(p Point, tile Tile) {
if !prev.Contains(p) && box.Contains(p) {
fn(p, tile)
}
})
}
})
}
// MoveBy moves the viewport towards a particular direction.
func (v *View) MoveBy(x, y int16, fn Iterator) {
v.Resize(Rect{
Min: v.rect.Min.Add(At(x, y)),
Max: v.rect.Max.Add(At(x, y)),
}, fn)
}
// MoveAt moves the viewport to a specific coordinate.
func (v *View) MoveAt(nw Point, fn Iterator) {
size := v.rect.Max.Subtract(v.rect.Min)
v.Resize(Rect{
Min: nw,
Max: nw.Add(size),
}, fn)
}
// Each iterates over all of the tiles in the view.
func (v *View) Each(fn Iterator) {
v.Grid.Within(v.rect.Min, v.rect.Max, fn)
}
// At returns the tile at a specified position.
func (v *View) At(x, y int16) (Tile, bool) {
return v.Grid.At(x, y)
}
// WriteAt updates the entire tile at a specific coordinate.
func (v *View) WriteAt(x, y int16, tile Tile) {
v.Grid.WriteAt(x, y, tile)
}
// MergeAt updates the bits of tile at a specific coordinate. The bits are specified
// by the mask. The bits that need to be updated should be flipped on in the mask.
func (v *View) MergeAt(x, y int16, tile, mask Tile) {
v.Grid.MergeAt(x, y, tile, mask)
}
// Close closes the view and unsubscribes from everything.
func (v *View) Close() error {
v.Grid.pagesWithin(v.rect.Min, v.rect.Max, func(page *page) {
if v.Grid.observers.Unsubscribe(page.point, v) {
page.SetObserved(false) // Mark the page as not being observed
}
})
return nil
}
// onUpdate occurs when a tile has updated.
func (v *View) onUpdate(ev *Update) {
if v.rect.Contains(ev.Point) {
v.Inbox <- *ev // (copy)
}
}
// -----------------------------------------------------------------------------
// observer represents a tile update observer.
type observer interface {
onUpdate(*Update)
}
// Pubsub represents a publish/subscribe layer for observers.
type pubsub struct {
m sync.Map
}
// Notify notifies listeners of an update that happened.
func (p *pubsub) Notify(page, point Point, tile Tile) {
if v, ok := p.m.Load(page.Integer()); ok {
v.(*observers).Notify(&Update{
Point: point,
Tile: tile,
})
}
}
// Subscribe registers an event listener on a system
func (p *pubsub) Subscribe(at Point, sub observer) bool {
if v, ok := p.m.Load(at.Integer()); ok {
return v.(*observers).Subscribe(sub)
}
// Slow path
v, _ := p.m.LoadOrStore(at.Integer(), newObservers())
return v.(*observers).Subscribe(sub)
}
// Unsubscribe deregisters an event listener from a system
func (p *pubsub) Unsubscribe(at Point, sub observer) bool {
if v, ok := p.m.Load(at.Integer()); ok {
return v.(*observers).Unsubscribe(sub)
}
return false
}
// -----------------------------------------------------------------------------
// Observers represents a change notifier which notifies the subscribers when
// a specific tile is updated.
type observers struct {
sync.Mutex
subs []observer
}
// newObservers creates a new instance of an change observer.
func newObservers() *observers {
return &observers{
subs: make([]observer, 0, 8),
}
}
// Notify notifies listeners of an update that happened.
func (s *observers) Notify(ev *Update) {
if s == nil {
return
}
s.Lock()
subs := s.subs
s.Unlock()
// Update every subscriber
for _, sub := range subs {
sub.onUpdate(ev)
}
}
// Subscribe registers an event listener on a system
func (s *observers) Subscribe(sub observer) bool {
s.Lock()
defer s.Unlock()
s.subs = append(s.subs, sub)
return len(s.subs) > 0 // At least one
}
// Unsubscribe deregisters an event listener from a system
func (s *observers) Unsubscribe(sub observer) bool {
s.Lock()
defer s.Unlock()
clean := s.subs[:0]
for _, o := range s.subs {
if o != sub {
clean = append(clean, o)
}
}
s.subs = clean
return len(s.subs) == 0
}