yasshi2525/RushHour

View on GitHub
services/model.go

Summary

Maintainability
D
1 day
Test Coverage
F
0%
package services

import (
    "fmt"
    "log"
    "math"
    "math/rand"
    "reflect"
    "time"

    "github.com/yasshi2525/RushHour/entities"
)

// Operation represents request for model
type Operation struct {
    Source string
    Target entities.ModelType
    ID     uint
    X      float64
    Y      float64
    Op     string
    OName  string
}

func (op *Operation) String() string {
    return fmt.Sprintf("Op(%s:%s) %s %v(%d) at (%.2f, %.2f)", op.Source, op.OName, op.Op, op.Target, op.ID, op.X, op.Y)
}

var modelChannel chan *Operation

var mkFuncs map[entities.ModelType]interface{}
var rmFuncs map[entities.ModelType]interface{}

// StartModelWatching setup watching model
func StartModelWatching() {

    modelChannel = make(chan *Operation, conf.Game.Service.Procedure.Queue)

    mkFuncs = make(map[entities.ModelType]interface{})
    mkFuncs[entities.PLAYER] = CreatePlayer
    mkFuncs[entities.RESIDENCE] = CreateResidence
    mkFuncs[entities.COMPANY] = CreateCompany
    mkFuncs[entities.RAILNODE] = CreateRailNode
    mkFuncs[entities.RAILEDGE] = ExtendRailNode
    mkFuncs[entities.STATION] = CreateStation
    mkFuncs[entities.RAILLINE] = CreateRailLine
    mkFuncs[entities.TRAIN] = CreateTrain

    rmFuncs = make(map[entities.ModelType]interface{})
    rmFuncs[entities.RESIDENCE] = RemoveResidence
    rmFuncs[entities.COMPANY] = RemoveCompany
    rmFuncs[entities.RAILNODE] = RemoveRailNode
    rmFuncs[entities.RAILEDGE] = RemoveRailEdge
    rmFuncs[entities.STATION] = RemoveStation
    rmFuncs[entities.RAILLINE] = RemoveRailLine
    rmFuncs[entities.TRAIN] = RemoveTrain

    go watchModel()
    log.Println("model watching was successfully started.")
}

// StopModelWatching closes channel
func StopModelWatching() {
    if modelChannel != nil {
        close(modelChannel)
        log.Println("model watching was successfully stopped.")
    }
}

func watchModel() {
    for msg := range modelChannel {
        start := time.Now()
        lock := processMsg(msg)
        WarnLongExec(start, lock, conf.Game.Service.Perf.Operation.D, fmt.Sprintf("operation(%v)", msg))
    }
    log.Println("model watching channel was closed.")
}

