player and dimension loader with ecs system
This commit is contained in:
58
server/ecs/bitest.go
Normal file
58
server/ecs/bitest.go
Normal file
@ -0,0 +1,58 @@
|
||||
package ecs
|
||||
|
||||
type BitSetLike interface {
|
||||
Set(i Index) (old bool)
|
||||
Unset(i Index) (old bool)
|
||||
Contains(i Index) bool
|
||||
And(other BitSetLike) (result BitSetLike)
|
||||
AndNot(other BitSetLike) (result BitSetLike)
|
||||
Range(f func(eid Index))
|
||||
}
|
||||
|
||||
type BitSet struct {
|
||||
// TODO: this is not a BitSet, I'm just testing
|
||||
values map[Index]struct{}
|
||||
}
|
||||
|
||||
func (b BitSet) Set(i Index) (old bool) {
|
||||
_, old = b.values[i]
|
||||
b.values[i] = struct{}{}
|
||||
return
|
||||
}
|
||||
|
||||
func (b BitSet) Unset(i Index) (old bool) {
|
||||
_, old = b.values[i]
|
||||
delete(b.values, i)
|
||||
return
|
||||
}
|
||||
|
||||
func (b BitSet) Contains(i Index) bool {
|
||||
_, contains := b.values[i]
|
||||
return contains
|
||||
}
|
||||
|
||||
func (b BitSet) And(other BitSetLike) BitSetLike {
|
||||
result := BitSet{values: make(map[Index]struct{})}
|
||||
for i := range b.values {
|
||||
if other.Contains(i) {
|
||||
result.values[i] = struct{}{}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (b BitSet) AndNot(other BitSetLike) BitSetLike {
|
||||
result := BitSet{values: make(map[Index]struct{})}
|
||||
for i := range b.values {
|
||||
if !other.Contains(i) {
|
||||
result.values[i] = struct{}{}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (b BitSet) Range(f func(eid Index)) {
|
||||
for i := range b.values {
|
||||
f(i)
|
||||
}
|
||||
}
|
44
server/ecs/dispatcher.go
Normal file
44
server/ecs/dispatcher.go
Normal file
@ -0,0 +1,44 @@
|
||||
package ecs
|
||||
|
||||
import "sync"
|
||||
|
||||
type Dispatcher struct {
|
||||
waiters map[string][]*sync.WaitGroup
|
||||
tasks []func(w *World, wg *sync.WaitGroup)
|
||||
}
|
||||
|
||||
func NewDispatcher() *Dispatcher {
|
||||
return &Dispatcher{
|
||||
waiters: make(map[string][]*sync.WaitGroup),
|
||||
tasks: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Dispatcher) Add(s System, name string, deps []string) {
|
||||
var start sync.WaitGroup
|
||||
start.Add(len(deps))
|
||||
for _, dep := range deps {
|
||||
if wg, ok := d.waiters[dep]; ok {
|
||||
d.waiters[dep] = append(wg, &start)
|
||||
} else {
|
||||
panic("Unknown deps: " + dep)
|
||||
}
|
||||
}
|
||||
d.tasks = append(d.tasks, func(w *World, done *sync.WaitGroup) {
|
||||
start.Wait()
|
||||
defer done.Done()
|
||||
s.Update(w)
|
||||
for _, wg := range d.waiters[name] {
|
||||
wg.Done()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (d *Dispatcher) Run(w *World) {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(d.tasks))
|
||||
for _, f := range d.tasks {
|
||||
go f(w, &wg)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
34
server/ecs/ecs_test.go
Normal file
34
server/ecs/ecs_test.go
Normal file
@ -0,0 +1,34 @@
|
||||
package ecs
|
||||
|
||||
import "testing"
|
||||
|
||||
func Test_common(t *testing.T) {
|
||||
// W
|
||||
w := NewWorld()
|
||||
// C
|
||||
type pos [2]int
|
||||
type vel [2]int
|
||||
Register[pos, *HashMapStorage[pos]](w)
|
||||
Register[vel, *HashMapStorage[vel]](w)
|
||||
// E
|
||||
e1 := w.CreateEntity(pos{0, 0})
|
||||
w.CreateEntity(vel{1, 2})
|
||||
w.CreateEntity(pos{1, 2}, vel{2, 0})
|
||||
// S
|
||||
s1 := FuncSystem(func(p *pos) {
|
||||
t.Log("system 1", p)
|
||||
})
|
||||
s2 := FuncSystem(func(p *pos, v *vel) {
|
||||
t.Log("system 2", p, v)
|
||||
})
|
||||
s3 := FuncSystem(func(p pos, v *vel) {
|
||||
t.Log("system 2", p, v)
|
||||
})
|
||||
// Run
|
||||
s1.Update(w)
|
||||
s2.Update(w)
|
||||
s3.Update(w)
|
||||
|
||||
w.DeleteEntity(e1)
|
||||
s1.Update(w)
|
||||
}
|
78
server/ecs/storage.go
Normal file
78
server/ecs/storage.go
Normal file
@ -0,0 +1,78 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type Index uint32
|
||||
|
||||
type Storage[T any] interface {
|
||||
Init()
|
||||
GetValue(eid Index) *T
|
||||
SetValue(eid Index, v T)
|
||||
DelValue(eid Index)
|
||||
}
|
||||
|
||||
type HashMapStorage[T any] struct {
|
||||
values map[Index]*T
|
||||
}
|
||||
|
||||
func (h *HashMapStorage[T]) Init() { h.values = make(map[Index]*T) }
|
||||
func (h *HashMapStorage[T]) Len() int { return len(h.values) }
|
||||
func (h *HashMapStorage[T]) GetValue(eid Index) *T { return h.values[eid] }
|
||||
func (h *HashMapStorage[T]) SetValue(eid Index, v T) { h.values[eid] = &v }
|
||||
func (h *HashMapStorage[T]) DelValue(eid Index) { delete(h.values, eid) }
|
||||
func (h *HashMapStorage[T]) Range(f func(eid Index, value *T)) {
|
||||
for i, v := range h.values {
|
||||
f(i, v)
|
||||
}
|
||||
}
|
||||
|
||||
type NullStorage[T any] struct{}
|
||||
|
||||
func (NullStorage[T]) Init() {
|
||||
var v T
|
||||
if size := unsafe.Sizeof(v); size != 0 {
|
||||
typeName := reflect.TypeOf(v).String()
|
||||
typeSize := strconv.Itoa(int(size))
|
||||
panic("NullStorage can only be used with ZST, " + typeName + " has size of " + typeSize)
|
||||
}
|
||||
}
|
||||
func (NullStorage[T]) GetValue(eid Index) *T { return nil }
|
||||
func (NullStorage[T]) SetValue(eid Index, v T) {}
|
||||
func (NullStorage[T]) DelValue(eid Index) {}
|
||||
|
||||
type MaskedStorage[T any] struct {
|
||||
BitSetLike
|
||||
Storage[T]
|
||||
Len int
|
||||
}
|
||||
|
||||
func (m *MaskedStorage[T]) Init() {
|
||||
if m.BitSetLike == nil {
|
||||
m.BitSetLike = BitSet{make(map[Index]struct{})}
|
||||
}
|
||||
m.Storage.Init()
|
||||
}
|
||||
func (m *MaskedStorage[T]) GetValue(eid Index) *T {
|
||||
if m.Contains(eid) {
|
||||
return m.Storage.GetValue(eid)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (m *MaskedStorage[T]) GetValueAny(eid Index) any { return m.GetValue(eid) }
|
||||
func (m *MaskedStorage[T]) SetValue(eid Index, v T) {
|
||||
if !m.BitSetLike.Set(eid) {
|
||||
m.Len++
|
||||
}
|
||||
m.Storage.SetValue(eid, v)
|
||||
}
|
||||
func (m *MaskedStorage[T]) SetAny(eid Index, v any) { m.SetValue(eid, v.(T)) }
|
||||
func (m *MaskedStorage[T]) DelValue(eid Index) {
|
||||
if m.BitSetLike.Unset(eid) {
|
||||
m.Len--
|
||||
}
|
||||
m.Storage.DelValue(eid)
|
||||
}
|
66
server/ecs/system.go
Normal file
66
server/ecs/system.go
Normal file
@ -0,0 +1,66 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type System interface {
|
||||
Update(w *World)
|
||||
}
|
||||
|
||||
type funcsystem struct {
|
||||
update func(w *World)
|
||||
}
|
||||
|
||||
func FuncSystem(F any) System {
|
||||
type Storage interface {
|
||||
BitSetLike
|
||||
GetValueAny(eid Index) any
|
||||
}
|
||||
f := reflect.ValueOf(F)
|
||||
in := f.Type().NumIn()
|
||||
argTypes := make([]reflect.Type, in)
|
||||
needCopy := make([]bool, in)
|
||||
for i := 0; i < in; i++ {
|
||||
if t := f.Type().In(i); t.Kind() == reflect.Pointer {
|
||||
argTypes[i] = t.Elem()
|
||||
} else {
|
||||
argTypes[i] = t
|
||||
needCopy[i] = true
|
||||
}
|
||||
}
|
||||
return &funcsystem{
|
||||
update: func(w *World) {
|
||||
storages := make([]Storage, in)
|
||||
for i := 0; i < in; i++ {
|
||||
storages[i] = w.GetResourceRaw(argTypes[i]).(Storage)
|
||||
}
|
||||
args := make([]reflect.Value, len(storages))
|
||||
if len(storages) > 0 {
|
||||
set := BitSetLike(storages[0])
|
||||
for _, v := range storages[1:] {
|
||||
set = set.And(v)
|
||||
}
|
||||
set.Range(func(eid Index) {
|
||||
for i := range args {
|
||||
arg := storages[i].GetValueAny(eid)
|
||||
if arg == nil {
|
||||
args[i] = reflect.Zero(argTypes[i])
|
||||
} else if needCopy[i] {
|
||||
args[i] = reflect.ValueOf(arg).Elem()
|
||||
} else {
|
||||
args[i] = reflect.ValueOf(arg)
|
||||
}
|
||||
}
|
||||
f.Call(args)
|
||||
})
|
||||
} else {
|
||||
f.Call(args)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (f *funcsystem) Update(w *World) {
|
||||
f.update(w)
|
||||
}
|
13
server/ecs/system_test.go
Normal file
13
server/ecs/system_test.go
Normal file
@ -0,0 +1,13 @@
|
||||
package ecs
|
||||
|
||||
type PositionComponent struct {
|
||||
X, Y int
|
||||
}
|
||||
|
||||
type MySystem1 struct {
|
||||
*PositionComponent
|
||||
}
|
||||
|
||||
func (s *MySystem1) Update(w *World) {
|
||||
|
||||
}
|
90
server/ecs/world.go
Normal file
90
server/ecs/world.go
Normal file
@ -0,0 +1,90 @@
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type World struct {
|
||||
resources map[reflect.Type]any
|
||||
maxEID Index
|
||||
}
|
||||
|
||||
func NewWorld() *World {
|
||||
return &World{resources: make(map[reflect.Type]any)}
|
||||
}
|
||||
|
||||
func SetResource[Res any](w *World, v Res) *Res {
|
||||
w.resources[reflect.TypeOf(v)] = &v
|
||||
return &v
|
||||
}
|
||||
|
||||
func (w *World) Remove(resource any) any {
|
||||
t := reflect.ValueOf(resource).Type()
|
||||
resource = w.resources[t]
|
||||
delete(w.resources, t)
|
||||
return resource
|
||||
}
|
||||
|
||||
func GetResource[Res any](w *World) *Res {
|
||||
var res Res
|
||||
t := reflect.TypeOf(res)
|
||||
if v, ok := w.resources[t]; ok {
|
||||
return v.(*Res)
|
||||
}
|
||||
panic("Resource " + t.Name() + " not found")
|
||||
}
|
||||
|
||||
func (w *World) GetResourceRaw(t reflect.Type) any {
|
||||
v, _ := w.resources[t]
|
||||
return v
|
||||
}
|
||||
|
||||
func GetComponent[T any](w *World) *MaskedStorage[T] {
|
||||
var value T
|
||||
t := reflect.ValueOf(value).Type()
|
||||
if res, ok := w.resources[t]; ok {
|
||||
return res.(*MaskedStorage[T])
|
||||
}
|
||||
panic("Component " + t.Name() + " not found")
|
||||
}
|
||||
|
||||
// Register the component with the storage.
|
||||
//
|
||||
// Will be changed to func (w *World) Register[C Component]() after Go support it
|
||||
func Register[T any, S Storage[T]](w *World) {
|
||||
var value T
|
||||
t := reflect.TypeOf(value)
|
||||
if _, ok := w.resources[t]; ok {
|
||||
panic("Component " + t.Name() + " already exist")
|
||||
}
|
||||
var storage S
|
||||
var storageInt Storage[T]
|
||||
storageType := reflect.TypeOf(storage)
|
||||
if storageType.Kind() == reflect.Pointer {
|
||||
storageInt = reflect.New(storageType.Elem()).Interface().(Storage[T])
|
||||
} else {
|
||||
storageInt = storage
|
||||
}
|
||||
ms := MaskedStorage[T]{Storage: storageInt}
|
||||
ms.Init()
|
||||
w.resources[t] = &ms
|
||||
}
|
||||
|
||||
func (w *World) CreateEntity(components ...any) (i Index) {
|
||||
type Storage interface{ SetAny(Index, any) }
|
||||
eid := Index(atomic.AddUint32((*uint32)(&w.maxEID), 1))
|
||||
for _, c := range components {
|
||||
w.resources[reflect.TypeOf(c)].(Storage).SetAny(eid, c)
|
||||
}
|
||||
return eid
|
||||
}
|
||||
|
||||
func (w *World) DeleteEntity(eid Index) {
|
||||
type Storage interface{ Del(eid Index) }
|
||||
for _, r := range w.resources {
|
||||
if c, ok := r.(Storage); ok {
|
||||
c.Del(eid)
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user