simple ecs system

This commit is contained in:
Tnze
2022-05-23 10:04:01 +08:00
parent a5dd70d1ea
commit d2f7db9d0d
9 changed files with 239 additions and 110 deletions

View File

@ -1,102 +0,0 @@
package dimension
import (
"container/list"
"github.com/Tnze/go-mc/data/packetid"
"github.com/Tnze/go-mc/level"
pk "github.com/Tnze/go-mc/net/packet"
"github.com/Tnze/go-mc/server"
"github.com/Tnze/go-mc/server/clientinfo"
"github.com/Tnze/go-mc/server/internal/bvh"
)
type vec2d = bvh.Vec2[float64]
type sphere2d = bvh.Sphere[float64, vec2d]
type manager struct {
storage
activeChunks *list.List
chunkElement map[level.ChunkPos]*list.Element
players bvh.Tree[float64, sphere2d, *server.Player]
clients *clientinfo.ClientInformation
}
type chunkHandler struct {
level.ChunkPos
*level.Chunk
// records all players loaded this chunk
players map[*server.Player]struct{}
}
func (m *manager) refresh() error {
all := func(b sphere2d) bool { return true }
m.players.Find(all, func(n *bvh.Node[float64, sphere2d, *server.Player]) bool {
p := n.Value
v := m.clients.Players[p.UUID].ViewDistance
for i := 1 - v; i < v; i++ {
for j := 1 - v; j < v; j++ {
pos := level.ChunkPos{
X: int(n.Box.Center[0]) >> 4,
Z: int(n.Box.Center[1]) >> 4,
}
point := vec2d{float64(pos.X + i), float64(pos.Z + j)}
if _, exist := m.chunkElement[pos]; !exist && n.Box.WithIn(point) {
c, err := m.GetChunk(pos)
if err != nil {
return false
}
m.chunkElement[pos] = m.activeChunks.PushBack(c)
}
}
}
return true
})
for e := m.activeChunks.Front(); e != nil; {
ch := e.Value.(*chunkHandler)
point := vec2d{float64(ch.X), float64(ch.Z)}
filter := bvh.TouchPoint[vec2d, sphere2d](point)
newPlayers := make(map[*server.Player]struct{})
m.players.Find(filter, func(n *bvh.Node[float64, sphere2d, *server.Player]) bool {
p := n.Value
if _, ok := ch.players[p]; ok {
delete(ch.players, p)
} else {
playerLoadChunk(p, ch)
}
newPlayers[p] = struct{}{}
return true
})
for p := range ch.players {
playerUnloadChunk(p, ch)
}
if len(newPlayers) > 0 {
ch.players = newPlayers
e = e.Next()
} else {
// no player around this chunk, unload it
if err := m.PutChunk(ch.ChunkPos, ch.Chunk); err != nil {
return err
}
next := e.Next()
m.activeChunks.Remove(e)
delete(m.chunkElement, ch.ChunkPos)
e = next
}
}
return nil
}
func playerLoadChunk(p *server.Player, c *chunkHandler) {
p.WritePacket(server.Packet758(pk.Marshal(
packetid.ClientboundLevelChunkWithLight,
c.ChunkPos, c.Chunk,
)))
}
func playerUnloadChunk(p *server.Player, c *chunkHandler) {
p.WritePacket(server.Packet758(pk.Marshal(
packetid.ClientboundForgetLevelChunk,
c.ChunkPos,
)))
}

View File

@ -1,7 +0,0 @@
package dimension
import "testing"
func Test_manager(t *testing.T) {
}

32
server/dimension/world.go Normal file
View File

@ -0,0 +1,32 @@
package dimension
import (
"github.com/Tnze/go-mc/server"
)
type World struct {
}
func (w *World) Init(g *server.Game) {
//TODO implement me
panic("implement me")
}
func (w *World) Info() server.LevelInfo {
//TODO implement me
panic("implement me")
}
func (w *World) PlayerJoin(p *server.Player) {
//TODO implement me
panic("implement me")
}
func (w *World) PlayerQuit(p *server.Player) {
//TODO implement me
panic("implement me")
}
func (w *World) register() {
}

View File

@ -1 +0,0 @@
package server

View File

@ -0,0 +1,39 @@
package ecs
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 BitSet) (result BitSet) {
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)
}
}

View File

@ -0,0 +1,27 @@
package ecs
import "testing"
func Test_common(t *testing.T) {
// W
w := NewWorld()
// C
type pos [2]int
type vel [2]int
Register(w, pos{})
Register(w, vel{})
// E
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)
})
// Run
s1.Update(w)
s2.Update(w)
}

View File

@ -0,0 +1,34 @@
package ecs
type Index uint32
type Storage interface {
Get(eid Index) any
Insert(eid Index, v any)
Remove(eid Index) any
BitSet() BitSet
}
type HashMapStorage[T any] struct {
keys BitSet
values map[Index]T
}
func NewHashMapStorage[T any]() *HashMapStorage[T] {
return &HashMapStorage[T]{
keys: BitSet{values: make(map[Index]struct{})},
values: make(map[Index]T),
}
}
func (h *HashMapStorage[T]) Get(eid Index) any { return h.values[eid] }
func (h *HashMapStorage[T]) Insert(eid Index, v any) {
h.keys.Set(eid)
h.values[eid] = v.(T)
}
func (h *HashMapStorage[T]) Remove(eid Index) any {
h.keys.Unset(eid)
v := h.values[eid]
delete(h.values, eid)
return v
}
func (h *HashMapStorage[T]) BitSet() BitSet { return h.keys }

View File

@ -0,0 +1,49 @@
package ecs
import "reflect"
type System interface {
Update(w *World)
}
type funcsystem struct {
update reflect.Value
args func(w *World) []Storage
}
func FuncSystem(F any) System {
f := reflect.ValueOf(F)
in := f.Type().NumIn()
argTypes := make([]reflect.Type, in)
for i := 0; i < in; i++ {
argTypes[i] = f.Type().In(i)
}
return &funcsystem{
update: f,
args: func(w *World) (args []Storage) {
args = make([]Storage, in)
for i := 0; i < in; i++ {
args[i] = w.GetResourceRaw(argTypes[i]).(Storage)
}
return
},
}
}
func (f *funcsystem) Update(w *World) {
storages := f.args(w)
if len(storages) == 0 {
return
}
eids := storages[0].BitSet()
for _, v := range storages[1:] {
eids = eids.And(v.BitSet())
}
args := make([]reflect.Value, len(storages))
eids.Range(func(eid Index) {
for i := range args {
args[i] = reflect.ValueOf(storages[i].Get(eid))
}
f.update.Call(args)
})
}

View File

@ -0,0 +1,58 @@
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 (w *World) Insert(resource any) {
t := reflect.ValueOf(resource).Type()
w.resources[t] = resource
}
func (w *World) Remove(resource any) any {
t := reflect.ValueOf(resource).Type()
resource = w.resources[t]
delete(w.resources, t)
return resource
}
func (w *World) GetResource(resource any) any {
if resource == nil {
return nil
}
t := reflect.ValueOf(resource).Type()
v, _ := w.resources[t]
return v
}
func (w *World) GetResourceRaw(t reflect.Type) any {
v, _ := w.resources[t]
return v
}
func Register[T any](w *World, component T) {
t := reflect.TypeOf(component)
s := NewHashMapStorage[T]()
w.resources[t] = s
}
func (w *World) CreateEntity(components ...any) (i Index) {
i = Index(atomic.AddUint32((*uint32)(&w.maxEID), 1))
for _, c := range components {
v := reflect.ValueOf(c)
t := v.Type()
storage := w.resources[t].(Storage)
storage.Insert(w.maxEID, c)
}
return
}