From 36828773170aa14164624f2616aea1289721d6d9 Mon Sep 17 00:00:00 2001 From: Gwyneth Llewelyn Date: Sun, 23 Jul 2017 18:41:47 +0100 Subject: [PATCH] First tweaks for the preparation of the engine --- engine.go | 227 ++++++++++++++++++++++++++++++++---------------------- gobot.go | 5 ++ 2 files changed, 142 insertions(+), 90 deletions(-) diff --git a/engine.go b/engine.go index 2546d33..35a1f7b 100644 --- a/engine.go +++ b/engine.go @@ -44,6 +44,23 @@ func (wsM *WsMessageType) New(msgType string, msgSubType string, msgText string, // running bool //} +// Constants for movement algorithm. Names are retained from the PHP version. +// TODO(gwyneth): Have these constants as variables which are read from the configuration file. + +const OS_NPC_SIT_NOW = 0 +// for genetic algorithm +const RADIUS = 10 // this is the size of the grid that is thrown around the avatar +const POPULATION_SIZE = 50 // was 50 +const GENERATIONS = 20 // was 20 for 20x20 grid +const CHROMOSOMES = 7 // was 28 for 20x20 grid +const CROSSOVER_RATE = 90.0 // = 90%, we use a random number generator for 0-100 +const MUTATION_RATE = 5.0 // = 0.005%, we use a random number generator for 0-1000 - TODO(gwyneth): try later with 0.01 +const WALKING_SPEED = 3.19 // avatar walking speed in meters per second) +// Weights for Shi & Cui +const W1 = 1.0 // Sub-function of Path Length +const W2 = 10.0 // Sub-function of Path Security +const W3 = 5.0 // Sub-function of Smoothness + // Go is tricky. While we send and receive WebSocket messages as it would be expected on a 'normal' // programming language, we actually have an insane amount of goroutines all in parallel. So what we do is to // send messages to a 'channel' (Go's version of a semaphore) and receive them from a different one; two sets @@ -284,8 +301,9 @@ func engine() { db, err := sql.Open(PDO_Prefix, SQLiteDBFilename) checkErr(err) - // load in Agents! - rows, err := db.Query("SELECT * FROM Agents ORDER BY Name") // can't hurt much to let the DB engine sort it + // load in Agents! We need them to call the movement algorithm for each one + // BUG(gwyneth): what if the number of agents _change_ while we're running the engine? We need a way to reset the engine somehow. We have a hack at the moment: send a SIGCONT, it will try to restart the engine in a new goroutine + rows, err := db.Query("SELECT * FROM Agents ORDER BY Name") // can't hurt much to let the DB engine sort it, that way we humans have an idea on how far we've progressed when adding agents checkErr(err) for rows.Next() { @@ -314,101 +332,130 @@ func engine() { // we should extract the region name from Agent.Location, but I'm lazy! Agents = append(Agents, Agent) } - - // Load in the 'special' objects (cubes). Because the Master Controllers can be somewhere in here, to save code. - // and a database query, we simply skip all the Master Controllers until we get the most recent one, which gets saved - // The rest of the objects are cubes, so we will need them in the ObjectType array (20170722). - // BUG(gwyneth): Does not work across regions! We will probably need a map of bot controllers for that and check which one to call depending on the region of the current agent; simple, but I'm lazy (20170722). - - rows, err = db.Query("SELECT * FROM Positions ORDER BY LastUpdate ASC") - checkErr(err) - - for rows.Next() { - err = rows.Scan( - &Position.PermURL, - &Position.UUID, - &Position.Name, - &Position.OwnerName, - &Position.Location, - &Position.Position, - &Position.Rotation, - &Position.Velocity, - &Position.LastUpdate, - &Position.OwnerKey, - &Position.ObjectType, - &Position.ObjectClass, - &Position.RateEnergy, - &Position.RateMoney, - &Position.RateHappiness, - ) - Position.Coords_xyz = strings.Split(strings.Trim(*Position.Position.Ptr(), "() \t\n\r"), ",") - - // check if we got a Master Bot Controller! - if (*Position.ObjectType.Ptr() == "Bot Controller") { - masterController = Position // this will get overwritten until we get the last, most recent one - } else { - Cubes = append(Cubes, Position) // if not a controller, it must be a cube! add it to array! - } - } - - // load in everything we found out so far on our region(s) but ignore phantom objects - // end-users ought to set their cubes to phantom as well, or else the agents will think of them as obstacles! - rows, err = db.Query("SELECT * FROM Obstacles WHERE Phantom = 0") - checkErr(err) - - for rows.Next() { - err = rows.Scan( - &Object.UUID, - &Object.Name, - &Object.BotKey, - &Object.BotName, - &Object.Type, - &Object.Position, - &Object.Rotation, - &Object.Velocity, - &Object.LastUpdate, - &Object.Origin, - &Object.Phantom, - &Object.Prims, - &Object.BBHi, - &Object.BBLo, - ) - Object.Coords_xyz = strings.Split(strings.Trim(*Object.Position.Ptr(), "() \t\n\r"), ",") - - Objects = append(Objects, Object) - } - - // Debug stuff. Delete it after usage. - var marshalled []byte = []byte("Kablooie! JSON blew up everything! No data available...") - marshalled, err = json.MarshalIndent(Objects, "", " ") - checkErr(err) - log.Println("Objects", marshalled) - sendMessageToBrowser("status", "info", fmt.Sprintf("Objects: %s
", marshalled), "") - marshalled, err = json.MarshalIndent(Agents, "", " ") - checkErr(err) - log.Println("Agents", marshalled) - sendMessageToBrowser("status", "info", fmt.Sprintf("Agents: %s
", marshalled ), "") - marshalled, err = json.MarshalIndent(Cubes, "", " ") - checkErr(err) - log.Println("Cubes", marshalled) - sendMessageToBrowser("status", "info", fmt.Sprintf("Cubes: %s
", marshalled), "") - marshalled, err = json.MarshalIndent(masterController, "", " ") - checkErr(err) - log.Println("Master Bot Controller", marshalled) - sendMessageToBrowser("status", "info", fmt.Sprintf("Master Bot Controller: %s
", marshalled), "") - - // debugging stuff ends here - // release DB resources before we start our job rows.Close() db.Close() - + + // if we have zero agents, we cannot go on! + // TODO(gwyneth): be more graceful handling this, because the engine will stop forever this way + if len(Agents) == 0 { + log.Println("Error: no Agents found. Engine cannot run. Aborted. Add an Agent and try sending a SIGCONT to restart engine again") + sendMessageToBrowser("status", "restart", "Error: no Agents found. Engine cannot run. Aborted. Add an Agent and try sending a SIGCONT to restart engine again
"," ") + return + } + for { for i, Agent := range Agents { - log.Println("Starting to manipulate Agent", i, "-", *Agent.Name.Ptr()) - + // check if we should be running or not if engineRunning.Load().(bool) { + log.Println("Starting to manipulate Agent", i, "-", *Agent.Name.Ptr()) + // We need to refresh all the data about cubes and positions again! + // do stuff while it runs, e.g. open databases, search for agents and so forth + + log.Println("Reloading database for Cubes (Positions) and Obstacles...") + + // Open database + db, err = sql.Open(PDO_Prefix, SQLiteDBFilename) + checkErr(err) + + + // Load in the 'special' objects (cubes). Because the Master Controllers can be somewhere in here, to save code. + // and a database query, we simply skip all the Master Controllers until we get the most recent one, which gets saved + // The rest of the objects are cubes, so we will need them in the ObjectType array (20170722). + // BUG(gwyneth): Does not work across regions! We will probably need a map of bot controllers for that and check which one to call depending on the region of the current agent; simple, but I'm lazy (20170722). + + Cubes = nil // clear array, let the Go garbage collector deal with the memory (20170723) + rows, err = db.Query("SELECT * FROM Positions ORDER BY LastUpdate ASC") + checkErr(err) + + for rows.Next() { + err = rows.Scan( + &Position.PermURL, + &Position.UUID, + &Position.Name, + &Position.OwnerName, + &Position.Location, + &Position.Position, + &Position.Rotation, + &Position.Velocity, + &Position.LastUpdate, + &Position.OwnerKey, + &Position.ObjectType, + &Position.ObjectClass, + &Position.RateEnergy, + &Position.RateMoney, + &Position.RateHappiness, + ) + Position.Coords_xyz = strings.Split(strings.Trim(*Position.Position.Ptr(), "() \t\n\r"), ",") + + // check if we got a Master Bot Controller! + if (*Position.ObjectType.Ptr() == "Bot Controller") { + masterController = Position // this will get overwritten until we get the last, most recent one + } else { + Cubes = append(Cubes, Position) // if not a controller, it must be a cube! add it to array! + } + } + + // load in everything we found out so far on our region(s) but ignore phantom objects + // end-users ought to set their cubes to phantom as well, or else the agents will think of them as obstacles! + Objects = nil + rows, err = db.Query("SELECT * FROM Obstacles WHERE Phantom = 0") + checkErr(err) + + for rows.Next() { + err = rows.Scan( + &Object.UUID, + &Object.Name, + &Object.BotKey, + &Object.BotName, + &Object.Type, + &Object.Position, + &Object.Rotation, + &Object.Velocity, + &Object.LastUpdate, + &Object.Origin, + &Object.Phantom, + &Object.Prims, + &Object.BBHi, + &Object.BBLo, + ) + Object.Coords_xyz = strings.Split(strings.Trim(*Object.Position.Ptr(), "() \t\n\r"), ",") + + Objects = append(Objects, Object) + } + + // Debug stuff. Delete it after usage. + /* + var marshalled []byte = []byte("Kablooie! JSON blew up everything! No data available...") + marshalled, err = json.MarshalIndent(Objects, "", " ") + checkErr(err) + log.Println("Objects", marshalled) + sendMessageToBrowser("status", "info", fmt.Sprintf("Objects: %s
", marshalled), "") + marshalled, err = json.MarshalIndent(Agents, "", " ") + checkErr(err) + log.Println("Agents", marshalled) + sendMessageToBrowser("status", "info", fmt.Sprintf("Agents: %s
", marshalled ), "") + marshalled, err = json.MarshalIndent(Cubes, "", " ") + checkErr(err) + log.Println("Cubes", marshalled) + sendMessageToBrowser("status", "info", fmt.Sprintf("Cubes: %s
", marshalled), "") + marshalled, err = json.MarshalIndent(masterController, "", " ") + checkErr(err) + log.Println("Master Bot Controller", marshalled) + sendMessageToBrowser("status", "info", fmt.Sprintf("Master Bot Controller: %s
", marshalled), "") + */ + // debugging stuff ends here + + // release DB resources before we start our job + rows.Close() + db.Close() + + // Do not trust the database with the exact Agent position: ask the master controller directly + log.Println(*masterController.PermURL.Ptr()) + + + // output something to console so that we know this is being run in parallel fmt.Print("\r|") time.Sleep(1000 * time.Millisecond) fmt.Print("\r/") diff --git a/gobot.go b/gobot.go index 672738b..296a7ca 100644 --- a/gobot.go +++ b/gobot.go @@ -86,6 +86,11 @@ func main() { sendMessageToBrowser("status", "", randomdata.FullName(randomdata.Female) + "
", "") // defined on engine.go for now case syscall.SIGUSR2: sendMessageToBrowser("status", "", randomdata.Country(randomdata.FullCountry) + "
", "") // defined on engine.go for now + case syscall.SIGCONT: + // HACK(gwyneth): if the engine dies, send a SIGCONT to get it running again (20170723). + log.Println("SIGCONT caught; trying to launch the engine again") + sendMessageToBrowser("status", "restart", "SIGCONT caught; trying to launch the engine again
", "") + go engine() // is this a good idea? Maybe we ought to have a flag saying if we're running or not! (20170723) default: log.Println("Unknown UNIX signal caught!! Ignoring...") }