a new era for Containe

This commit is contained in:
Josh Deprez 2021-09-30 10:36:13 +10:00
parent 8516d1cb91
commit 8ec3ef2a58
4 changed files with 95 additions and 105 deletions

View file

@ -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{})
}

View file

@ -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)
}

View file

@ -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{}

View file

@ -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) {