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

Cleanup, remove World.ComponentType #341

Merged
merged 3 commits into from
Jan 18, 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ This change was necessary to get the same performance as before, despite the mor
* Component and resource IDs are now opaque types instead of type aliases for `uint8` (#329)
* Restructures `EntityEvent` to remove redundant information and better handle relation changes (#333)
* World event listener changed from a simple function to a `Listener` interface (#334)
* Removes `World.ComponentType(ID)`, use function `ComponentInfo(ID)` instead (#341)

### Features

Expand Down
4 changes: 2 additions & 2 deletions ecs/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import "github.com/mlange-42/arche/ecs/event"
// Events that cover multiple types (e.g. entity creation and component addition) are only notified once.
// Field EventTypes contains the [event.Subscription] bits of covered event types.
//
// See module [event] and the [event.Subscription] constants for subscription logic.
// See sub-package [event] and the [event.Subscription] constants for subscription logic.
//
// # Event scheduling
//
Expand Down Expand Up @@ -52,7 +52,7 @@ func (e *EntityEvent) Contains(bit event.Subscription) bool {
// Listeners can subscribe to one or more event types via method Subscriptions.
// Further, subscriptions can be restricted to one or more components via method Components.
//
// See module [event] and the [event.Subscription] constants for subscription logic.
// See sub-package [event] and the [event.Subscription] constants for subscription logic.
//
// # See also
//
Expand Down
101 changes: 101 additions & 0 deletions ecs/functions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package ecs

import "reflect"

// ComponentID returns the [ID] for a component type via generics.
// Registers the type if it is not already registered.
//
// The number of unique component types per [World] is limited to 256 ([MaskTotalBits]).
// (64 with build tag `tiny`).
//
// Panics if called on a locked world and the type is not registered yet.
//
// ⚠️ Warning: Using IDs that are outside of the range of registered IDs anywhere in [World] or other places will result in undefined behavior!
func ComponentID[T any](w *World) ID {
tp := reflect.TypeOf((*T)(nil)).Elem()
return w.componentID(tp)
}

// ComponentIDs returns a list of all registered component IDs.
func ComponentIDs(w *World) []ID {
intIds := w.registry.IDs
ids := make([]ID, len(intIds))
for i, iid := range intIds {
ids[i] = id(iid)
}
return ids
}

// TypeID returns the [ID] for a component type.
// Registers the type if it is not already registered.
//
// The number of unique component types per [World] is limited to [MaskTotalBits].
func TypeID(w *World, tp reflect.Type) ID {
return w.componentID(tp)
}

// ComponentInfo returns the [CompInfo] for a component [ID], and whether the ID is assigned.
func ComponentInfo(w *World, id ID) (CompInfo, bool) {
tp, ok := w.registry.ComponentType(id.id)
if !ok {
return CompInfo{}, false
}

return CompInfo{
ID: id,
Type: tp,
IsRelation: w.registry.IsRelation.Get(id),
}, true
}

// ResourceID returns the [ResID] for a resource type via generics.
// Registers the type if it is not already registered.
//
// The number of resources per [World] is limited to [MaskTotalBits].
func ResourceID[T any](w *World) ResID {
tp := reflect.TypeOf((*T)(nil)).Elem()
return w.resourceID(tp)
}

// ResourceIDs returns a list of all registered resource IDs.
func ResourceIDs(w *World) []ResID {
intIds := w.resources.registry.IDs
ids := make([]ResID, len(intIds))
for i, iid := range intIds {
ids[i] = ResID{id: iid}
}
return ids
}

// ResourceType returns the reflect.Type for a resource [ResID], and whether the ID is assigned.
func ResourceType(w *World, id ResID) (reflect.Type, bool) {
return w.resources.registry.ComponentType(id.id)
}

// GetResource returns a pointer to the given resource type in the world.
//
// Returns nil if there is no such resource.
//
// Uses reflection. For more efficient access, see [World.Resources],
// and [github.com/mlange-42/arche/generic.Resource.Get] for a generic variant.
// These methods are more than 20 times faster than the GetResource function.
//
// See also [AddResource].
func GetResource[T any](w *World) *T {
return w.resources.Get(ResourceID[T](w)).(*T)
}

// AddResource adds a resource to the world.
// Returns the ID for the added resource.
//
// Panics if there is already such a resource.
//
// Uses reflection. For more efficient access, see [World.Resources],
// and [github.com/mlange-42/arche/generic.Resource.Add] for a generic variant.
//
// The number of resources per [World] is limited to [MaskTotalBits].
func AddResource[T any](w *World, res *T) ResID {
id := ResourceID[T](w)
w.resources.Add(id, res)
return id
}
66 changes: 66 additions & 0 deletions ecs/functions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package ecs

import (
"reflect"
"testing"

"github.com/stretchr/testify/assert"
)

func TestCompResIDs(t *testing.T) {
w := NewWorld()

posID := ComponentID[Position](&w)
rotID := ComponentID[rotation](&w)

res1ID := ResourceID[Position](&w)
res2ID := ResourceID[Velocity](&w)

tPosID := TypeID(&w, reflect.TypeOf(Position{}))
tRotID := TypeID(&w, reflect.TypeOf(rotation{}))

assert.Equal(t, posID, tPosID)
assert.Equal(t, rotID, tRotID)

assert.Equal(t, uint8(0), posID.id)
assert.Equal(t, uint8(1), rotID.id)

assert.Equal(t, uint8(0), res1ID.id)
assert.Equal(t, uint8(1), res2ID.id)

assert.Equal(t, []ID{id(0), id(1)}, ComponentIDs(&w))
assert.Equal(t, []ResID{{id: 0}, {id: 1}}, ResourceIDs(&w))
}

func TestRegisterComponents(t *testing.T) {
world := NewWorld()

ComponentID[Position](&world)

assert.Equal(t, id(0), ComponentID[Position](&world))
assert.Equal(t, id(1), ComponentID[rotation](&world))
}

func TestComponentInfo(t *testing.T) {
w := NewWorld()
_ = ComponentID[Velocity](&w)
posID := ComponentID[Position](&w)

info, ok := ComponentInfo(&w, posID)
assert.True(t, ok)
assert.Equal(t, info.Type, reflect.TypeOf(Position{}))

info, ok = ComponentInfo(&w, ID{id: 3})
assert.False(t, ok)
assert.Equal(t, info, CompInfo{})

resID := ResourceID[Velocity](&w)

tp, ok := ResourceType(&w, resID)
assert.True(t, ok)
assert.Equal(t, tp, reflect.TypeOf(Velocity{}))

tp, ok = ResourceType(&w, ResID{id: 3})
assert.False(t, ok)
assert.Equal(t, tp, nil)
}
4 changes: 4 additions & 0 deletions ecs/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ type testStruct14 struct{ val int32 }
type testStruct15 struct{ val int32 }
type testStruct16 struct{ val int32 }

type withSlice struct {
Slice []int
}

func TestTypeSizes(t *testing.T) {
printTypeSize[Entity]()
printTypeSize[entityIndex]()
Expand Down
Loading
Loading