other: ecs 基本实现
This commit is contained in:
parent
cc3573b792
commit
dff6faa834
|
@ -0,0 +1,8 @@
|
||||||
|
// Package buffer 提供了缓冲区相关的实用程序。
|
||||||
|
//
|
||||||
|
// 包括创建、读取和写入缓冲区的函数。
|
||||||
|
//
|
||||||
|
// 这个包还提供了一个无界缓冲区的实现,可以在不使用额外 goroutine 的情况下实现无界缓冲区。
|
||||||
|
//
|
||||||
|
// 无界缓冲区的所有方法都是线程安全的,除了用于同步的互斥锁外,不会阻塞任何东西。
|
||||||
|
package buffer
|
|
@ -0,0 +1,7 @@
|
||||||
|
package buffer
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrBufferIsEmpty = errors.New("buffer is empty")
|
||||||
|
)
|
|
@ -0,0 +1,148 @@
|
||||||
|
package buffer
|
||||||
|
|
||||||
|
// NewRing 创建一个并发不安全的环形缓冲区
|
||||||
|
// - initSize: 初始容量
|
||||||
|
//
|
||||||
|
// 当初始容量小于 2 或未设置时,将会使用默认容量 2
|
||||||
|
func NewRing[T any](initSize ...int) *Ring[T] {
|
||||||
|
if len(initSize) == 0 {
|
||||||
|
initSize = append(initSize, 2)
|
||||||
|
}
|
||||||
|
if initSize[0] < 2 {
|
||||||
|
initSize[0] = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Ring[T]{
|
||||||
|
buf: make([]T, initSize[0]),
|
||||||
|
initSize: initSize[0],
|
||||||
|
size: initSize[0],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ring 环形缓冲区
|
||||||
|
type Ring[T any] struct {
|
||||||
|
buf []T
|
||||||
|
initSize int
|
||||||
|
size int
|
||||||
|
r int
|
||||||
|
w int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read 读取数据
|
||||||
|
func (b *Ring[T]) Read() (T, error) {
|
||||||
|
var t T
|
||||||
|
if b.r == b.w {
|
||||||
|
return t, ErrBufferIsEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
v := b.buf[b.r]
|
||||||
|
b.r++
|
||||||
|
if b.r == b.size {
|
||||||
|
b.r = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadAll 读取所有数据
|
||||||
|
func (b *Ring[T]) ReadAll() []T {
|
||||||
|
if b.r == b.w {
|
||||||
|
return nil // 没有数据时返回空切片
|
||||||
|
}
|
||||||
|
|
||||||
|
var length int
|
||||||
|
var data []T
|
||||||
|
|
||||||
|
if b.w > b.r {
|
||||||
|
length = b.w - b.r
|
||||||
|
} else {
|
||||||
|
length = len(b.buf) - b.r + b.w
|
||||||
|
}
|
||||||
|
data = make([]T, length) // 预分配空间
|
||||||
|
|
||||||
|
if b.w > b.r {
|
||||||
|
copy(data, b.buf[b.r:b.w])
|
||||||
|
} else {
|
||||||
|
copied := copy(data, b.buf[b.r:])
|
||||||
|
copy(data[copied:], b.buf[:b.w])
|
||||||
|
}
|
||||||
|
|
||||||
|
b.r = 0
|
||||||
|
b.w = 0
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
// Peek 查看数据
|
||||||
|
func (b *Ring[T]) Peek() (t T, err error) {
|
||||||
|
if b.r == b.w {
|
||||||
|
return t, ErrBufferIsEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.buf[b.r], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write 写入数据
|
||||||
|
func (b *Ring[T]) Write(v T) {
|
||||||
|
b.buf[b.w] = v
|
||||||
|
b.w++
|
||||||
|
|
||||||
|
if b.w == b.size {
|
||||||
|
b.w = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.w == b.r {
|
||||||
|
b.grow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// grow 扩容
|
||||||
|
func (b *Ring[T]) grow() {
|
||||||
|
var size int
|
||||||
|
if b.size < 1024 {
|
||||||
|
size = b.size * 2
|
||||||
|
} else {
|
||||||
|
size = b.size + b.size/4
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]T, size)
|
||||||
|
|
||||||
|
copy(buf[0:], b.buf[b.r:])
|
||||||
|
copy(buf[b.size-b.r:], b.buf[0:b.r])
|
||||||
|
|
||||||
|
b.r = 0
|
||||||
|
b.w = b.size
|
||||||
|
b.size = size
|
||||||
|
b.buf = buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty 是否为空
|
||||||
|
func (b *Ring[T]) IsEmpty() bool {
|
||||||
|
return b.r == b.w
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cap 返回缓冲区容量
|
||||||
|
func (b *Ring[T]) Cap() int {
|
||||||
|
return b.size
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len 返回缓冲区长度
|
||||||
|
func (b *Ring[T]) Len() int {
|
||||||
|
if b.r == b.w {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.w > b.r {
|
||||||
|
return b.w - b.r
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.size - b.r + b.w
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset 重置缓冲区
|
||||||
|
func (b *Ring[T]) Reset() {
|
||||||
|
b.r = 0
|
||||||
|
b.w = 0
|
||||||
|
b.size = b.initSize
|
||||||
|
b.buf = make([]T, b.initSize)
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package buffer_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/utils/buffer"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkRing_Write(b *testing.B) {
|
||||||
|
ring := buffer.NewRing[int](1024)
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
ring.Write(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkRing_Read(b *testing.B) {
|
||||||
|
ring := buffer.NewRing[int](1024)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
ring.Write(i)
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, _ = ring.Read()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package buffer_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/utils/buffer"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewRing(t *testing.T) {
|
||||||
|
ring := buffer.NewRing[int]()
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
ring.Write(i)
|
||||||
|
t.Log(ring.Read())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
package buffer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewRingUnbounded 创建一个并发安全的基于环形缓冲区实现的无界缓冲区
|
||||||
|
func NewRingUnbounded[T any](bufferSize int) *RingUnbounded[T] {
|
||||||
|
ru := &RingUnbounded[T]{
|
||||||
|
ring: NewRing[T](1024),
|
||||||
|
rc: make(chan T, bufferSize),
|
||||||
|
closedSignal: make(chan struct{}),
|
||||||
|
}
|
||||||
|
ru.cond = sync.NewCond(&ru.rrm)
|
||||||
|
|
||||||
|
ru.process()
|
||||||
|
return ru
|
||||||
|
}
|
||||||
|
|
||||||
|
// RingUnbounded 基于环形缓冲区实现的无界缓冲区
|
||||||
|
type RingUnbounded[T any] struct {
|
||||||
|
ring *Ring[T]
|
||||||
|
rrm sync.Mutex
|
||||||
|
cond *sync.Cond
|
||||||
|
rc chan T
|
||||||
|
closed bool
|
||||||
|
closedMutex sync.RWMutex
|
||||||
|
closedSignal chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write 写入数据
|
||||||
|
func (b *RingUnbounded[T]) Write(v T) {
|
||||||
|
b.closedMutex.RLock()
|
||||||
|
defer b.closedMutex.RUnlock()
|
||||||
|
if b.closed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b.rrm.Lock()
|
||||||
|
b.ring.Write(v)
|
||||||
|
b.cond.Signal()
|
||||||
|
b.rrm.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read 读取数据
|
||||||
|
func (b *RingUnbounded[T]) Read() <-chan T {
|
||||||
|
return b.rc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Closed 判断缓冲区是否已关闭
|
||||||
|
func (b *RingUnbounded[T]) Closed() bool {
|
||||||
|
b.closedMutex.RLock()
|
||||||
|
defer b.closedMutex.RUnlock()
|
||||||
|
return b.closed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close 关闭缓冲区,关闭后将不再接收新数据,但是已有数据仍然可以读取
|
||||||
|
func (b *RingUnbounded[T]) Close() <-chan struct{} {
|
||||||
|
b.closedMutex.Lock()
|
||||||
|
defer b.closedMutex.Unlock()
|
||||||
|
if b.closed {
|
||||||
|
return b.closedSignal
|
||||||
|
}
|
||||||
|
b.closed = true
|
||||||
|
|
||||||
|
b.rrm.Lock()
|
||||||
|
b.cond.Signal()
|
||||||
|
b.rrm.Unlock()
|
||||||
|
return b.closedSignal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *RingUnbounded[T]) process() {
|
||||||
|
go func(b *RingUnbounded[T]) {
|
||||||
|
for {
|
||||||
|
b.closedMutex.RLock()
|
||||||
|
b.rrm.Lock()
|
||||||
|
vs := b.ring.ReadAll()
|
||||||
|
if len(vs) == 0 && !b.closed {
|
||||||
|
b.closedMutex.RUnlock()
|
||||||
|
b.cond.Wait()
|
||||||
|
} else {
|
||||||
|
b.closedMutex.RUnlock()
|
||||||
|
}
|
||||||
|
b.rrm.Unlock()
|
||||||
|
|
||||||
|
b.closedMutex.RLock()
|
||||||
|
if b.closed && len(vs) == 0 {
|
||||||
|
close(b.rc)
|
||||||
|
close(b.closedSignal)
|
||||||
|
b.closedMutex.RUnlock()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range vs {
|
||||||
|
b.rc <- v
|
||||||
|
}
|
||||||
|
b.closedMutex.RUnlock()
|
||||||
|
}
|
||||||
|
}(b)
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package buffer_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/utils/buffer"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkRingUnbounded_Write(b *testing.B) {
|
||||||
|
ring := buffer.NewRingUnbounded[int](1024 * 16)
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
ring.Write(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkRingUnbounded_Read(b *testing.B) {
|
||||||
|
ring := buffer.NewRingUnbounded[int](1024 * 16)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
ring.Write(i)
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
<-ring.Read()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkRingUnbounded_Write_Parallel(b *testing.B) {
|
||||||
|
ring := buffer.NewRingUnbounded[int](1024 * 16)
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
ring.Write(1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkRingUnbounded_Read_Parallel(b *testing.B) {
|
||||||
|
ring := buffer.NewRingUnbounded[int](1024 * 16)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
ring.Write(i)
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
<-ring.Read()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package buffer_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/utils/buffer"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRingUnbounded_Write2Read(t *testing.T) {
|
||||||
|
ring := buffer.NewRingUnbounded[int](1024 * 16)
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
ring.Write(i)
|
||||||
|
}
|
||||||
|
t.Log("write done")
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
t.Log(<-ring.Read())
|
||||||
|
}
|
||||||
|
t.Log("read done")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRingUnbounded_Close(t *testing.T) {
|
||||||
|
ring := buffer.NewRingUnbounded[int](1024 * 16)
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
ring.Write(i)
|
||||||
|
}
|
||||||
|
t.Log("write done")
|
||||||
|
ring.Close()
|
||||||
|
t.Log("close done")
|
||||||
|
for v := range ring.Read() {
|
||||||
|
ring.Write(v)
|
||||||
|
t.Log(v)
|
||||||
|
}
|
||||||
|
t.Log("read done")
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
package buffer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewUnbounded 创建一个无界缓冲区
|
||||||
|
// - generateNil: 生成空值的函数,该函数仅需始终返回 nil 即可
|
||||||
|
//
|
||||||
|
// 该缓冲区来源于 gRPC 的实现,用于在不使用额外 goroutine 的情况下实现无界缓冲区
|
||||||
|
// - 该缓冲区的所有方法都是线程安全的,除了用于同步的互斥锁外,不会阻塞任何东西
|
||||||
|
func NewUnbounded[V any]() *Unbounded[V] {
|
||||||
|
return &Unbounded[V]{c: make(chan V, 1)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unbounded 是无界缓冲区的实现
|
||||||
|
type Unbounded[V any] struct {
|
||||||
|
c chan V
|
||||||
|
closed bool
|
||||||
|
mu sync.Mutex
|
||||||
|
backlog []V
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put 将数据放入缓冲区
|
||||||
|
func (slf *Unbounded[V]) Put(t V) {
|
||||||
|
slf.mu.Lock()
|
||||||
|
defer slf.mu.Unlock()
|
||||||
|
if slf.closed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(slf.backlog) == 0 {
|
||||||
|
select {
|
||||||
|
case slf.c <- t:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slf.backlog = append(slf.backlog, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load 将缓冲区中的数据发送到读取通道中,如果缓冲区中没有数据,则不会发送
|
||||||
|
// - 在每次 Get 后都应该执行该函数
|
||||||
|
func (slf *Unbounded[V]) Load() {
|
||||||
|
slf.mu.Lock()
|
||||||
|
defer slf.mu.Unlock()
|
||||||
|
if slf.closed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(slf.backlog) > 0 {
|
||||||
|
select {
|
||||||
|
case slf.c <- slf.backlog[0]:
|
||||||
|
slf.backlog = slf.backlog[1:]
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get 获取读取通道
|
||||||
|
func (slf *Unbounded[V]) Get() <-chan V {
|
||||||
|
return slf.c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close 关闭
|
||||||
|
func (slf *Unbounded[V]) Close() {
|
||||||
|
slf.mu.Lock()
|
||||||
|
defer slf.mu.Unlock()
|
||||||
|
if slf.closed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
slf.closed = true
|
||||||
|
close(slf.c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsClosed 是否已关闭
|
||||||
|
func (slf *Unbounded[V]) IsClosed() bool {
|
||||||
|
slf.mu.Lock()
|
||||||
|
defer slf.mu.Unlock()
|
||||||
|
return slf.closed
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package buffer_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/utils/buffer"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkUnbounded_Write(b *testing.B) {
|
||||||
|
u := buffer.NewUnbounded[int]()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
u.Put(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnbounded_Read(b *testing.B) {
|
||||||
|
u := buffer.NewUnbounded[int]()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
u.Put(i)
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
<-u.Get()
|
||||||
|
u.Load()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnbounded_Write_Parallel(b *testing.B) {
|
||||||
|
u := buffer.NewUnbounded[int]()
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
u.Put(1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnbounded_Read_Parallel(b *testing.B) {
|
||||||
|
u := buffer.NewUnbounded[int]()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
u.Put(i)
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
<-u.Get()
|
||||||
|
u.Load()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package buffer_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/kercylan98/minotaur/utils/buffer"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUnbounded_Get(t *testing.T) {
|
||||||
|
ub := buffer.NewUnbounded[int]()
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
ub.Put(i + 1)
|
||||||
|
fmt.Println(<-ub.Get())
|
||||||
|
//<-ub.Get()
|
||||||
|
ub.Load()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package ecs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/utils/super"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newArchetype(world *World, mask *super.BitSet[ComponentId]) *archetype {
|
||||||
|
arch := &archetype{
|
||||||
|
world: world,
|
||||||
|
mask: mask,
|
||||||
|
entityData: make(map[ComponentId][]reflect.Value),
|
||||||
|
}
|
||||||
|
|
||||||
|
return arch
|
||||||
|
}
|
||||||
|
|
||||||
|
// archetype 原型是一种实体的集合,它们都包含了相同的组件
|
||||||
|
type archetype struct {
|
||||||
|
world *World
|
||||||
|
mask *super.BitSet[ComponentId]
|
||||||
|
entities []Entity
|
||||||
|
entityData map[ComponentId][]reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *archetype) addEntity(entity Entity) Entity {
|
||||||
|
entity.setArchetypeIndex(len(a.entities))
|
||||||
|
a.entities = append(a.entities, entity)
|
||||||
|
for _, componentId := range a.mask.Bits() {
|
||||||
|
t := a.world.getComponentTypeById(componentId)
|
||||||
|
if t == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
v := reflect.New(t)
|
||||||
|
a.entityData[componentId] = append(a.entityData[componentId], v)
|
||||||
|
}
|
||||||
|
return entity
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *archetype) removeEntity(entity Entity) {
|
||||||
|
idx := entity.GetArchetypeIndex()
|
||||||
|
for componentId, values := range a.entityData {
|
||||||
|
a.entityData[componentId] = append(values[:idx], values[idx+1:]...)
|
||||||
|
}
|
||||||
|
a.entities = append(a.entities[:idx], a.entities[idx+1:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *archetype) getEntityComponentData(entity Entity, componentId ComponentId) reflect.Value {
|
||||||
|
return a.entityData[componentId][entity.GetArchetypeIndex()]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *archetype) getEntityData(entity Entity) []reflect.Value {
|
||||||
|
var idx = entity.GetArchetypeIndex()
|
||||||
|
var data []reflect.Value
|
||||||
|
for _, componentId := range a.mask.Bits() {
|
||||||
|
data = append(data, a.entityData[componentId][idx])
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package ecs
|
||||||
|
|
||||||
|
type ComponentId = int
|
||||||
|
|
||||||
|
// Component 组件是一个数据结构,它包含了一些数据
|
||||||
|
type Component struct {
|
||||||
|
id ComponentId
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package ecs
|
||||||
|
|
||||||
|
type EntityId = int
|
||||||
|
|
||||||
|
// Entity 仅包含一个实体的唯一标识
|
||||||
|
type Entity struct {
|
||||||
|
id EntityId // 实体的唯一标识
|
||||||
|
archIdx int // 实体所在的原型索引
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Entity) GetId() EntityId {
|
||||||
|
return e.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Entity) GetArchetypeIndex() int {
|
||||||
|
return e.archIdx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Entity) setArchetypeIndex(idx int) {
|
||||||
|
e.archIdx = idx
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package ecs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/utils/super"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
func QueryEntity[C any](world *World, entity Entity) *C {
|
||||||
|
t := reflect.TypeOf((*C)(nil)).Elem()
|
||||||
|
id, exist := world.components[t]
|
||||||
|
if !exist {
|
||||||
|
var ids []ComponentId
|
||||||
|
for i := range t.NumField() {
|
||||||
|
ids = append(ids, world.ComponentId(t.Field(i).Type.Elem()))
|
||||||
|
}
|
||||||
|
mask := super.NewBitSet(ids...)
|
||||||
|
for _, arch := range world.archetypes {
|
||||||
|
if !arch.mask.ContainsAll(mask) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
values := arch.getEntityData(entity)
|
||||||
|
fields := make(map[reflect.Type]reflect.Value)
|
||||||
|
for _, value := range values {
|
||||||
|
fields[value.Type()] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
result := reflect.New(t)
|
||||||
|
for i := range t.NumField() {
|
||||||
|
f := result.Elem().Field(i)
|
||||||
|
f.Set(fields[f.Type()])
|
||||||
|
}
|
||||||
|
return result.Interface().(*C)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, arch := range world.archetypes {
|
||||||
|
if !arch.mask.Has(id) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range arch.entities {
|
||||||
|
if e == entity {
|
||||||
|
return arch.getEntityComponentData(entity, id).Interface().(*C)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func QueryEntitiesByComponentId[T any](world *World, id ComponentId) []*T {
|
||||||
|
t := reflect.TypeOf((*T)(nil)).Elem()
|
||||||
|
if world.components[t] != id {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var cs []*T
|
||||||
|
for _, arch := range world.archetypes {
|
||||||
|
if arch.mask.Has(id) {
|
||||||
|
for _, entity := range arch.entities {
|
||||||
|
arch.getEntityComponentData(entity, id)
|
||||||
|
cs = append(cs, arch.getEntityComponentData(entity, id).Interface().(*T))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cs
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
package ecs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/utils/super"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewWorld 创建一个新的世界
|
||||||
|
func NewWorld() World {
|
||||||
|
return World{
|
||||||
|
components: make(map[reflect.Type]int),
|
||||||
|
componentTypes: make(map[int]reflect.Type),
|
||||||
|
archetypes: make(map[*super.BitSet[int]]*archetype),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type World struct {
|
||||||
|
componentIds []ComponentId // 已经注册的组件 Id 清单
|
||||||
|
components map[reflect.Type]ComponentId // 已经注册的组件清单
|
||||||
|
componentTypes map[ComponentId]reflect.Type // 已经注册的组件类型清单
|
||||||
|
|
||||||
|
archetypes map[*super.BitSet[ComponentId]]*archetype // 已经注册的原型清单
|
||||||
|
entityGuid EntityId // 实体的唯一标识当前值
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateEntity 创建一个新的实体
|
||||||
|
func (w *World) CreateEntity(componentId ComponentId, componentIds ...ComponentId) Entity {
|
||||||
|
mask := super.NewBitSet(append([]ComponentId{componentId}, componentIds...)...)
|
||||||
|
|
||||||
|
var arch *archetype
|
||||||
|
for existingMask, existingArch := range w.archetypes {
|
||||||
|
if existingMask.Equal(mask) {
|
||||||
|
arch = existingArch
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if arch == nil {
|
||||||
|
arch = newArchetype(w, mask)
|
||||||
|
w.archetypes[mask] = arch
|
||||||
|
}
|
||||||
|
|
||||||
|
return arch.addEntity(Entity{
|
||||||
|
id: w.entityGuid,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ComponentId 返回一个组件的 Id,如果组件未注册,则注册它
|
||||||
|
func (w *World) ComponentId(t reflect.Type) ComponentId {
|
||||||
|
id, exist := w.components[t]
|
||||||
|
if !exist {
|
||||||
|
id = len(w.components)
|
||||||
|
w.components[t] = id
|
||||||
|
w.componentTypes[id] = t
|
||||||
|
w.componentIds = append(w.componentIds, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
// getComponentTypeById 通过 Id 获取一个组件的类型
|
||||||
|
func (w *World) getComponentTypeById(id ComponentId) reflect.Type {
|
||||||
|
if id < 0 || id >= len(w.componentIds) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return w.componentTypes[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
// unregisterComponentById 通过 Id 注销一个组件
|
||||||
|
func (w *World) unregisterComponentById(id ComponentId) {
|
||||||
|
t := w.componentTypes[id]
|
||||||
|
delete(w.components, t)
|
||||||
|
delete(w.componentTypes, id)
|
||||||
|
w.componentIds = append(w.componentIds[:id], w.componentIds[id+1:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// unregisterComponentByType 通过类型注销一个组件
|
||||||
|
func (w *World) unregisterComponentByType(t reflect.Type) {
|
||||||
|
id, exist := w.components[t]
|
||||||
|
if !exist {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.unregisterComponentById(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// nextEntityGuid 返回下一个实体的唯一标识
|
||||||
|
func (w *World) nextEntityGuid() EntityId {
|
||||||
|
guid := w.entityGuid
|
||||||
|
w.entityGuid++
|
||||||
|
return guid
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package ecs_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/toolkit/ecs"
|
||||||
|
"github.com/kercylan98/minotaur/utils/super"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NameComponent struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type AgeComponent struct {
|
||||||
|
Age int
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWorld_ComponentId(t *testing.T) {
|
||||||
|
w := ecs.NewWorld()
|
||||||
|
|
||||||
|
nameComponent := w.ComponentId(reflect.TypeOf(NameComponent{}))
|
||||||
|
ageComponent := w.ComponentId(reflect.TypeOf(AgeComponent{}))
|
||||||
|
|
||||||
|
ea := w.CreateEntity(nameComponent, ageComponent)
|
||||||
|
eb := w.CreateEntity(nameComponent, ageComponent)
|
||||||
|
|
||||||
|
ecs.QueryEntity[NameComponent](&w, ea).Name = "Alice"
|
||||||
|
ecs.QueryEntity[NameComponent](&w, eb).Name = "Bob"
|
||||||
|
|
||||||
|
ecs.QueryEntity[AgeComponent](&w, ea).Age = 20
|
||||||
|
ecs.QueryEntity[AgeComponent](&w, eb).Age = 30
|
||||||
|
|
||||||
|
t.Log(string(super.MarshalJSON(ecs.QueryEntity[NameComponent](&w, ea))))
|
||||||
|
t.Log(string(super.MarshalJSON(ecs.QueryEntity[NameComponent](&w, eb))))
|
||||||
|
t.Log(string(super.MarshalJSON(ecs.QueryEntity[AgeComponent](&w, ea))))
|
||||||
|
t.Log(string(super.MarshalJSON(ecs.QueryEntity[AgeComponent](&w, eb))))
|
||||||
|
|
||||||
|
merge := ecs.QueryEntity[struct {
|
||||||
|
*NameComponent
|
||||||
|
*AgeComponent
|
||||||
|
}](&w, ea)
|
||||||
|
|
||||||
|
t.Log(string(super.MarshalJSON(merge)))
|
||||||
|
}
|
Loading…
Reference in New Issue