yasshi2525/RushHour

View on GitHub
entities/cluster.go

Summary

Maintainability
A
0 mins
Test Coverage
C
70%
package entities

import (
    "fmt"
    "log"
    "strings"
)

const (
    // N represents North
    N = 0
    // S represents South
    S = 1
    // W represents West
    W = 0
    // E represents East
    E = 1
)

// ChunkPoint is scaled value
type ChunkPoint struct {
    X     int
    Y     int
    Scale int
}

func (p *ChunkPoint) has(pos *Point) bool {
    x, y := int(pos.X), int(pos.Y)
    left, right := p.X<<p.Scale, p.X<<p.Scale+1
    top, buttom := p.Y<<p.Scale, p.Y<<p.Scale+1
    return left <= x && x < right && top <= y && y < buttom
}

func (p *ChunkPoint) contains(oth *ChunkPoint) bool {
    if p.Scale < oth.Scale {
        return false
    }
    diff := p.Scale - oth.Scale
    return p.X == oth.X>>diff && p.Y == oth.Y>>diff
}

func (p *ChunkPoint) String() string {
    return fmt.Sprintf("(%d,%d,%d)", p.X, p.Y, p.Scale)
}

// Cluster summarize entities of each owner.
type Cluster struct {
    Base
    ChunkPoint
    Parent *Cluster
    Data   map[uint]*Chunk

    Children [2][2]*Cluster
}

// NewCluster creates Cluster on specified point.
// dx, dy must be 0 or 1
func (m *Model) NewCluster(p *Cluster, dx int, dy int) *Cluster {
    cl := &Cluster{
        Base: m.NewBase(CLUSTER),
    }
    if p == nil {
        cl.Scale = m.conf.MaxScale
    } else {
        cl.Parent = p
        cl.Scale = p.Scale - 1
        cl.X = p.X<<1 + dx
        cl.Y = p.Y<<1 + dy
        p.Children[dy][dx] = cl
    }
    cl.Init(m)
    m.Add(cl)
    return cl
}

// B returns base information of this elements.
func (cl *Cluster) B() *Base {
    return &cl.Base
}

// Init creates map.
func (cl *Cluster) Init(m *Model) {
    cl.Base.Init(CLUSTER, m)
    cl.Data = make(map[uint]*Chunk)
}

// FindChunk returns specific scale's Chunk which has specified Entity.
func (cl *Cluster) FindChunk(obj Entity, scale int) *Chunk {
    if cl.Scale == scale {
        data := cl.Data[obj.B().OwnerID]
        if data != nil && data.Has(obj) {
            return data
        }
    } else {
        for _, list := range cl.Children {
            for _, child := range list {
                if child != nil {
                    if d := child.FindChunk(obj, scale); d != nil {
                        return d
                    }
                }
            }
        }
    }
    return nil
}

// Add deploy Entity over related Chunk.
func (cl *Cluster) Add(raw Entity) {
    if obj, ok := raw.(Connectable); ok {
        cl.addEntity(raw, obj.From().(Localable).Pos())
    }
    if obj, ok := raw.(Localable); ok {
        cl.addEntity(raw, obj.Pos())
    }
}

func (cl *Cluster) addEntity(obj Entity, p *Point) {
    if p == nil {
        return
    }
    if cl.Parent == nil && (int(p.X)>>cl.Scale > 0 || int(p.Y)>>cl.Scale > 0) {
        log.Printf("%v(%v) is out of bounds for %v", obj, p, cl)
    }

    oid := obj.B().OwnerID
    if _, ok := cl.Data[oid]; !ok {
        cl.Data[oid] = cl.M.NewChunk(cl, obj.B().O)
    }

    cl.Data[oid].Add(obj)

    cl.eachChildren(func(dx int, dy int, c *Cluster, pos *ChunkPoint) {
        if pos.has(p) {
            if c == nil {
                c = cl.M.NewCluster(cl, dx, dy)
            }
            c.Add(obj)
        }
    })
}

// Update changes Chunk of specified Entity
func (cl *Cluster) Update(obj Entity) {
    cl.Remove(obj)
    cl.Add(obj)
}

