Skip to content

Commit

Permalink
Added without filter
Browse files Browse the repository at this point in the history
  • Loading branch information
unitoftime committed Nov 15, 2023
1 parent d2f0f18 commit 23fe989
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 30 deletions.
42 changes: 41 additions & 1 deletion component.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ func (c Box[T]) Get() T {
// Note: you can increase max component size by increasing maxComponentId and archetypeMask
// TODO: I should have some kind of panic if you go over maximum component size
const maxComponentId = 255

var blankArchMask archetypeMask
// Supports maximum 256 unique component types
type archetypeMask [4]uint64 // TODO: can/should I make this configurable?
func buildArchMask(comps ...Component) archetypeMask {
Expand All @@ -57,26 +59,47 @@ func buildArchMask(comps ...Component) archetypeMask {
}
return mask
}
func buildArchMaskFromAny(comps ...any) archetypeMask {
var mask archetypeMask
for _, comp := range comps {
// Ranges: [0, 64), [64, 128), [128, 192), [192, 256)
c := name(comp)
idx := c / 64
offset := c - (64 * idx)
mask[idx] |= (1<<offset)
}
return mask
}

// Performs a bitwise or on the base mask `m` with the added mask `a`
// Performs a bitwise OR on the base mask `m` with the added mask `a`
func (m archetypeMask) bitwiseOr(a archetypeMask) archetypeMask {
for i := range m {
m[i] = m[i] | a[i]
}
return m
}

// Performs a bitwise AND on the base mask `m` with the added mask `a`
func (m archetypeMask) bitwiseAnd(a archetypeMask) archetypeMask {
for i := range m {
m[i] = m[i] & a[i]
}
return m
}

// TODO: You should move to this (ie archetype graph (or bitmask?). maintain the current archetype node, then traverse to nodes (and add new ones) based on which components are added): https://ajmmertens.medium.com/building-an-ecs-2-archetypes-and-vectorization-fe21690805f9
// Dynamic component Registry
type componentRegistry struct {
archSet [][]archetypeId // Contains the set of archetypeIds that have this component
archMask map[archetypeMask]archetypeId // Contains a mapping of archetype bitmasks to archetypeIds
revArchMask map[archetypeId]archetypeMask // Contains the reverse mapping of archetypeIds to archetype masks
}

func newComponentRegistry() *componentRegistry {
r := &componentRegistry{
archSet: make([][]archetypeId, maxComponentId + 1), // TODO: hardcoded to max component
archMask: make(map[archetypeMask]archetypeId),
revArchMask: make(map[archetypeId]archetypeMask),
}
return r
}
Expand All @@ -99,6 +122,7 @@ func (r *componentRegistry) getArchetypeId(engine *archEngine, comps ...Componen
if !ok {
archId = engine.newArchetypeId(mask)
r.archMask[mask] = archId
r.revArchMask[archId] = mask

// Add this archetypeId to every component's archList
for _, comp := range comps {
Expand All @@ -108,3 +132,19 @@ func (r *componentRegistry) getArchetypeId(engine *archEngine, comps ...Componen
}
return archId
}

// This is mostly for the without filter
func (r *componentRegistry) archIdOverlapsMask(archId archetypeId, compArchMask archetypeMask) bool {
// compArchMask := buildArchMask(comps...)
archMaskToCheck, ok := r.revArchMask[archId]
if !ok {
// TODO: I'm not sure what the best thing to do here is. If we get here it means that an archId was passed in which hasn't been created yet. I think that indicates a programmer bug, so I'm going to panic
panic("Bug: Invalid ArchId used")
}
resultArchMask := archMaskToCheck.bitwiseAnd(compArchMask)
if resultArchMask != blankArchMask {
// If the resulting arch mask is nonzero, it means that both the component mask and the base mask had the same bit set, which means the arch had one of the components
return true
}
return false
}
61 changes: 32 additions & 29 deletions filter.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,8 @@
package ecs

// import "fmt"

// type without struct {
// fields []any
// }
// func Without(fields ...any) without {
// return without{fields}
// }
// extractedFilters := make([]any, 0)
// for _, f := range filters {
// switch t := f.(type) {
// case without:
// }
// }

// type buildQuery interface {
// build(world)
// }
import (
"slices"

Check failure on line 4 in filter.go

View workflow job for this annotation

GitHub Actions / build (1.20.x, ubuntu-latest)

package slices is not in GOROOT (/opt/hostedtoolcache/go/1.20.10/x64/src/slices)
)

// TODO - Filter types:
// Optional - Lets you view even if component is missing (func will return nil)
Expand All @@ -27,17 +12,20 @@ type Filter interface {
Filter([]componentId) []componentId
}

// type without struct {
// comps []any
// }
type without struct {
mask archetypeMask
}

// TODO - figure out how to implement
// // Creates a filter to ensure that entities will not have the specified components
// func Without(comps ...any) without {
// return without{
// comps: comps,
// }
// }
// Creates a filter to ensure that entities will not have the specified components
func Without(comps ...any) without {
return without{
mask: buildArchMaskFromAny(comps...),
}
}
func (w without) Filter(list []componentId) []componentId {
return list // Dont filter anything. We need to exclude later on
// return append(list, w.comps...)
}

type with struct {
comps []componentId
Expand Down Expand Up @@ -93,23 +81,38 @@ func (f optional) Filter(list []componentId) []componentId {

type filterList struct {
comps []componentId
withoutArchMask archetypeMask
cachedArchetypeGeneration int // Denotes the world's archetype generation that was used to create the list of archIds. If the world has a new generation, we should probably regenerate
archIds []archetypeId
}

func newFilterList(comps []componentId, filters ...Filter) filterList {
var withoutArchMask archetypeMask
for _, f := range filters {
comps = f.Filter(comps)
withoutFilter, isWithout := f.(without)
if isWithout {
withoutArchMask = withoutFilter.mask
} else {
comps = f.Filter(comps)
}
}

return filterList{
comps: comps,
withoutArchMask: withoutArchMask,
archIds: make([]archetypeId, 0),
}
}
func (f *filterList) regenerate(world *World) {
if world.engine.getGeneration() != f.cachedArchetypeGeneration {
f.archIds = world.engine.FilterList(f.archIds, f.comps)

if f.withoutArchMask != blankArchMask {
f.archIds = slices.DeleteFunc(f.archIds, func(archId archetypeId) bool {
return world.engine.dcr.archIdOverlapsMask(archId, f.withoutArchMask)
})
}

f.cachedArchetypeGeneration = world.engine.getGeneration()
}
}
Expand Down

0 comments on commit 23fe989

Please sign in to comment.