Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build tag tiny for max. 64 components #338

Merged
merged 7 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 59 additions & 3 deletions .github/workflows/benchmarks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,25 @@ jobs:
run: |
cd benchmark
go test -benchmem -run=^$ -bench ^.*$ ./arche/...

internal_tiny:
name: Internals (tiny)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: '1.21.x'
- name: Install dependencies
run: go get .
- name: Run internal benchmarks (tiny)
run: |
go test -tags tiny -benchmem -run=^$ -bench ^.*$ ./...
- name: Run Arche benchmarks (tiny)
run: |
cd benchmark
go test -tags tiny -benchmem -run=^$ -bench ^.*$ ./arche/...

relations:
name: Entity relations
Expand All @@ -43,6 +62,23 @@ jobs:
run: |
cd benchmark
go test -benchmem -run=^$ -bench ^.*$ ./competition/relations/...


relations_tiny:
name: Entity relations (tiny)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: '1.21.x'
- name: Install dependencies
run: go get .
- name: Benchmark entity relations (tiny)
run: |
cd benchmark
go test -tags tiny -benchmem -run=^$ -bench ^.*$ ./competition/relations/...

other_methods:
name: vs. Array of Structs
Expand Down Expand Up @@ -74,12 +110,32 @@ jobs:
- name: Benchmark Pos/Vel
run: |
cd benchmark
go test -benchmem -run=^$ -bench ^.*$ ./competition/pos_vel/... --count 12
go test -benchmem -run=^$ -bench ^.*$ ./competition/pos_vel/... --count 10
- name: Benchmark Access 10 Components
run: |
cd benchmark
go test -benchmem -run=^$ -bench ^.*$ ./competition/many_components/... --count 12
go test -benchmem -run=^$ -bench ^.*$ ./competition/many_components/... --count 10
- name: Benchmark Add/Remove Components
run: |
cd benchmark
go test -benchmem -run=^$ -bench ^.*$ ./competition/add_remove/... --count 12
go test -benchmem -run=^$ -bench ^.*$ ./competition/add_remove/... --count 10

competition_tiny:
name: ECS competition (tiny)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: '1.21.x'
- name: Install dependencies
run: go get .
- name: Benchmark Pos/Vel (tiny)
run: |
cd benchmark
go test -tags tiny -benchmem -run=^$ -bench ^.*$ ./competition/pos_vel/... --count 5
- name: Benchmark Access 10 Components (tiny)
run: |
cd benchmark
go test -tags tiny -benchmem -run=^$ -bench ^.*$ ./competition/many_components/... --count 5
58 changes: 57 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,16 @@ jobs:
run: go get .
- name: Build Linux
run: GOOS=linux GOARCH=amd64 go build ./...
- name: Build Linux (tiny)
run: GOOS=linux GOARCH=amd64 go build -tags tiny ./...
- name: Build Windows
run: GOOS=windows GOARCH=amd64 go build ./...
- name: Build Windows (tiny)
run: GOOS=windows GOARCH=amd64 go build -tags tiny ./...
- name: Build MacOS
run: GOOS=darwin GOARCH=amd64 go build ./...
- name: Build MacOS (tiny)
run: GOOS=darwin GOARCH=amd64 go build -tags tiny ./...

test:
name: Run tests
Expand All @@ -48,6 +54,24 @@ jobs:
with:
path-to-lcov: coverage.out

test_tiny:
name: Run tests (tiny)
runs-on: ubuntu-latest
steps:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: '1.21.x'
- name: Check out code
uses: actions/checkout@v2
- name: Install dependencies
run: |
go get .
- name: Run Unit tests (tiny)
run: |
go test -v -covermode atomic -coverprofile="coverage.out" ./...
go tool cover -func="coverage.out"

lint:
name: Run linters
runs-on: ubuntu-latest
Expand All @@ -69,8 +93,12 @@ jobs:
fi
- name: Lint with vet
run: go vet ./...
- name: Lint with vet (tiny)
run: go vet -tags tiny ./...
- name: Lint with staticcheck
run: staticcheck ./...
- name: Lint with staticcheck (tiny)
run: staticcheck -tags tiny ./...
- name: Lint with ineffassign
run: ineffassign ./...

Expand All @@ -94,7 +122,7 @@ jobs:
run: gorelease -base=${{ steps.latest-tag.outputs.tag }}

examples:
name: Run examples
name: Run Examples
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
Expand All @@ -120,3 +148,31 @@ jobs:
go run ./examples/resources
go run ./examples/systems
go run ./examples/world_stats