// Remove undeploy specified Entity over related Chunk.
func (cl *Cluster) Remove(obj Entity) {
    if _, ok := obj.(Localable); ok {
        cl.removeEntity(obj)
    }
    if _, ok := obj.(Connectable); ok {
        cl.removeEntity(obj)
    }
}

func (cl *Cluster) removeEntity(obj Entity) {
    oid := obj.B().OwnerID
    if chunk := cl.Data[oid]; chunk != nil {
        chunk.Remove(obj)
        cl.eachChildren(func(dx int, dy int, c *Cluster, p *ChunkPoint) {
            if c != nil && c.Data[oid] != nil && c.Data[oid].Has(obj) {
                c.Remove(obj)
            }
        })
        if chunk.IsEmpty() {
            chunk.Delete()
        }
    }
    if len(cl.Data) == 0 {
        cl.Delete()
    }
}

// ViewMap set delegate Entity to DelegateMap.
func (cl *Cluster) ViewMap(dm *DelegateMap, pos *ChunkPoint, span int) {
    if cl.ChunkPoint.contains(pos) {
        if cl.Scale <= pos.Scale-span {
            for _, d := range cl.Data {
                d.Export(dm)
            }
        } else {
            cl.eachChildren(func(dx int, dy int, c *Cluster, p *ChunkPoint) {
                if c != nil {
                    c.ViewMap(dm, pos, span)
                }
            })
        }
    }
}

func (cl *Cluster) eachChildren(callback func(int, int, *Cluster, *ChunkPoint)) {
    if cl.Scale > cl.M.conf.MinScale {
        for _, dy := range []int{0, 1} {
            for _, dx := range []int{0, 1} {
                callback(dx, dy, cl.Children[dy][dx],
                    &ChunkPoint{cl.X<<1 + dx, cl.Y<<1 + dy, cl.Scale - 1})
            }
        }
    }
}

// BeforeDelete remove reference of related entity
func (cl *Cluster) BeforeDelete() {
    if cl.Parent != nil {
        cl.Parent.UnResolve(cl)
    }
}

// UnResolve unregisters specified refernce.
func (cl *Cluster) UnResolve(args ...interface{}) {
    for _, raw := range args {
        switch obj := raw.(type) {
        case *Cluster:
            for y, list := range cl.Children {
                for x, child := range list {
                    if child == cl {
                        cl.Children[y][x] = nil
                    }
                }
            }
        case *Chunk:
            delete(cl.Data, obj.OwnerID)
        default:
            panic(fmt.Errorf("invalid type: %T %+v", obj, obj))
        }
    }
}

// Resolve set reference.
func (cl *Cluster) Resolve(args ...Entity) {
    for _, raw := range args {
        switch obj := raw.(type) {
        case *Chunk:
            cl.Data[obj.OwnerID] = obj
        default:
            panic(fmt.Errorf("invalid type: %T %+v", obj, obj))
        }
    }
}

// CheckDelete check remain relation.
func (cl *Cluster) CheckDelete() error {
    if len(cl.Data) > 0 {
        return fmt.Errorf("data exists")
    }
    if cl.Scale > cl.M.conf.MinScale {
        for _, dy := range []int{-1, +1} {
            for _, dx := range []int{-1, +1} {
                if res := cl.Children[dy][dx].CheckDelete(); res != nil {
                    return res
                }
            }
        }
    }
    return nil
}

// Delete removes this entity with related ones.
func (cl *Cluster) Delete() {
    for _, list := range cl.Children {
        for _, child := range list {
            if child != nil {
                child.Delete()
            }
        }
    }
    for _, ch := range cl.Data {
        ch.Delete()
    }
    cl.M.Delete(cl)
}

// String represents status
func (cl *Cluster) String() string {
    list := []string{}
    for id := range cl.Data {
        list = append(list, fmt.Sprintf("ch(%d)", id))
    }
    return fmt.Sprintf("%s(%d:%d):%s,%v", cl.Type().Short(),
        cl.Scale, cl.ID, strings.Join(list, ","), cl.ChunkPoint)
}