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

mlange-42 / ark / 13636797181

03 Mar 2025 05:30PM CUT coverage: 97.422% (-0.3%) from 97.712%
13636797181

Pull #114

github

web-flow
Merge 74bc98fa8 into 0dcd540e6
Pull Request #114: Filter caching

426 of 470 new or added lines in 9 files covered. (90.64%)

41 existing lines in 4 files now uncovered.

5592 of 5740 relevant lines covered (97.42%)

35229.13 hits per line

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

48.15
/ecs/cache.go
1
package ecs
2

3
// Cache entry for a [Filter].
4
type cacheEntry struct {
5
        id      uint32          // Entry ID.
6
        filter  Batch           // The underlying filter.
7
        indices map[tableID]int // Map of table indices for removal.
8
        tables  []tableID       // Tables matching the filter.
9
}
10

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

27
// newCache creates a new [cache].
28
func newCache() cache {
187✔
29
        return cache{
187✔
30
                intPool: newIntPool[uint32](128),
187✔
31
                indices: map[uint32]int{},
187✔
32
                filters: []cacheEntry{},
187✔
33
        }
187✔
34
}
187✔
35

36
// Register a [Filter].
37
func (c *cache) register(storage *storage, batch *Batch) *cacheEntry {
25✔
38
        // TODO: prevent duplicate registration
25✔
39
        id := c.intPool.Get()
25✔
40
        c.filters = append(c.filters,
25✔
41
                cacheEntry{
25✔
42
                        id:      id,
25✔
43
                        filter:  *batch,
25✔
44
                        tables:  storage.getTableIDs(batch),
25✔
45
                        indices: nil,
25✔
46
                })
25✔
47
        c.indices[id] = len(c.filters) - 1
25✔
48
        return &c.filters[len(c.filters)-1]
25✔
49
}
25✔
50

51
func (c *cache) unregister(f *cacheEntry) {
25✔
52
        idx, ok := c.indices[f.id]
25✔
53
        if !ok {
25✔
NEW
54
                panic("no filter for id found to unregister")
×
55
        }
56
        delete(c.indices, f.id)
25✔
57

25✔
58
        last := len(c.filters) - 1
25✔
59
        if idx != last {
25✔
NEW
60
                c.filters[idx], c.filters[last] = c.filters[last], c.filters[idx]
×
NEW
61
                c.indices[c.filters[idx].id] = idx
×
NEW
62
        }
×
63
        c.filters[last] = cacheEntry{}
25✔
64
        c.filters = c.filters[:last]
25✔
65
}
66

67
// Adds a table.
68
//
69
// Iterates over all filters and adds the node to the resp. entry where the filter matches.
70
func (c *cache) addTable(storage *storage, table *table) {
459✔
71
        arch := &storage.archetypes[table.archetype]
459✔
72
        if !table.HasRelations() {
800✔
73
                for i := range c.filters {
341✔
NEW
74
                        e := &c.filters[i]
×
NEW
75
                        if !e.filter.filter.matches(&arch.mask) {
×
NEW
76
                                continue
×
77
                        }
NEW
78
                        e.tables = append(e.tables, table.id)
×
79
                }
80
                return
341✔
81
        }
82

83
        for i := range c.filters {
118✔
NEW
84
                e := &c.filters[i]
×
NEW
85
                if !e.filter.filter.matches(&arch.mask) {
×
NEW
86
                        continue
×
87
                }
NEW
88
                if !table.Matches(e.filter.relations) {
×
NEW
89
                        continue
×
90
                }
NEW
91
                e.tables = append(e.tables, table.id)
×
NEW
92
                if e.indices != nil {
×
NEW
93
                        e.indices[table.id] = len(e.tables) - 1
×
NEW
94
                }
×
95
        }
96
}
97

98
// Removes a table.
99
//
100
// Can only be used for tables that have a relation target.
101
// Tables without a relation are never removed.
102
func (c *cache) removeTable(storage *storage, table *table) {
4✔
103
        if !table.HasRelations() {
4✔
NEW
104
                return
×
NEW
105
        }
×
106
        arch := &storage.archetypes[table.archetype]
4✔
107
        for i := range c.filters {
4✔
NEW
108
                e := &c.filters[i]
×
NEW
109

×
NEW
110
                if e.indices == nil && e.filter.filter.matches(&arch.mask) {
×
NEW
111
                        c.mapTables(storage, e)
×
NEW
112
                }
×
113

NEW
114
                if idx, ok := e.indices[table.id]; ok {
×
NEW
115
                        lastIndex := len(e.tables) - 1
×
NEW
116
                        swapped := idx != lastIndex
×
NEW
117
                        if swapped {
×
NEW
118
                                e.tables[idx], e.tables[lastIndex] = e.tables[lastIndex], e.tables[idx]
×
NEW
119
                        }
×
NEW
120
                        e.tables = e.tables[:lastIndex]
×
NEW
121
                        if swapped {
×
NEW
122
                                e.indices[e.tables[idx]] = idx
×
NEW
123
                        }
×
NEW
124
                        delete(e.indices, table.id)
×
125
                }
126
        }
127
}
128

NEW
129
func (c *cache) mapTables(storage *storage, e *cacheEntry) {
×
NEW
130
        e.indices = map[tableID]int{}
×
NEW
131
        for i, tableID := range e.tables {
×
NEW
132
                table := &storage.tables[tableID]
×
NEW
133
                if table.HasRelations() {
×
NEW
134
                        e.indices[tableID] = i
×
NEW
135
                }
×
136
        }
137
}
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