Skip to content

Commit

Permalink
improve thread safety (#131)
Browse files Browse the repository at this point in the history
  • Loading branch information
yohamta authored Mar 31, 2024
1 parent d4f8606 commit ce2bc07
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 156 deletions.
89 changes: 37 additions & 52 deletions entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,45 +20,40 @@ type Entry struct {
}

// Get returns the component from the entry
func Get[T any](e *Entry, ctype component.IComponentType) *T {
return (*T)(e.Component(ctype))
func Get[T any](e *Entry, c component.IComponentType) *T {
return (*T)(e.Component(c))
}

// GetComponents uses reflection to convert the unsafe.Pointers of an entry into its component data instances.
// Note that this is likely to be slow and should not be used in hot paths or if not necessary.
func GetComponents(e *Entry) []any {
archetypeIdx := e.loc.Archetype
s := e.World.StorageAccessor().Archetypes[archetypeIdx]
cs := s.ComponentTypes()
var instances []any
for _, ctyp := range cs {
instancePtr := e.Component(ctyp)
componentType := ctyp.Typ()
val := reflect.NewAt(componentType, instancePtr)
valInstance := reflect.Indirect(val).Interface()
instances = append(instances, valInstance)
for _, c := range e.World.StorageAccessor().Archetypes[e.loc.Archetype].ComponentTypes() {
instances = append(instances, reflect.Indirect(
reflect.NewAt(c.Typ(), e.Component(c))).Interface(),
)
}
return instances
}

// Add adds the component to the entry.
func Add[T any](e *Entry, ctype component.IComponentType, component *T) {
e.AddComponent(ctype, unsafe.Pointer(component))
func Add[T any](e *Entry, c component.IComponentType, component *T) {
e.AddComponent(c, unsafe.Pointer(component))
}

// Set sets the comopnent of the entry.
func Set[T any](e *Entry, ctype component.IComponentType, component *T) {
e.SetComponent(ctype, unsafe.Pointer(component))
func Set[T any](e *Entry, c component.IComponentType, component *T) {
e.SetComponent(c, unsafe.Pointer(component))
}

// SetValue sets the value of the component.
func SetValue[T any](e *Entry, ctype component.IComponentType, value T) {
*Get[T](e, ctype) = value
func SetValue[T any](e *Entry, c component.IComponentType, value T) {
*Get[T](e, c) = value
}

// GetValue gets the value of the component.
func GetValue[T any](e *Entry, ctype component.IComponentType) T {
return *Get[T](e, ctype)
func GetValue[T any](e *Entry, c component.IComponentType) T {
return *Get[T](e, c)
}

// Remove removes the component from the entry.
Expand All @@ -85,60 +80,50 @@ func (e *Entry) Entity() Entity {
}

// Component returns the component.
func (e *Entry) Component(ctype component.IComponentType) unsafe.Pointer {
c := e.loc.Component
a := e.loc.Archetype
return e.World.components.Storage(ctype).Component(a, c)
func (e *Entry) Component(c component.IComponentType) unsafe.Pointer {
return e.World.components.Storage(c).Component(e.loc.Archetype, e.loc.Component)
}

// SetComponent sets the component.
func (e *Entry) SetComponent(ctype component.IComponentType, component unsafe.Pointer) {
c := e.loc.Component
a := e.loc.Archetype
e.World.components.Storage(ctype).SetComponent(a, c, component)
func (e *Entry) SetComponent(c component.IComponentType, component unsafe.Pointer) {
e.World.components.Storage(c).SetComponent(e.loc.Archetype, e.loc.Component, component)
}

// AddComponent adds the component to the entity.
func (e *Entry) AddComponent(ctype component.IComponentType, components ...unsafe.Pointer) {
func (e *Entry) AddComponent(c component.IComponentType, components ...unsafe.Pointer) {
if len(components) > 1 {
panic("AddComponent: component argument must be a single value")
}
if !e.HasComponent(ctype) {
c := e.loc.Component
a := e.loc.Archetype

base_layout := e.World.archetypes[a].Layout().Components()
target_arc := e.World.getArchetypeForComponents(append(base_layout, ctype))
e.World.TransferArchetype(a, target_arc, c)

if !e.HasComponent(c) {
archetypeIndex := e.loc.Archetype
targetArchetype := e.World.getArchetypeForComponents(
append(e.World.archetypes[archetypeIndex].Layout().Components(), c),
)
e.World.TransferArchetype(archetypeIndex, targetArchetype, e.loc.Component)
e.loc = e.World.Entry(e.entity).loc
}
if len(components) == 1 {
e.SetComponent(ctype, components[0])
e.SetComponent(c, components[0])
}
}

// RemoveComponent removes the component from the entity.
func (e *Entry) RemoveComponent(ctype component.IComponentType) {
if !e.Archetype().Layout().HasComponent(ctype) {
func (e *Entry) RemoveComponent(c component.IComponentType) {
if !e.Archetype().Layout().HasComponent(c) {
return
}

c := e.loc.Component
a := e.loc.Archetype

base_layout := e.World.archetypes[a].Layout().Components()
target_layout := make([]component.IComponentType, 0, len(base_layout)-1)
for _, c2 := range base_layout {
if c2 == ctype {
baseLayout := e.World.archetypes[e.loc.Archetype].Layout().Components()
targetLayout := make([]component.IComponentType, 0, len(baseLayout)-1)
for _, c2 := range baseLayout {
if c2 == c {
continue
}
target_layout = append(target_layout, c2)
targetLayout = append(targetLayout, c2)
}

target_arc := e.World.getArchetypeForComponents(target_layout)
e.World.TransferArchetype(e.loc.Archetype, target_arc, c)

targetArchetype := e.World.getArchetypeForComponents(targetLayout)
e.World.TransferArchetype(e.loc.Archetype, targetArchetype, e.loc.Component)
e.loc = e.World.Entry(e.entity).loc
}

Expand All @@ -159,8 +144,8 @@ func (e *Entry) Archetype() *storage.Archetype {
}

// HasComponent returns true if the entity has the given component type.
func (e *Entry) HasComponent(componentType component.IComponentType) bool {
return e.Archetype().Layout().HasComponent(componentType)
func (e *Entry) HasComponent(c component.IComponentType) bool {
return e.Archetype().Layout().HasComponent(c)
}

func (e *Entry) String() string {
Expand Down
16 changes: 13 additions & 3 deletions internal/storage/archetype.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package storage

import (
"github.com/yohamta/donburi/component"
"sync"
)

type ArchetypeIndex int
Expand All @@ -12,6 +13,15 @@ type Archetype struct {
index ArchetypeIndex
entities []Entity
layout *Layout
lock sync.Mutex
}

func (archetype *Archetype) Lock() {
archetype.lock.Lock()
}

func (archetype *Archetype) Unlock() {
archetype.lock.Unlock()
}

// NewArchetype creates a new archetype.
Expand Down Expand Up @@ -39,9 +49,9 @@ func (archetype *Archetype) ComponentTypes() []component.IComponentType {
}

// SwapRemove removes an entity from the archetype and returns it.
func (archetype *Archetype) SwapRemove(entity_index int) Entity {
removed := archetype.entities[entity_index]
archetype.entities[entity_index] = archetype.entities[len(archetype.entities)-1]
func (archetype *Archetype) SwapRemove(entityIndex int) Entity {
removed := archetype.entities[entityIndex]
archetype.entities[entityIndex] = archetype.entities[len(archetype.entities)-1]
archetype.entities = archetype.entities[:len(archetype.entities)-1]
return removed
}
Expand Down
13 changes: 11 additions & 2 deletions internal/storage/entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ type Entity uint64
type EntityId uint32

const idMask Entity = 0xFFFFFFFF00000000
const versionMask Entity = 0xFFFFFFF
const versionMask Entity = 0x0FFFFFF
const readyMask Entity = 0x1000000

// NewEntity creates a new entity.
// The id is a unique identifier for the entity.
Expand All @@ -34,9 +35,17 @@ func (e Entity) Version() uint32 {
return uint32(e & Entity(versionMask))
}

func (e Entity) IsReady() bool {
return e&readyMask != 0
}

func (e Entity) Ready() Entity {
return e | readyMask
}

// IncVersion increments the entity version.
func (e Entity) IncVersion() Entity {
return e&idMask | ((e + 1) & versionMask)
return e&idMask | ((e+1)&versionMask)&^readyMask
}

func (e Entity) String() string {
Expand Down
8 changes: 3 additions & 5 deletions internal/storage/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,8 @@ func (idx *Index) Push(layout *Layout) {
}

// SearchFrom searches for archetypes that match the given filter from the given index.
func (idx *Index) SearchFrom(f filter.LayoutFilter, start int) *ArchetypeIterator {
iterator := &ArchetypeIterator{
current: 0,
}
func (idx *Index) SearchFrom(f filter.LayoutFilter, start int) ArchetypeIterator {
iterator := ArchetypeIterator{current: 0}
iterator.values = []ArchetypeIndex{}
for i := start; i < len(idx.layouts); i++ {
if f.MatchesLayout(idx.layouts[i]) {
Expand All @@ -52,6 +50,6 @@ func (idx *Index) SearchFrom(f filter.LayoutFilter, start int) *ArchetypeIterato
}

// Search searches for archetypes that match the given filter.
func (idx *Index) Search(filter filter.LayoutFilter) *ArchetypeIterator {
func (idx *Index) Search(filter filter.LayoutFilter) ArchetypeIterator {
return idx.SearchFrom(filter, 0)
}
4 changes: 2 additions & 2 deletions internal/storage/iterator.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ func (it *EntityIterator) HasNext() bool {
}

// Next returns the next entity list.
func (it *EntityIterator) Next() []Entity {
func (it *EntityIterator) Next() *Archetype {
archetypeIndex := it.indices[it.current]
it.current++
return it.archetypes[archetypeIndex].Entities()
return it.archetypes[archetypeIndex]
}
26 changes: 13 additions & 13 deletions internal/storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func NewStorage() *Storage {
// PushComponent stores the new data of the component in the archetype.
func (cs *Storage) PushComponent(component component.IComponentType, archetypeIndex ArchetypeIndex) {
if len(cs.storages) <= int(archetypeIndex) {
cs.ensureCapacity(archetypeIndex)
cs.ensureCapacity()
}
if v := cs.storages[archetypeIndex]; v == nil {
cs.storages[archetypeIndex] = []unsafe.Pointer{}
Expand All @@ -46,21 +46,21 @@ func (cs *Storage) SetComponent(archetypeIndex ArchetypeIndex, componentIndex Co
}

// MoveComponent moves the pointer to data of the component in the archetype.
func (cs *Storage) MoveComponent(source ArchetypeIndex, index ComponentIndex, dst ArchetypeIndex) {
if len(cs.storages) <= int(dst) {
cs.ensureCapacity(dst)
func (cs *Storage) MoveComponent(srcIndex ArchetypeIndex, index ComponentIndex, dstIndex ArchetypeIndex) {
if len(cs.storages) <= int(dstIndex) {
cs.ensureCapacity()
}

src_slice := cs.storages[source]
dst_slice := cs.storages[dst]
src := cs.storages[srcIndex]
dst := cs.storages[dstIndex]

value := src_slice[index]
src_slice[index] = src_slice[len(src_slice)-1]
src_slice = src_slice[:len(src_slice)-1]
cs.storages[source] = src_slice
value := src[index]
src[index] = src[len(src)-1]
src = src[:len(src)-1]
cs.storages[srcIndex] = src

dst_slice = append(dst_slice, value)
cs.storages[dst] = dst_slice
dst = append(dst, value)
cs.storages[dstIndex] = dst
}

// SwapRemove removes the pointer to data of the component in the archetype.
Expand All @@ -82,7 +82,7 @@ func (cs *Storage) Contains(archetypeIndex ArchetypeIndex, componentIndex Compon
return cs.storages[archetypeIndex][componentIndex] != nil
}

func (cs *Storage) ensureCapacity(archetypeIndex ArchetypeIndex) {
func (cs *Storage) ensureCapacity() {
newStorages := make([][]unsafe.Pointer, len(cs.storages)*2)
copy(newStorages, cs.storages)
cs.storages = newStorages
Expand Down
45 changes: 22 additions & 23 deletions query.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,33 +17,33 @@ type cache struct {
// So it is not recommended to create a new query every time you want
// to filter entities with the same query.
type Query struct {
layout_matches map[WorldId]*cache
filter filter.LayoutFilter
layoutMatches map[WorldId]*cache
filter filter.LayoutFilter
}

// NewQuery creates a new query.
// It receives arbitrary filters that are used to filter entities.
func NewQuery(filter filter.LayoutFilter) *Query {
return &Query{
layout_matches: make(map[WorldId]*cache),
filter: filter,
layoutMatches: make(map[WorldId]*cache),
filter: filter,
}
}

// Each iterates over all entities that match the query.
func (q *Query) Each(w World, callback func(*Entry)) {
accessor := w.StorageAccessor()
result := q.evaluateQuery(w, &accessor)
iter := storage.NewEntityIterator(0, accessor.Archetypes, result)
f := func(entity storage.Entity) {
entry := w.Entry(entity)
callback(entry)
}
iter := storage.NewEntityIterator(0, accessor.Archetypes, q.evaluateQuery(w, &accessor))
for iter.HasNext() {
entities := iter.Next()
for _, entity := range entities {
f(entity)
archetype := iter.Next()
archetype.Lock()
for _, entity := range archetype.Entities() {
entry := w.Entry(entity)
if entry.entity.IsReady() {
callback(entry)
}
}
archetype.Unlock()
}
}

Expand All @@ -55,26 +55,25 @@ func (q *Query) EachEntity(w World, callback func(*Entry)) {
// Count returns the number of entities that match the query.
func (q *Query) Count(w World) int {
accessor := w.StorageAccessor()
result := q.evaluateQuery(w, &accessor)
iter := storage.NewEntityIterator(0, accessor.Archetypes, result)
iter := storage.NewEntityIterator(0, accessor.Archetypes, q.evaluateQuery(w, &accessor))
ret := 0
for iter.HasNext() {
entities := iter.Next()
ret += len(entities)
archetype := iter.Next()
ret += len(archetype.Entities())
}
return ret
}

// First returns the first entity that matches the query.
func (q *Query) First(w World) (entry *Entry, ok bool) {
accessor := w.StorageAccessor()
result := q.evaluateQuery(w, &accessor)
iter := storage.NewEntityIterator(0, accessor.Archetypes, result)
iter := storage.NewEntityIterator(0, accessor.Archetypes, q.evaluateQuery(w, &accessor))
if !iter.HasNext() {
return nil, false
}
for iter.HasNext() {
entities := iter.Next()
archetype := iter.Next()
entities := archetype.Entities()
if len(entities) > 0 {
return w.Entry(entities[0]), true
}
Expand All @@ -89,13 +88,13 @@ func (q *Query) FirstEntity(w World) (entry *Entry, ok bool) {

func (q *Query) evaluateQuery(world World, accessor *StorageAccessor) []storage.ArchetypeIndex {
w := world.Id()
if _, ok := q.layout_matches[w]; !ok {
q.layout_matches[w] = &cache{
if _, ok := q.layoutMatches[w]; !ok {
q.layoutMatches[w] = &cache{
archetypes: make([]storage.ArchetypeIndex, 0),
seen: 0,
}
}
cache := q.layout_matches[w]
cache := q.layoutMatches[w]
for it := accessor.Index.SearchFrom(q.filter, cache.seen); it.HasNext(); {
cache.archetypes = append(cache.archetypes, it.Next())
}
Expand Down
Loading

0 comments on commit ce2bc07

Please sign in to comment.