examples_tiny:
name: Run Examples (tiny)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: '1.21.x'
- name: Install dependencies
run: go get .
- name: Run examples
run: |
go run -tags tiny ./examples/base
go run -tags tiny ./examples/batch_ops
go run -tags tiny ./examples/events
go run -tags tiny ./examples/filter
go run -tags tiny ./examples/generic
go run -tags tiny ./examples/locked_world
go run -tags tiny ./examples/parallel
go run -tags tiny ./examples/random_access
go run -tags tiny ./examples/random_sampling
go run -tags tiny ./examples/readme
go run -tags tiny ./examples/relations
go run -tags tiny ./examples/resources
go run -tags tiny ./examples/systems
go run -tags tiny ./examples/world_stats
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ This change was necessary to get the same performance as before, despite the mor
* The world's entity state can be extracted and re-established via `World.DumpEntities()` and `World.LoadEntities()` (#319, #326)
* Adds functions `ComponentIDs(*World)` and `ResourceIDs(*World)` to get all registered IDs (#329)
* Adds methods `Mask.And`, `Mask.Or` and `Mask.Xor` (#335)
* Adds build tag `tiny` to restrict to 64 components for an extra bit of performance (#338)

### Performance

Expand Down
93 changes: 93 additions & 0 deletions benchmark/profile/events/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package main

// Profiling:
// go build ./benchmark/profile/events
// events
// go tool pprof -http=":8000" -nodefraction=0.001 events cpu.pprof
// go tool pprof -http=":8000" -nodefraction=0.001 events mem.pprof

import (
"github.com/mlange-42/arche/ecs"
"github.com/mlange-42/arche/ecs/event"
"github.com/pkg/profile"
)

type position struct {
X int
Y int
}

type velocity struct {
X int
Y int
}

type rotation struct {
Angle int
}

func main() {

count := 100
iters := 1000
entities := 1000

stop := profile.Start(profile.CPUProfile, profile.ProfilePath("."))
run(count, iters, entities)
stop.Stop()

stop = profile.Start(profile.MemProfileAllocs, profile.ProfilePath("."))
run(count, iters, entities)
stop.Stop()
}

func run(rounds, iters, numEntities int) {
for i := 0; i < rounds; i++ {
world := ecs.NewWorld(
ecs.NewConfig().WithCapacityIncrement(1024),
)

posID := ecs.ComponentID[position](&world)
velID := ecs.ComponentID[velocity](&world)

builder := ecs.NewBuilder(&world, posID)
builder.NewBatch(1000)

pos := []ecs.ID{posID}
vel := []ecs.ID{velID}

filterPos := ecs.All(posID)
filterVel := ecs.All(velID)

var temp event.Subscription
listener := dummyListener{}
world.SetListener(&listener)

hasPos := true
for j := 0; j < iters; j++ {
if hasPos {
world.Batch().Exchange(filterPos, vel, pos)
} else {
world.Batch().Exchange(filterVel, pos, vel)
}
hasPos = !hasPos
}
_ = temp
}
}

type dummyListener struct {
temp event.Subscription
}

func (l *dummyListener) Notify(w *ecs.World, evt ecs.EntityEvent) {
l.temp = evt.EventTypes
}

func (l *dummyListener) Subscriptions() event.Subscription {
return event.Components
}

func (l *dummyListener) Components() *ecs.Mask {
return nil
}
60 changes: 22 additions & 38 deletions ecs/bitmask.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//go:build !tiny

package ecs

import (
Expand All @@ -8,7 +10,6 @@ import (
//
// It is the maximum number of component types that may exist in any [World].
const MaskTotalBits = 256
const wordSize = 64

// Mask is a 256 bit bitmask.
// It is also a [Filter] for including certain components.
Expand All @@ -19,43 +20,6 @@ type Mask struct {
bits [4]uint64 // 4x 64 bits of the mask
}

// All creates a new Mask from a list of IDs.
// Matches all entities that have the respective components, and potentially further components.
//
// See also [Mask.Without] and [Mask.Exclusive]
//
// If any [ID] is greater than or equal to [MaskTotalBits], it will not be added to the mask.
func All(ids ...ID) Mask {
var mask Mask
for _, id := range ids {
mask.Set(id, true)
}
return mask
}

// Matches the mask as filter against another mask.
func (b Mask) Matches(bits *Mask) bool {
return bits.Contains(&b)
}

// Without creates a [MaskFilter] which filters for including the mask's components,
// and excludes the components given as arguments.
func (b Mask) Without(comps ...ID) MaskFilter {
return MaskFilter{
Include: b,
Exclude: All(comps...),
}
}

// Exclusive creates a [MaskFilter] which filters for exactly the mask's components.
// Matches only entities that have exactly the given components, and no other.
func (b Mask) Exclusive() MaskFilter {
return MaskFilter{
Include: b,
Exclude: b.Not(),
}
}

// Get reports whether the bit at the given index [ID] is set.
//
// Returns false for bit >= [MaskTotalBits].
Expand Down Expand Up @@ -152,3 +116,23 @@ func (b *Mask) Xor(other *Mask) Mask {
func (b *Mask) TotalBitsSet() int {
return bits.OnesCount64(b.bits[0]) + bits.OnesCount64(b.bits[1]) + bits.OnesCount64(b.bits[2]) + bits.OnesCount64(b.bits[3])
}

func (b *Mask) toTypes(reg *componentRegistry) []componentType {
count := int(b.TotalBitsSet())
types := make([]componentType, count)

idx := 0
for i := range b.bits {
if b.bits[i] == 0 {
continue
}
for j := 0; j < wordSize; j++ {
id := ID{id: uint8(i*wordSize + j)}
if b.Get(id) {
types[idx] = componentType{ID: id, Type: reg.Types[id.id]}
idx++
}
}
}
return types
}
Loading
Loading