Files
2026-09-26 16:32:40 +03:00

353 lines
7.5 KiB
Go

package scene
import (
"aego/core/handle"
"aego/schema"
"reflect"
"unsafe"
)
const pendingChunk = 64
type sparseIndex struct {
dense []int32
}
const (
slotEmpty int32 = -1
tombstoneBias int32 = -3
)
func (s *sparseIndex) get(idx uint32) int32 {
if int(idx) >= len(s.dense) {
return slotEmpty
}
return s.dense[idx]
}
func (s *sparseIndex) set(idx uint32, v int32) {
for int(idx) >= len(s.dense) {
s.dense = append(s.dense, slotEmpty)
}
s.dense[idx] = v
}
func (s *sparseIndex) tomb(slot int32) int32 {
return tombstoneBias - slot
}
func (s *sparseIndex) tombSlot(v int32) int32 {
return tombstoneBias - v
}
func (s *sparseIndex) isTomb(v int32) bool {
return v <= tombstoneBias
}
type anyStore interface {
typeID() schema.TypeID
descriptor() *schema.TypeDescriptor
hasNode(h NodeHandle) bool
pointer(h NodeHandle) (unsafe.Pointer, bool)
addRaw(h NodeHandle) (unsafe.Pointer, bool)
removeNode(h NodeHandle)
dropNode(h NodeHandle)
flush(s *Scene)
count() int
}
type pendingEntry[T any] struct {
node NodeHandle
chunk int
slot int
}
type ComponentStore[T any] struct {
scene *Scene
desc *schema.TypeDescriptor
sparse sparseIndex
nodes []NodeHandle
data []T
pending []pendingEntry[T]
chunks [][]T
dead []NodeHandle
}
func (c *ComponentStore[T]) typeID() schema.TypeID {
return c.desc.ID
}
func (c *ComponentStore[T]) descriptor() *schema.TypeDescriptor {
return c.desc
}
func (c *ComponentStore[T]) Type() schema.TypeID {
return c.desc.ID
}
func (c *ComponentStore[T]) Len() int {
return len(c.data)
}
func (c *ComponentStore[T]) count() int {
return len(c.data) + len(c.pending)
}
func (c *ComponentStore[T]) Nodes() []NodeHandle {
return c.nodes
}
func (c *ComponentStore[T]) Data() []T {
return c.data
}
func (c *ComponentStore[T]) pendingIndex(h NodeHandle) int {
for i := range c.pending {
if c.pending[i].node == h {
return i
}
}
return -1
}
func (c *ComponentStore[T]) pendingPtr(i int) *T {
e := &c.pending[i]
return &c.chunks[e.chunk][e.slot]
}
func (c *ComponentStore[T]) get(h NodeHandle) (*T, bool) {
if !c.scene.IsAlive(h) {
return nil, false
}
if slot := c.sparse.get(h.Index); slot >= 0 {
if c.nodes[slot] == h {
return &c.data[slot], true
}
return nil, false
} else if c.sparse.isTomb(slot) {
return nil, false
}
if i := c.pendingIndex(h); i >= 0 {
return c.pendingPtr(i), true
}
return nil, false
}
func (c *ComponentStore[T]) add(h NodeHandle) (*T, bool) {
n, ok := c.scene.node(h)
if !ok || n.has(flagPendingDestroy) {
return nil, false
}
slot := c.sparse.get(h.Index)
if slot >= 0 && c.nodes[slot] == h {
return &c.data[slot], true
}
if c.sparse.isTomb(slot) {
if p := c.reviveTombstone(h, slot); p != nil {
return p, true
}
}
if i := c.pendingIndex(h); i >= 0 {
return c.pendingPtr(i), true
}
chunk := len(c.chunks) - 1
if chunk < 0 || len(c.chunks[chunk]) == pendingChunk {
c.chunks = append(c.chunks, make([]T, 0, pendingChunk))
chunk = len(c.chunks) - 1
}
var zero T
c.chunks[chunk] = append(c.chunks[chunk], zero)
slotIdx := len(c.chunks[chunk]) - 1
c.pending = append(c.pending, pendingEntry[T]{node: h, chunk: chunk, slot: slotIdx})
c.scene.queueStructural()
c.scene.emit(Event{Kind: EvComponentAdded, Node: h, ID: c.scene.ID(h), Type: c.desc.ID})
return &c.chunks[chunk][slotIdx], true
}
func (c *ComponentStore[T]) reviveTombstone(h NodeHandle, marker int32) *T {
slot := c.sparse.tombSlot(marker)
if int(slot) >= len(c.nodes) || c.nodes[slot] != h {
return nil
}
for i := range c.dead {
if c.dead[i] == h {
c.dead = append(c.dead[:i], c.dead[i+1:]...)
break
}
}
var zero T
c.data[slot] = zero
c.sparse.set(h.Index, slot)
c.scene.emit(Event{Kind: EvComponentAdded, Node: h, ID: c.scene.ID(h), Type: c.desc.ID})
return &c.data[slot]
}
func (c *ComponentStore[T]) hasNode(h NodeHandle) bool {
_, ok := c.get(h)
return ok
}
func (c *ComponentStore[T]) pointer(h NodeHandle) (unsafe.Pointer, bool) {
p, ok := c.get(h)
if !ok {
return nil, false
}
return unsafe.Pointer(p), true
}
func (c *ComponentStore[T]) addRaw(h NodeHandle) (unsafe.Pointer, bool) {
p, ok := c.add(h)
if !ok {
return nil, false
}
return unsafe.Pointer(p), true
}
func (c *ComponentStore[T]) removeNode(h NodeHandle) {
if i := c.pendingIndex(h); i >= 0 {
c.pending = append(c.pending[:i], c.pending[i+1:]...)
c.scene.emit(Event{Kind: EvComponentRemoved, Node: h, ID: c.scene.ID(h), Type: c.desc.ID})
return
}
slot := c.sparse.get(h.Index)
if slot < 0 || c.nodes[slot] != h {
return
}
c.sparse.set(h.Index, c.sparse.tomb(slot))
c.dead = append(c.dead, h)
c.scene.queueStructural()
c.scene.emit(Event{Kind: EvComponentRemoved, Node: h, ID: c.scene.ID(h), Type: c.desc.ID})
}
func (c *ComponentStore[T]) dropNode(h NodeHandle) {
if i := c.pendingIndex(h); i >= 0 {
c.pending = append(c.pending[:i], c.pending[i+1:]...)
}
slot := c.sparse.get(h.Index)
if slot == slotEmpty {
return
}
if c.sparse.isTomb(slot) {
c.sparse.set(h.Index, slotEmpty)
for i := range c.dead {
if c.dead[i] == h {
c.dead = append(c.dead[:i], c.dead[i+1:]...)
break
}
}
}
for i := range c.nodes {
if c.nodes[i] == h {
c.swapRemove(i)
break
}
}
c.sparse.set(h.Index, slotEmpty)
}
func (c *ComponentStore[T]) swapRemove(slot int) {
last := len(c.data) - 1
if slot != last {
c.data[slot] = c.data[last]
c.nodes[slot] = c.nodes[last]
c.sparse.set(c.nodes[slot].Index, int32(slot))
}
var zero T
c.data[last] = zero
c.data = c.data[:last]
c.nodes = c.nodes[:last]
}
func (c *ComponentStore[T]) flush(s *Scene) {
for _, h := range c.dead {
slot := c.sparse.get(h.Index)
if !c.sparse.isTomb(slot) {
continue
}
c.sparse.set(h.Index, slotEmpty)
for i := range c.nodes {
if c.nodes[i] == h {
c.swapRemove(i)
break
}
}
}
c.dead = c.dead[:0]
for i := range c.pending {
e := &c.pending[i]
if !s.IsAlive(e.node) {
continue
}
if c.sparse.get(e.node.Index) >= 0 {
continue
}
c.nodes = append(c.nodes, e.node)
c.data = append(c.data, c.chunks[e.chunk][e.slot])
c.sparse.set(e.node.Index, int32(len(c.data)-1))
}
c.pending = c.pending[:0]
for i := range c.chunks {
c.chunks[i] = c.chunks[i][:0]
}
if len(c.chunks) > 1 {
c.chunks = c.chunks[:1]
}
}
func (c *ComponentStore[T]) Each(fn func(NodeHandle, *T)) {
s := c.scene
for i := range c.data {
h := c.nodes[i]
if int(c.sparse.get(h.Index)) != i {
continue
}
n, ok := s.node(h)
if !ok || n.has(flagPendingDestroy) || !n.has(flagEffective) {
continue
}
fn(h, &c.data[i])
}
}
func (c *ComponentStore[T]) EachAll(fn func(NodeHandle, *T)) {
for i := range c.data {
if int(c.sparse.get(c.nodes[i].Index)) != i {
continue
}
fn(c.nodes[i], &c.data[i])
}
}
func Store[T any](s *Scene) *ComponentStore[T] {
return storeOf[T](s)
}
func newStore[T any](s *Scene, desc *schema.TypeDescriptor) *ComponentStore[T] {
c := &ComponentStore[T]{
scene: s,
desc: desc,
nodes: make([]NodeHandle, 0, 64),
data: make([]T, 0, 64),
chunks: [][]T{make([]T, 0, pendingChunk)},
}
s.storesByGo[reflect.TypeFor[T]()] = c
s.storesByID[desc.ID] = c
s.storeList = append(s.storeList, c)
return c
}
func storeOf[T any](s *Scene) *ComponentStore[T] {
rt := reflect.TypeFor[T]()
if existing, ok := s.storesByGo[rt]; ok {
return existing.(*ComponentStore[T])
}
desc, ok := s.registry.ByGoType(rt)
if !ok {
panic("scene: component type " + rt.String() + " is not registered in the schema")
}
return newStore[T](s, desc)
}
var _ = handle.Handle[nodeData]{}