• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mlange-42 / ark / 13842813710

13 Mar 2025 07:15PM CUT coverage: 99.762% (+0.2%) from 99.547%
13842813710

push

github

web-flow
Improve test coverage (#194)

8371 of 8391 relevant lines covered (99.76%)

21363.27 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.85
/ecs/cache.go
1
package ecs
2

3
import "math"
4

5
type cacheID uint32
6

7
const maxCacheID = math.MaxUint32
8

9
// Cache entry for a [Filter].
10
type cacheEntry struct {
11
        id        cacheID         // Entry ID.
12
        filter    *filter         // The underlying filter.
13
        relations []RelationID    // Entity relationships.
14
        indices   map[tableID]int // Map of table indices for removal.
15
        tables    []tableID       // Tables matching the filter.
16
}
17

18
// cache provides [Filter] caching to speed up queries.
19
//
20
// For registered filters, the relevant archetypes are tracked internally,
21
// so that there are no mask checks required during iteration.
22
// This is particularly helpful to avoid query iteration slowdown by a very high number of archetypes.
23
// If the number of archetypes exceeds approx. 50-100, uncached filters experience a slowdown.
24
// The relative slowdown increases with lower numbers of entities queried (noticeable below a few thousand entities).
25
// Cached filters avoid this slowdown.
26
//
27
// The overhead of tracking cached filters internally is very low, as updates are required only when new archetypes are created.
28
type cache struct {
29
        indices map[cacheID]int  // Mapping from filter IDs to indices in filters
30
        filters []cacheEntry     // The cached filters, indexed by indices
31
        intPool intPool[cacheID] // Pool for filter IDs
32
}
33

34
// newCache creates a new [cache].
35
func newCache() cache {
252✔
36
        return cache{
252✔
37
                intPool: newIntPool[cacheID](128),
252✔
38
                indices: map[cacheID]int{},
252✔
39
                filters: []cacheEntry{},
252✔
40
        }
252✔
41
}
252✔
42

43
func (c *cache) getEntry(id cacheID) *cacheEntry {
30✔
44
        return &c.filters[c.indices[id]]
30✔
45
}
30✔
46

47
// Register a [Filter].
48
func (c *cache) register(storage *storage, filter *filter, relations []RelationID) cacheID {
34✔
49
        // TODO: prevent duplicate registration
34✔
50
        id := c.intPool.Get()
34✔
51
        index := len(c.filters)
34✔
52
        c.filters = append(c.filters,
34✔
53
                cacheEntry{
34✔
54
                        id:        id,
34✔
55
                        filter:    filter,
34✔
56
                        relations: relations,
34✔
57
                        tables:    storage.getTableIDs(filter, relations),
34✔
58
                        indices:   nil,
34✔
59
                })
34✔
60
        c.indices[id] = index
34✔
61
        return id
34✔
62
}
34✔
63

64
func (c *cache) unregister(id cacheID) {
29✔
65
        idx, ok := c.indices[id]
29✔
66
        if !ok {
30✔
67
                panic("no filter for id found to unregister")
1✔
68
        }
69
        delete(c.indices, id)
28✔
70

28✔
71
        last := len(c.filters) - 1
28✔
72
        if idx != last {
29✔
73
                c.filters[idx], c.filters[last] = c.filters[last], c.filters[idx]
1✔
74
                c.indices[c.filters[idx].id] = idx
1✔
75
        }
1✔
76
        c.filters[last] = cacheEntry{}
28✔
77
        c.filters = c.filters[:last]
28✔
78
}
79

80
// Adds a table.
81
//
82
// Iterates over all filters and adds the node to the resp. entry where the filter matches.
83
func (c *cache) addTable(storage *storage, table *table) {
576✔
84
        arch := &storage.archetypes[table.archetype]
576✔
85
        if !table.HasRelations() {
999✔
86
                for i := range c.filters {
428✔
87
                        e := &c.filters[i]
5✔
88
                        if !e.filter.matches(&arch.mask) {
9✔
89
                                continue
4✔
90
                        }
91
                        e.tables = append(e.tables, table.id)
1✔
92
                }
93
                return
423✔
94
        }
95

96
        for i := range c.filters {
176✔
97
                e := &c.filters[i]
23✔
98
                if !e.filter.matches(&arch.mask) {
28✔
99
                        continue
5✔
100
                }
101
                if !table.Matches(e.relations) {
27✔
102
                        continue
9✔
103
                }
104
                e.tables = append(e.tables, table.id)
9✔
105
                if e.indices != nil {
15✔
106
                        e.indices[table.id] = len(e.tables) - 1
6✔
107
                }
6✔
108
        }
109
}
110

111
// Removes a table.
112
//
113
// Can only be used for tables that have a relation target.
114
// Tables without a relation are never removed.
115
func (c *cache) removeTable(storage *storage, table *table) {
15✔
116
        if !table.HasRelations() {
15✔
117
                return
×
118
        }
×
119
        arch := &storage.archetypes[table.archetype]
15✔
120
        for i := range c.filters {
37✔
121
                e := &c.filters[i]
22✔
122

22✔
123
                if e.indices == nil && e.filter.matches(&arch.mask) {
25✔
124
                        c.mapTables(storage, e)
3✔
125
                }
3✔
126

127
                if idx, ok := e.indices[table.id]; ok {
31✔
128
                        lastIndex := len(e.tables) - 1
9✔
129
                        swapped := idx != lastIndex
9✔
130
                        if swapped {
13✔
131
                                e.tables[idx], e.tables[lastIndex] = e.tables[lastIndex], e.tables[idx]
4✔
132
                        }
4✔
133
                        e.tables = e.tables[:lastIndex]
9✔
134
                        if swapped {
13✔
135
                                e.indices[e.tables[idx]] = idx
4✔
136
                        }
4✔
137
                        delete(e.indices, table.id)
9✔
138
                }
139
        }
140
}
141

142
func (c *cache) mapTables(storage *storage, e *cacheEntry) {
3✔
143
        e.indices = map[tableID]int{}
3✔
144
        for i, tableID := range e.tables {
6✔
145
                table := &storage.tables[tableID]
3✔
146
                if table.HasRelations() {
6✔
147
                        e.indices[tableID] = i
3✔
148
                }
3✔
149
        }
150
}
151

152
func (c *cache) Reset() {
1✔
153
        for i := range c.filters {
2✔
154
                c.filters[i].tables = nil
1✔
155
        }
1✔
156
        c.indices = map[cacheID]int{}
1✔
157
        c.filters = c.filters[:0]
1✔
158
        c.intPool.Reset()
1✔
159
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc