simple ecs system
This commit is contained in:
@ -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,
|
|
||||||
)))
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
package dimension
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func Test_manager(t *testing.T) {
|
|
||||||
|
|
||||||
}
|
|
32
server/dimension/world.go
Normal file
32
server/dimension/world.go
Normal 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() {
|
||||||
|
|
||||||
|
}
|
@ -1 +0,0 @@
|
|||||||
package server
|
|
39
server/internal/ecs/bitest.go
Normal file
39
server/internal/ecs/bitest.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
27
server/internal/ecs/ecs_test.go
Normal file
27
server/internal/ecs/ecs_test.go
Normal 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)
|
||||||
|
}
|
34
server/internal/ecs/storage.go
Normal file
34
server/internal/ecs/storage.go
Normal 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 }
|
49
server/internal/ecs/system.go
Normal file
49
server/internal/ecs/system.go
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
58
server/internal/ecs/world.go
Normal file
58
server/internal/ecs/world.go
Normal 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
|
||||||
|
}
|
Reference in New Issue
Block a user