a new era for Containe
This commit is contained in:
parent
8516d1cb91
commit
8ec3ef2a58
4 changed files with 95 additions and 105 deletions
|
@ -3,10 +3,11 @@ package engine
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var _ interface {
|
||||
Registrar
|
||||
Prepper
|
||||
Scanner
|
||||
gob.GobDecoder
|
||||
gob.GobEncoder
|
||||
|
@ -16,10 +17,11 @@ func init() {
|
|||
gob.Register(&Container{})
|
||||
}
|
||||
|
||||
// Container contains many components, in order.
|
||||
// Container is a component that contains many other components, in order.
|
||||
// It can be used as both a component in its own right, or as a ordered set.
|
||||
// A nil *Container contains no items and modifications will panic (like a map).
|
||||
type Container struct {
|
||||
items []interface{}
|
||||
free map[int]struct{}
|
||||
reverse map[interface{}]int
|
||||
}
|
||||
|
||||
|
@ -35,7 +37,7 @@ func (c *Container) GobDecode(in []byte) error {
|
|||
if err := gob.NewDecoder(bytes.NewReader(in)).Decode(&c.items); err != nil {
|
||||
return err
|
||||
}
|
||||
c.free, c.reverse = nil, nil
|
||||
c.reverse = nil
|
||||
return c.Prepare(nil)
|
||||
}
|
||||
|
||||
|
@ -57,14 +59,14 @@ func (c *Container) Prepare(*Game) error {
|
|||
c.reverse[x] = i
|
||||
}
|
||||
}
|
||||
if c.free == nil {
|
||||
c.free = make(map[int]struct{})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Scan visits every non-nil component in the container.
|
||||
func (c *Container) Scan(visit VisitFunc) error {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
for _, x := range c.items {
|
||||
if x != nil {
|
||||
if err := visit(x); err != nil {
|
||||
|
@ -75,6 +77,55 @@ func (c *Container) Scan(visit VisitFunc) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Add adds an item to the end of the container, if not already present.
|
||||
func (c *Container) Add(component interface{}) {
|
||||
if c.Contains(component) {
|
||||
return
|
||||
}
|
||||
c.reverse[component] = len(c.items)
|
||||
c.items = append(c.items, component)
|
||||
}
|
||||
|
||||
// Remove replaces an item with nil. If the number of nil items is greater than
|
||||
// half the slice, the slice is compacted (indexes of items will change).
|
||||
func (c *Container) Remove(component interface{}) {
|
||||
i, found := c.reverse[component]
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
c.items[i] = nil
|
||||
delete(c.reverse, i)
|
||||
if len(c.reverse) < len(c.items)/2 {
|
||||
c.compact()
|
||||
}
|
||||
}
|
||||
|
||||
// Contains reports if an item exists in the container.
|
||||
func (c *Container) Contains(component interface{}) bool {
|
||||
if c == nil {
|
||||
return false
|
||||
}
|
||||
_, found := c.reverse[component]
|
||||
return found
|
||||
}
|
||||
|
||||
// IndexOf reports if an item exists in the container and returns the index if
|
||||
// present.
|
||||
func (c *Container) IndexOf(component interface{}) (int, bool) {
|
||||
if c == nil {
|
||||
return 0, false
|
||||
}
|
||||
i, found := c.reverse[component]
|
||||
return i, found
|
||||
}
|
||||
|
||||
func (c *Container) ItemCount() int {
|
||||
if c == nil {
|
||||
return 0
|
||||
}
|
||||
return len(c.reverse)
|
||||
}
|
||||
|
||||
// Element returns the item at index i, or nil for a free slot.
|
||||
func (c *Container) Element(i int) interface{} { return c.items[i] }
|
||||
|
||||
|
@ -83,72 +134,21 @@ func (c *Container) Len() int { return len(c.items) }
|
|||
|
||||
// Swap swaps any two items, free slots, or a combination.
|
||||
func (c *Container) Swap(i, j int) {
|
||||
if i == j {
|
||||
return
|
||||
c.items[i], c.items[j] = c.items[j], c.items[i]
|
||||
if c.items[i] != nil {
|
||||
c.reverse[c.items[i]] = i
|
||||
}
|
||||
ifree := c.items[i] == nil
|
||||
jfree := c.items[j] == nil
|
||||
switch {
|
||||
case ifree && jfree:
|
||||
return
|
||||
case ifree:
|
||||
c.items[i] = c.items[j]
|
||||
c.reverse[c.items[i]] = i
|
||||
c.free[j] = struct{}{}
|
||||
delete(c.free, i)
|
||||
case jfree:
|
||||
c.items[j] = c.items[i]
|
||||
c.reverse[c.items[j]] = j
|
||||
c.free[i] = struct{}{}
|
||||
delete(c.free, j)
|
||||
default:
|
||||
c.items[i], c.items[j] = c.items[j], c.items[i]
|
||||
c.reverse[c.items[i]] = i
|
||||
if c.items[j] != nil {
|
||||
c.reverse[c.items[j]] = j
|
||||
}
|
||||
}
|
||||
|
||||
func (c Container) String() string { return "Container" }
|
||||
|
||||
// Register records component into the slice, if parent is this container. It
|
||||
// writes the component to an arbitrary free index in the slice, or appends if
|
||||
// there are none free.
|
||||
func (c *Container) Register(component, parent interface{}) error {
|
||||
if parent != c {
|
||||
return nil
|
||||
}
|
||||
if len(c.free) == 0 {
|
||||
c.reverse[component] = len(c.items)
|
||||
c.items = append(c.items, component)
|
||||
return nil
|
||||
}
|
||||
for i := range c.free {
|
||||
c.reverse[component] = i
|
||||
c.items[i] = component
|
||||
delete(c.free, i)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unregister searches the slice for the component, and removes it by setting
|
||||
// to nil. If the number of nil items is greater than half the slice, the slice
|
||||
// is compacted.
|
||||
func (c *Container) Unregister(component interface{}) {
|
||||
i, found := c.reverse[component]
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
c.items[i] = nil
|
||||
c.free[i] = struct{}{}
|
||||
delete(c.reverse, i)
|
||||
if len(c.free) > len(c.items)/2 {
|
||||
c.compact()
|
||||
}
|
||||
func (c *Container) String() string {
|
||||
return "Container" + fmt.Sprint(c.items)
|
||||
}
|
||||
|
||||
// compact moves all the items to the front of the items slice, removing any
|
||||
// free slots, and empties the free map.
|
||||
// free slots, and resets the free counter.
|
||||
func (c *Container) compact() {
|
||||
i := 0
|
||||
for _, x := range c.items {
|
||||
|
@ -159,5 +159,4 @@ func (c *Container) compact() {
|
|||
}
|
||||
}
|
||||
c.items = c.items[:i]
|
||||
c.free = make(map[int]struct{})
|
||||
}
|
||||
|
|
|
@ -18,9 +18,6 @@ func TestMakeContainer(t *testing.T) {
|
|||
if want := []interface{}{69, 420}; !cmp.Equal(c.items, want) {
|
||||
t.Errorf("c.items = %v, want %v", c.items, want)
|
||||
}
|
||||
if want := make(map[int]struct{}); !cmp.Equal(c.free, want) {
|
||||
t.Errorf("c.free = %v, want %v", c.free, want)
|
||||
}
|
||||
if want := map[interface{}]int{69: 0, 420: 1}; !cmp.Equal(c.reverse, want) {
|
||||
t.Errorf("c.reverse = %v, want %v", c.reverse, want)
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"io/fs"
|
||||
"log"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
@ -45,10 +44,10 @@ type Game struct {
|
|||
VoxelScale geom.Float3
|
||||
|
||||
dbmu sync.RWMutex
|
||||
byID map[string]Identifier // Named components by ID
|
||||
byAB map[abKey]ComponentSet // paths matching interface
|
||||
parent map[interface{}]interface{} // parent[x] is parent of x
|
||||
children map[interface{}]ComponentSet // children[x] are chilren of x
|
||||
byID map[string]Identifier // Named components by ID
|
||||
byAB map[abKey]*Container // paths matching interface
|
||||
parent map[interface{}]interface{} // parent[x] is parent of x
|
||||
children map[interface{}]*Container // children[x] are chilren of x
|
||||
}
|
||||
|
||||
// Draw draws everything.
|
||||
|
@ -101,7 +100,7 @@ func (g *Game) Parent(c interface{}) interface{} {
|
|||
|
||||
// Children returns the direct subcomponents of the given component, or nil if
|
||||
// there are none. This only returns sensible values for registered components.
|
||||
func (g *Game) Children(c interface{}) ComponentSet {
|
||||
func (g *Game) Children(c interface{}) *Container {
|
||||
g.dbmu.RLock()
|
||||
defer g.dbmu.RUnlock()
|
||||
return g.children[c]
|
||||
|
@ -179,13 +178,16 @@ func (g *Game) Query(ancestor interface{}, behaviour reflect.Type, visitPre, vis
|
|||
g.dbmu.RLock()
|
||||
q := g.byAB[abKey{ancestor, behaviour}]
|
||||
g.dbmu.RUnlock()
|
||||
for x := range q {
|
||||
if err := q.Scan(func(x interface{}) error {
|
||||
if err := g.Query(x, behaviour, visitPre, visitPost); err != nil {
|
||||
if errors.Is(err, Skip) {
|
||||
continue
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if pi && visitPost != nil {
|
||||
return visitPost(ancestor)
|
||||
|
@ -262,9 +264,9 @@ func (g *Game) build() error {
|
|||
g.dbmu.Lock()
|
||||
defer g.dbmu.Unlock()
|
||||
g.byID = make(map[string]Identifier)
|
||||
g.byAB = make(map[abKey]ComponentSet)
|
||||
g.byAB = make(map[abKey]*Container)
|
||||
g.parent = make(map[interface{}]interface{})
|
||||
g.children = make(map[interface{}]ComponentSet)
|
||||
g.children = make(map[interface{}]*Container)
|
||||
return g.registerRecursive(g, nil)
|
||||
}
|
||||
|
||||
|
@ -311,9 +313,10 @@ func (g *Game) registerOne(component, parent interface{}) error {
|
|||
// register in g.parent and g.children
|
||||
g.parent[component] = parent
|
||||
if g.children[parent] == nil {
|
||||
g.children[parent] = make(ComponentSet)
|
||||
g.children[parent] = MakeContainer(component)
|
||||
} else {
|
||||
g.children[parent].Add(component)
|
||||
}
|
||||
g.children[parent][component] = struct{}{}
|
||||
|
||||
// register in g.byAB
|
||||
ct := reflect.TypeOf(component)
|
||||
|
@ -324,13 +327,13 @@ func (g *Game) registerOne(component, parent interface{}) error {
|
|||
for c, p := component, g.parent[component]; p != nil; c, p = p, g.parent[p] {
|
||||
k := abKey{p, b}
|
||||
if g.byAB[k] == nil {
|
||||
g.byAB[k] = ComponentSet{c: {}}
|
||||
g.byAB[k] = MakeContainer(c)
|
||||
continue
|
||||
}
|
||||
if _, exists := g.byAB[k][c]; exists {
|
||||
if g.byAB[k].Contains(c) {
|
||||
break
|
||||
}
|
||||
g.byAB[k][c] = struct{}{}
|
||||
g.byAB[k].Add(c)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -349,30 +352,33 @@ func (g *Game) Unregister(component interface{}) {
|
|||
}
|
||||
|
||||
func (g *Game) unregisterRecursive(component interface{}) {
|
||||
for x := range g.children[component] {
|
||||
g.children[component].Scan(func(x interface{}) error {
|
||||
g.unregisterRecursive(x)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
g.unregisterOne(component)
|
||||
}
|
||||
|
||||
func (g *Game) unregisterOne(component interface{}) {
|
||||
parent := g.parent[component]
|
||||
|
||||
// unregister from g.byAB
|
||||
ct := reflect.TypeOf(component)
|
||||
for _, b := range Behaviours {
|
||||
if !ct.Implements(b) {
|
||||
continue
|
||||
}
|
||||
for c, p := component, g.parent[component]; p != nil; c, p = p, g.parent[p] {
|
||||
for c, p := component, parent; p != nil; c, p = p, g.parent[p] {
|
||||
k := abKey{p, b}
|
||||
delete(g.byAB[k], c)
|
||||
if len(g.byAB[k]) > 0 {
|
||||
g.byAB[k].Remove(c)
|
||||
if g.byAB[k].ItemCount() > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unregister from g.parent and g.children
|
||||
delete(g.children[g.parent[component]], component)
|
||||
g.children[parent].Remove(component)
|
||||
delete(g.parent, component)
|
||||
|
||||
// unregister from g.byID if needed
|
||||
|
@ -385,19 +391,6 @@ func (g *Game) String() string { return "Game" }
|
|||
|
||||
// --------- Helper stuff ---------
|
||||
|
||||
// ComponentSet is a set of components.
|
||||
type ComponentSet map[interface{}]struct{}
|
||||
|
||||
func (c ComponentSet) String() string {
|
||||
var b strings.Builder
|
||||
b.WriteString("{")
|
||||
for x := range c {
|
||||
fmt.Fprint(&b, " ", x)
|
||||
}
|
||||
b.WriteString(" }")
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// abKey is the key type for game.byAB.
|
||||
type abKey struct {
|
||||
parent interface{}
|
||||
|
|
|
@ -102,9 +102,10 @@ func (g *Game) printTreeRecursive(dst io.Writer, depth int, c interface{}) {
|
|||
} else {
|
||||
fmt.Fprintf(dst, "%s%v\n", indent, c)
|
||||
}
|
||||
for x := range g.Children(c) {
|
||||
g.Children(c).Scan(func(x interface{}) error {
|
||||
g.printTreeRecursive(dst, depth+1, x)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (g *Game) cmdQuery(dst io.Writer, argv []string) {
|
||||
|
|
Loading…
Reference in a new issue