-
-
Notifications
You must be signed in to change notification settings - Fork 19
/
view.go
293 lines (250 loc) · 7.69 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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
// 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"
"sync/atomic"
)
// Observer represents a tile update Observer.
type Observer[T comparable] interface {
Viewport() Rect
Resize(Rect, func(Point, Tile[T]))
onUpdate(*Update[T])
}
// ValueAt represents a tile and its value.
type ValueAt struct {
Point // The point of the tile
Value // The value of the tile
}
// Update represents a tile update notification.
type Update[T comparable] struct {
Old ValueAt // Old tile + value
New ValueAt // New tile + value
Add T // An object was added to the tile
Del T // An object was removed from the tile
}
var _ Observer[string] = (*View[string, string])(nil)
// View represents a view which can monitor a collection of tiles. Type parameters
// S and T are the state and tile types respectively.
type View[S any, T comparable] struct {
Grid *Grid[T] // The associated map
Inbox chan Update[T] // The update inbox for the view
State S // The state of the view
rect atomic.Uint64 // The view box
}
// NewView creates a new view for a map with a given state. State can be anything
// that is passed to the view and can be used to store additional information.
func NewView[S any, T comparable](m *Grid[T], state S) *View[S, T] {
v := &View[S, T]{
Grid: m,
Inbox: make(chan Update[T], 32),
State: state,
}
v.rect.Store(NewRect(-1, -1, -1, -1).pack())
return v
}
// Viewport returns the current viewport of the view.
func (v *View[S, T]) Viewport() Rect {
return unpackRect(v.rect.Load())
}
// Resize resizes the viewport and notifies the observers of the changes.
func (v *View[S, T]) Resize(view Rect, fn func(Point, Tile[T])) {
grid := v.Grid
prev := unpackRect(v.rect.Swap(view.pack()))
for _, diff := range view.Difference(prev) {
if diff.IsZero() {
continue // Skip zero-value rectangles
}
grid.pagesWithin(diff.Min, diff.Max, func(page *page[T]) {
r := page.Bounds()
switch {
// Page is now in view
case view.Intersects(r) && !prev.Intersects(r):
if grid.observers.Subscribe(page.point, v) {
page.SetObserved(true) // Mark the page as being observed
}
// Page is no longer in view
case !view.Intersects(r) && prev.Intersects(r):
if grid.observers.Unsubscribe(page.point, v) {
page.SetObserved(false) // Mark the page as not being observed
}
}
// Callback for each new tile in the view
if fn != nil {
page.Each(v.Grid, func(p Point, tile Tile[T]) {
if view.Contains(p) && !prev.Contains(p) {
fn(p, tile)
}
})
}
})
}
}
// MoveTo moves the viewport towards a particular direction.
func (v *View[S, T]) MoveTo(angle Direction, distance int16, fn func(Point, Tile[T])) {
p := angle.Vector(distance)
r := v.Viewport()
v.Resize(Rect{
Min: r.Min.Add(p),
Max: r.Max.Add(p),
}, fn)
}
// MoveBy moves the viewport towards a particular direction.
func (v *View[S, T]) MoveBy(x, y int16, fn func(Point, Tile[T])) {
r := v.Viewport()
v.Resize(Rect{
Min: r.Min.Add(At(x, y)),
Max: r.Max.Add(At(x, y)),
}, fn)
}
// MoveAt moves the viewport to a specific coordinate.
func (v *View[S, T]) MoveAt(nw Point, fn func(Point, Tile[T])) {
r := v.Viewport()
size := r.Max.Subtract(r.Min)
v.Resize(Rect{
Min: nw,
Max: nw.Add(size),
}, fn)
}
// Each iterates over all of the tiles in the view.
func (v *View[S, T]) Each(fn func(Point, Tile[T])) {
r := v.Viewport()
v.Grid.Within(r.Min, r.Max, fn)
}
// At returns the tile at a specified position.
func (v *View[S, T]) At(x, y int16) (Tile[T], bool) {
return v.Grid.At(x, y)
}
// WriteAt updates the entire tile at a specific coordinate.
func (v *View[S, T]) WriteAt(x, y int16, tile Value) {
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[S, T]) MergeAt(x, y int16, tile, mask Value) {
v.Grid.MaskAt(x, y, tile, mask)
}
// Close closes the view and unsubscribes from everything.
func (v *View[S, T]) Close() error {
r := v.Viewport()
v.Grid.pagesWithin(r.Min, r.Max, func(page *page[T]) {
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[S, T]) onUpdate(ev *Update[T]) {
v.Inbox <- *ev // (copy)
}
// -----------------------------------------------------------------------------
// Pubsub represents a publish/subscribe layer for observers.
type pubsub[T comparable] struct {
m sync.Map // Concurrent map of observers
tmp sync.Pool // Temporary observer sets for notifications
}
// Subscribe registers an event listener on a system
func (p *pubsub[T]) Subscribe(page Point, sub Observer[T]) bool {
if v, ok := p.m.Load(page.Integer()); ok {
return v.(*observers[T]).Subscribe(sub)
}
// Slow path
v, _ := p.m.LoadOrStore(page.Integer(), newObservers[T]())
return v.(*observers[T]).Subscribe(sub)
}
// Unsubscribe deregisters an event listener from a system
func (p *pubsub[T]) Unsubscribe(page Point, sub Observer[T]) bool {
if v, ok := p.m.Load(page.Integer()); ok {
return v.(*observers[T]).Unsubscribe(sub)
}
return false
}
// Notify notifies listeners of an update that happened.
func (p *pubsub[T]) Notify1(ev *Update[T], page Point) {
p.Each1(func(sub Observer[T]) {
viewport := sub.Viewport()
if viewport.Contains(ev.New.Point) || viewport.Contains(ev.Old.Point) {
sub.onUpdate(ev)
}
}, page)
}
// Notify notifies listeners of an update that happened.
func (p *pubsub[T]) Notify2(ev *Update[T], pages [2]Point) {
p.Each2(func(sub Observer[T]) {
viewport := sub.Viewport()
if viewport.Contains(ev.New.Point) || viewport.Contains(ev.Old.Point) {
sub.onUpdate(ev)
}
}, pages)
}
// Each iterates over each observer in a page
func (p *pubsub[T]) Each1(fn func(sub Observer[T]), page Point) {
if v, ok := p.m.Load(page.Integer()); ok {
v.(*observers[T]).Each(func(sub Observer[T]) {
fn(sub)
})
}
}
// Each2 iterates over each observer in a page
func (p *pubsub[T]) Each2(fn func(sub Observer[T]), pages [2]Point) {
targets := p.tmp.Get().(map[Observer[T]]struct{})
clear(targets)
defer p.tmp.Put(targets)
// Collect all observers from all pages
for _, page := range pages {
if v, ok := p.m.Load(page.Integer()); ok {
v.(*observers[T]).Each(func(sub Observer[T]) {
targets[sub] = struct{}{}
})
}
}
// Invoke the callback for each observer, once
for sub := range targets {
fn(sub)
}
}
// -----------------------------------------------------------------------------
// Observers represents a change notifier which notifies the subscribers when
// a specific tile is updated.
type observers[T comparable] struct {
sync.Mutex
subs []Observer[T]
}
// newObservers creates a new instance of an change observer.
func newObservers[T comparable]() *observers[T] {
return &observers[T]{
subs: make([]Observer[T], 0, 8),
}
}
// Each iterates over each observer
func (s *observers[T]) Each(fn func(sub Observer[T])) {
if s == nil {
return
}
s.Lock()
defer s.Unlock()
for _, sub := range s.subs {
fn(sub)
}
}
// Subscribe registers an event listener on a system
func (s *observers[T]) Subscribe(sub Observer[T]) 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[T]) Unsubscribe(sub Observer[T]) 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
}