func processMsg(msg *Operation) time.Time {
    MuModel.Lock()
    defer MuModel.Unlock()
    lock := time.Now()

    owner, _ := PasswordSignIn(msg.OName, msg.OName)
    size := 1 << (conf.Game.Entity.MaxScale - conf.Game.Entity.MinScale)
    rnd := float64(size) * rand.Float64()

    switch msg.Op {
    case "create":
        rv := reflect.ValueOf(mkFuncs[msg.Target])
        switch msg.Target {
        case entities.PLAYER:
            level := entities.Normal
            if msg.OName == "admin" {
                level = entities.Admin
            }
            CreatePlayer(msg.OName, msg.OName, msg.OName, rand.Intn(360), level)
        case entities.RESIDENCE:
            fallthrough
        case entities.COMPANY:
            rv.Call([]reflect.Value{
                reflect.ValueOf(owner),
                reflect.ValueOf(msg.X),
                reflect.ValueOf(msg.Y),
            })
        case entities.RAILNODE:
            if rand.Float64() < 0.1 {
                rv.Call([]reflect.Value{
                    reflect.ValueOf(owner),
                    reflect.ValueOf(msg.X),
                    reflect.ValueOf(msg.Y),
                    reflect.ValueOf(10),
                })
            }
        case entities.RAILEDGE:
            if raw := randEntity(owner, entities.RAILNODE); raw != nil {
                rn := raw.(*entities.RailNode)
                theta := 2 * math.Pi * rand.Float64()

                p := &entities.Point{
                    X: rn.X + math.Cos(theta)*rnd,
                    Y: rn.Y + math.Sin(theta)*rnd,
                }

                for !p.IsIn(0, 0, float64(conf.Game.Entity.MaxScale)) {
                    theta = 2 * math.Pi * rand.Float64()
                    rnd := float64(size) * rand.Float64()

                    p = &entities.Point{
                        X: rn.X + math.Cos(theta)*rnd,
                        Y: rn.Y + math.Sin(theta)*rnd,
                    }
                }

                rv.Call([]reflect.Value{
                    reflect.ValueOf(owner),
                    reflect.ValueOf(raw),
                    reflect.ValueOf(p.X),
                    reflect.ValueOf(p.Y),
                    reflect.ValueOf(10),
                })

                if oth := randEntity(owner, entities.RAILNODE); oth != nil {
                    to := oth.(*entities.RailNode)
                    if rn != to {
                        var connected bool
                        for _, e := range rn.OutEdges {
                            if e.ToNode == to {
                                connected = true
                                break
                            }
                        }

                        if !connected && rn.Point.Dist(&to.Point) < math.Pow(2, 8) {
                            ConnectRailNode(owner, rn, to, 10)
                        }
                    }
                }
            }
            if raw := randEntity(owner, entities.RAILEDGE); raw != nil {
                re := raw.(*entities.RailEdge)
                d := re.ToNode.Point.Sub(&re.FromNode.Point)
                theta := math.Atan2(d.Y, d.Y) + (rand.Float64()-0.5)*math.Pi/32

                p := &entities.Point{
                    X: re.ToNode.X + math.Cos(theta)*rnd,
                    Y: re.ToNode.Y + math.Sin(theta)*rnd,
                }

                for !p.IsIn(0, 0, float64(conf.Game.Entity.MaxScale)) {
                    rnd := float64(size) * rand.Float64()
                    theta = math.Atan2(d.Y, d.Y) + rand.Float64() - 0.5

                    p = &entities.Point{
                        X: re.ToNode.X + math.Cos(theta)*rnd,
                        Y: re.ToNode.Y + math.Sin(theta)*rnd,
                    }
                }

                ExtendRailNode(owner, re.ToNode, p.X, p.Y, 10)
            }
        case entities.STATION:
            if rn := randEntity(owner, entities.RAILNODE); rn != nil {
                rv.Call([]reflect.Value{
                    reflect.ValueOf(owner),
                    reflect.ValueOf(rn),
                    reflect.ValueOf("NoName")})
            }
        case entities.RAILLINE:
            rv.Call([]reflect.Value{
                reflect.ValueOf(owner),
                reflect.ValueOf("NoName"),
                reflect.ValueOf(rand.Intn(2) == 0),
                reflect.ValueOf(rand.Intn(2) == 0)})
        case entities.LINETASK:
            l := randEntity(owner, entities.RAILLINE)
            if l != nil {
                if p := randEntity(owner, entities.PLATFORM); p != nil {
                    l, p := l.(*entities.RailLine), p.(*entities.Platform)
                    StartRailLine(owner, l, p)
                }
                if re := randEntity(owner, entities.RAILEDGE); re != nil {
                    re := re.(*entities.RailEdge)
                    StartRailLineEdge(owner, l.(*entities.RailLine), re)
                    InsertLineTaskRailEdge(owner, l.(*entities.RailLine), re)
                }
                if rand.Intn(2) == 0 {
                    ComplementRailLine(owner, l.(*entities.RailLine))
                }
            }
        case entities.TRAIN:
            t, _ := CreateTrain(owner, "NoName")
            l := randEntity(owner, entities.RAILLINE)
            if l != nil {
                DeployTrain(owner, t, l.(*entities.RailLine))
            }
        }
    case "remove":
        rv := reflect.ValueOf(rmFuncs[msg.Target])
        if !rv.IsValid() {
            break
        }
        if msg.ID == 0 {
            var ok bool
            msg.ID, ok = randID(msg.Target, owner)
            if !ok {
                break
            }
        }
        rv.Call([]reflect.Value{
            reflect.ValueOf(owner),
            reflect.ValueOf(msg.ID)})
    }
    return lock
}

// randID return random id existing in repository
func randID(t entities.ModelType, owner *entities.Player) (uint, bool) {
    mapdata := Model.Values[t]
    for _, key := range mapdata.MapKeys() {
        if mapdata.MapIndex(key).Interface().(entities.Entity).B().Permits(owner) {
            return uint(key.Uint()), true
        }
    }
    return 0, false
}

// UpdateModel queues user request.
func UpdateModel(msg *Operation) {
    //log.Printf("updatemodel op = %+v", *msg)
    select {
    case modelChannel <- msg:
    default:
        log.Printf("out of queue %+v", *msg)
    }
}

func randEntity(o *entities.Player, res entities.ModelType) interface{} {
    var entity interface{}
    Model.ForEach(res, func(obj entities.Entity) {
        if obj.B().Permits(o) {
            entity = obj
        }
    })
    return entity
}