1
0

engine.go 79 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655
  1. // Here is the main engine app.
  2. package main
  3. import (
  4. _ "github.com/go-sql-driver/mysql"
  5. "bytes"
  6. "database/sql"
  7. "encoding/json"
  8. "errors"
  9. "fmt"
  10. "github.com/jaytaylor/html2text" // converts HTML to pretty-printed text! (20170807)
  11. "github.com/spf13/viper"
  12. "golang.org/x/net/websocket"
  13. "gopkg.in/guregu/null.v3/zero"
  14. "html/template"
  15. "io/ioutil"
  16. "math"
  17. "math/rand"
  18. "net/http"
  19. "sort"
  20. "strings"
  21. "strconv"
  22. "sync/atomic" // used for sync'ing values across goroutines at a low level
  23. "time"
  24. )
  25. // Define a communications procotol with the client, so that we can selectively
  26. // send messages to turn options on and off, etc.
  27. // Messages will be JSON.
  28. type WsMessageType struct {
  29. Type zero.String `json:"type"`
  30. SubType zero.String `json:"subtype"`
  31. Text zero.String `json:"text"`
  32. Id zero.String `json:"id"`
  33. }
  34. // New creates a new WsMessage out of 4 strings
  35. func (wsM *WsMessageType) New(msgType string, msgSubType string, msgText string, msgId string) *WsMessageType {
  36. wsM.Type = zero.StringFrom(msgType)
  37. wsM.SubType = zero.StringFrom(msgSubType)
  38. wsM.Text = zero.StringFrom(msgText)
  39. wsM.Id = zero.StringFrom(msgId)
  40. return wsM
  41. }
  42. // Constants for genetic algorithm. Names are retained from the PHP version.
  43. // TODO(gwyneth): Have these constants as variables which are read from the configuration file.
  44. const OS_NPC_SIT_NOW = "0"
  45. // Constants used in genetic algorithm.
  46. const RADIUS = 10.0 // this is the size of the grid that is thrown around the avatar
  47. const POPULATION_SIZE = 50 // was 50
  48. const GENERATIONS = 20 // was 20 for 20x20 grid
  49. const CHROMOSOMES = 7 // was 28 for 20x20 grid
  50. const CROSSOVER_RATE = 90.0 // = 90%, we use a random number generator for 0-100
  51. const MUTATION_RATE = 5.0 // = 0.005%, we use a random number generator for 0-1000 - TODO(gwyneth): try later with 0.01
  52. const WALKING_SPEED = 3.19 // avatar walking speed in meters per second)
  53. // Weights for Shi & Cui
  54. const W1 = 1.0 // Sub-function of Path Length
  55. const W2 = 10.0 // Sub-function of Path Security
  56. const W3 = 5.0 // Sub-function of Smoothness
  57. // When transposing from the PHP version, we now cannot avoid having a few structs and types, since Go
  58. // is a strongly-typed language (20170726)
  59. // This was moved out of the GA code body because some external functions need those types (20170727)
  60. // chromosomeType is just a point in a path, really.
  61. type chromosomeType struct {
  62. x, y, z, distance, obstacle, angle, smoothness float64
  63. }
  64. // popType represents each population as a list of points (= chromosomes) indicating a possible path; it also includes the fitness for this particular path.
  65. type popType struct {
  66. Fitness float64
  67. chromosomes []chromosomeType
  68. }
  69. // movementJob is used in the worker goroutine which processes the points to move the avatars to, which needs to wait until the avatars have moved.
  70. // Go is so quick in recalculating generations that the avatars never get a chance to reach their destination until we wait for them!
  71. // So the commands to move the avatars need to go into a separate goroutine, to wait on avatars, while the main engine continues (20170730).
  72. type movementJob struct {
  73. agentUUID string // Agent UUID to move
  74. masterControllerPermURL string // masterController to use (note that the engine may pick one of several active ones)
  75. agentPermURL string // unfortunately the masterController cannot get or set Energy...
  76. destPoint chromosomeType // destination to go to; it's a chromosome so that we get distance information as well to calculate
  77. // for how long we need to sleep until the avatar reaches destination
  78. }
  79. // movementJobChannel is the blocking channel to which we write points for the next bot movement
  80. var movementJobChannel = make(chan movementJob, 1) // for now, we'll try with just 1 point
  81. // Go is tricky. While we send and receive WebSocket messages as it would be expected on a 'normal'
  82. // programming language, we actually have an insane amount of goroutines all in parallel. So what we do is to
  83. // send messages to a 'channel' (Go's version of a semaphore) and receive them from a different one; two sets
  84. // of goroutines will have their fun reading and sending messages to the client and updating the channels,
  85. // so other goroutines only send and receive to the channels and have no concept of 'WebSocket messages'
  86. // This is sort of neat because it solves parallelism (goroutines block on sockets) but it also allows
  87. // us to build in other transfer mechanisms and make them abstract using Go channels (20170703)
  88. var wsSendMessage = make(chan WsMessageType)
  89. var wsReceiveMessage = make(chan WsMessageType)
  90. var webSocketActive atomic.Value // this is an attempt to check if we have an active WebSocket, to avoid too many timeouts (20170728)
  91. // serveWs - this is what is 'called' from the outside, and I need to talk to a socket here.
  92. func serveWs(ws *websocket.Conn) {
  93. // see also how it is implemented here: http://eli.thegreenplace.net/2016/go-websocket-server-sample/ (20170703)
  94. var err error // to avoid constant redeclarations in tight loop below
  95. if ws == nil {
  96. Log.Panic("Received nil WebSocket — I have no idea why or how this happened!")
  97. }
  98. /*
  99. log.Printf("Client connected from %s", ws.RemoteAddr())
  100. log.Println("entering serveWs with connection config:", ws.Config())
  101. */
  102. webSocketActive.Store(true)
  103. defer webSocketActive.Store(false)
  104. go func() {
  105. // log.Println("entering send loop")
  106. for {
  107. sendMessage := <-wsSendMessage
  108. if err = websocket.JSON.Send(ws, sendMessage); err != nil {
  109. Log.Error("Can't send; error:", err)
  110. break
  111. }
  112. }
  113. }()
  114. //log.Println("entering receive loop")
  115. var receiveMessage WsMessageType
  116. for {
  117. if err = websocket.JSON.Receive(ws, &receiveMessage); err != nil {
  118. Log.Error("Can't receive; error:", err)
  119. break
  120. }
  121. // Log.Debugf("Received back from client: type '%s' subtype '%s' text '%s' id '%s'\n", *receiveMessage.Type.Ptr(), *receiveMessage.SubType.Ptr(), *receiveMessage.Text.Ptr(), *receiveMessage.Id.Ptr())
  122. wsReceiveMessage <- receiveMessage
  123. }
  124. }
  125. // convertLocPos converts a SL/OpenSim Location and Position into a single region name and (x,y,z) position coordinates
  126. func convertLocPos(location string, position string) (regionName string, xyz []string) {
  127. regionName = location[:strings.Index(location, "(")-1]
  128. coords := strings.Trim(position, "() \t\n\r")
  129. xyz = strings.Split(coords, ",")
  130. return regionName, xyz
  131. }
  132. // calcDistance calculates the distance between two points, which are actually arrays of x,y,z string coordinates.
  133. // TODO(gwyneth): Now that we have a strongly-typed language, we should create real objects for this.
  134. func calcDistance(vec1, vec2 []float64) float64 {
  135. deltaX := vec2[0] - vec1[0] // using extra variables because multiplication is probably
  136. deltaY := vec2[1] - vec1[1] // simpler than calling the math.Pow() function (20170725)
  137. deltaZ := vec2[2] - vec1[2]
  138. return math.Sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ)
  139. }
  140. // engineHandler is still being implemented, it uses the old Go websockets interface to try to keep the page updated.
  141. func backofficeEngine(w http.ResponseWriter, r *http.Request) {
  142. // start gathering the cubes and agents for the Engine form
  143. checkSession(w, r)
  144. // Collect a list of existing bots and their PermURLs for the form
  145. db, err := sql.Open(PDO_Prefix, GoBotDSN)
  146. checkErr(err)
  147. // query for in-world objects that are cubes (i.e. not Bot Controllers)
  148. rows, err := db.Query("SELECT UUID, Name, ObjectType, ObjectClass, Location, Position FROM Positions WHERE ObjectType <> 'Bot Controller' ORDER BY Name")
  149. checkErr(err)
  150. defer rows.Close()
  151. var (
  152. cubes, regionName = "", ""
  153. uuid, name, objType, objClass, location, position = "", "", "", "", "", ""
  154. xyz []string
  155. )
  156. cubes = "\t\t\t\t\t\t\t\t\t\t\t\t\t<option value=\"" + NullUUID + "\">Clean selection (let engine figure out next cube)</option>\n"
  157. // As on backofficeCommands, but a little more complicated
  158. for rows.Next() {
  159. err = rows.Scan(&uuid, &name, &objType, &objClass, &location, &position)
  160. checkErr(err)
  161. // parse name of the region and coordinates
  162. regionName, xyz = convertLocPos(location, position)
  163. cubes += fmt.Sprintf("\t\t\t\t\t\t\t\t\t\t\t\t\t<option value=\"%s\">%s (%s/%s) [%s (%s,%s,%s)]</option>\n", uuid, name, objType, objClass, regionName, xyz[0], xyz[1], xyz[2])
  164. }
  165. rows, err = db.Query("SELECT Name, UUID, Location, Position FROM Agents ORDER BY Name")
  166. checkErr(err)
  167. var uuidAgent, agentNames = "", ""
  168. agentNames = "\t\t\t\t\t\t\t\t\t\t\t\t\t<option value=\"" + NullUUID + "\">Clean selection (let engine figure out next agent)</option>\n"
  169. // To-Do: Agent options should also have location etc.
  170. // find all Names and OwnerKeys and create select options for each of them
  171. for rows.Next() {
  172. err = rows.Scan(&name, &uuidAgent, &location, &position)
  173. checkErr(err)
  174. regionName, xyz = convertLocPos(location, position)
  175. agentNames += fmt.Sprintf("\t\t\t\t\t\t\t\t\t\t\t\t\t<option value=\"%s\">%s (%s) [%s (%s,%s,%s)]</option>\n", uuidAgent, name, uuidAgent, regionName, xyz) // not obvious for the Go linter, but xyz is an array of 3 elements (gwyneth 20210711)
  176. }
  177. rows.Close() // closing after deferring to close is probably not good, but I'll try it anyway (20170723)
  178. db.Close()
  179. tplParams := templateParameters{ "Title": "Gobot Administrator Panel - engine",
  180. "URLPathPrefix": template.HTML(URLPathPrefix),
  181. "Host": template.HTML(Host),
  182. "DestinationOptions": template.HTML(cubes),
  183. "AgentOptions": template.HTML(agentNames),
  184. "ServerPort": template.HTML(ServerPort),
  185. "Content": template.HTML("<hr />"),
  186. }
  187. err = GobotTemplates.gobotRenderer(w, r, "engine", tplParams)
  188. checkErr(err)
  189. }
  190. // EngineRunning is the equivalent of a semaphore which starts or stops the engine.
  191. // OneStep allows the engine to run once, and then it stops.
  192. // These are an exported global (atomic) variables because we need to access it from the configuration function and from the SIGHUP/SIGCONT (20170811, 20170919).
  193. var EngineRunning, OneStep atomic.Value
  194. // engine does everything but the kitchen sink.
  195. // Notably, it does not only run the GA. It also deals on a separate goroutine with message handling for WebSockets, which also includes
  196. // the ability to start or stop the GA. And it launches another goroutine to deal with buffering commands to the virtual world. It really does
  197. // a lot, and possibly it ought to be simplified somehow. But this is the core, the essence, the kernel, the locus of all the rest!
  198. func engine() {
  199. // we use sync/atomic for making sure we can read a value that is set by a different goroutine
  200. // see https://texlution.com/post/golang-lock-free-values-with-atomic-value/ among others (20170704)
  201. var (
  202. receiveMessage WsMessageType
  203. userDestCube atomic.Value // using sync/atomic to make values consistent among goroutines (20170704)
  204. curAgent atomic.Value
  205. )
  206. // EngineRunning.Store(true) // we start by running the engine; note that this may very well happen before we even have WebSockets up (20170704)
  207. // now we let this be set via configuration file; the default is true; and a SIGHUP will start/stop the engine (20170811)
  208. userDestCube.Store(NullUUID) // we start to nullify these atomic values, either they will be changed by the user,
  209. curAgent.Store(NullUUID) // or the engine will simply go through all agents (20170725)
  210. OneStep.Store(false) // in theory, the engine starts or stops; one step is a special case if the client is connected (20170919)
  211. webSocketActive.Store(false) // as soon as we know that we have a connection to the client, we set this to true (20170728)
  212. sendMessageToBrowser("status", "info", "Entering the engine goroutine", "") // browser might not even know we're sending messages to it, so this will just gracefully timeout and be ignored and just appear on the log; changed message to display that we don't know if the engine is going to run or not (20170811)
  213. // Launch the movement worker goroutine. This is needed because Go is so fast calculating populations that it keeps giving the agents
  214. // contradictory movement commands. This uses a blocking channel and calculates how long the avatar needs to reach its destination
  215. // and sleeps for that time. There was something similar done in PHP as well, but PHP took long enough recalculating everything, so
  216. // it was deemed not to be necessary. (20170730)
  217. go movementWorker()
  218. // Now, this is a message handler to receive messages while inside the engine, we
  219. // block on a message and run a goroutine in the background, so we can safely continue
  220. // to run the engine without blocking or errors
  221. // I have no idea yet if this is a good idea or not (20170703)
  222. // At least it works (20170704)
  223. go func() {
  224. var messageType, messageSubType string
  225. for {
  226. receiveMessage = <-wsReceiveMessage
  227. if (receiveMessage.Type.Ptr() != nil) {
  228. messageType = *receiveMessage.Type.Ptr()
  229. } else {
  230. messageType = "empty"
  231. }
  232. if (receiveMessage.SubType.Ptr() != nil) {
  233. messageSubType = *receiveMessage.SubType.Ptr()
  234. } else {
  235. messageSubType = "empty"
  236. }
  237. switch messageType {
  238. case "status":
  239. switch messageSubType {
  240. case "ready": // this is what we get when WebSockets are established on the client
  241. webSocketActive.Store(true)
  242. // check for engine running or not and set the controls
  243. switch EngineRunning.Load().(bool) {
  244. case true:
  245. sendMessageToBrowser("htmlControl", "disable", "", "startEngine")
  246. sendMessageToBrowser("htmlControl", "disable", "", "oneStep")
  247. sendMessageToBrowser("htmlControl", "enable", "", "stopEngine")
  248. case false:
  249. sendMessageToBrowser("htmlControl", "enable", "", "startEngine")
  250. sendMessageToBrowser("htmlControl", "enable", "", "oneStep")
  251. sendMessageToBrowser("htmlControl", "disable", "", "stopEngine")
  252. default: // should never happen, but turn all buttons off just in case
  253. sendMessageToBrowser("htmlControl", "disable", "", "startEngine")
  254. sendMessageToBrowser("htmlControl", "disable", "", "oneStep")
  255. sendMessageToBrowser("htmlControl", "disable", "", "stopEngine")
  256. }
  257. case "gone": // The client has gone, we have no more websocket for this one (20170704)
  258. Log.Info("Client just told us that it went away, we continue on our own")
  259. webSocketActive.Store(false)
  260. default: // no other special functions for now, just echo what the client has sent...
  261. unknownMessage := "<nil>"
  262. if receiveMessage.Text.Ptr() != nil {
  263. unknownMessage = *receiveMessage.Text.Ptr()
  264. }
  265. Log.Warning("Received from client unknown status message with subtype",
  266. messageSubType, "text:", unknownMessage, " — ignoring...")
  267. }
  268. case "formSubmit":
  269. var messageText string
  270. if receiveMessage.Text.Ptr() != nil {
  271. messageText = *receiveMessage.Text.Ptr()
  272. } else {
  273. messageText = NullUUID + "|" + NullUUID // a bit stupid, we could skip this and do direct assigns, but this way we do a bit more effort wasting CPU cycles for the sake of code clarity (20170704)
  274. }
  275. returnValues := strings.Split(messageText, "|")
  276. userDestCube.Store(returnValues[0])
  277. curAgent.Store(returnValues[1])
  278. // Commented out because we know this works and we'll print it out later on anyway (20170730)
  279. // log.Println("Destination: ", userDestCube.Load().(string), "Agent:", curAgent.Load().(string))
  280. // sendMessageToBrowser("status", "info", "Received '" + userDestCube.Load().(string) + "|" + curAgent.Load().(string) + "'<br />", "")
  281. case "engineControl":
  282. switch messageSubType {
  283. case "start":
  284. sendMessageToBrowser("htmlControl", "disable", "", "startEngine")
  285. sendMessageToBrowser("htmlControl", "disable", "", "oneStep")
  286. sendMessageToBrowser("htmlControl", "enable", "", "stopEngine")
  287. EngineRunning.Store(true)
  288. OneStep.Store(false)
  289. case "one-step":
  290. sendMessageToBrowser("htmlControl", "disable", "", "startEngine")
  291. sendMessageToBrowser("htmlControl", "disable", "", "oneStep")
  292. sendMessageToBrowser("htmlControl", "enable", "", "stopEngine")
  293. EngineRunning.Store(true)
  294. OneStep.Store(true)
  295. Log.Debug("OneStep is now", OneStep.Load().(bool))
  296. /*
  297. case "stop":
  298. sendMessageToBrowser("htmlControl", "enable", "", "startEngine")
  299. sendMessageToBrowser("htmlControl", "enable", "", "oneStep")
  300. sendMessageToBrowser("htmlControl", "disable", "", "stopEngine")
  301. EngineRunning.Store(false)
  302. OneStep.Store(false)
  303. */
  304. default: // anything else will stop the engine!
  305. sendMessageToBrowser("htmlControl", "enable", "", "startEngine")
  306. sendMessageToBrowser("htmlControl", "enable", "", "oneStep")
  307. sendMessageToBrowser("htmlControl", "disable", "", "stopEngine")
  308. EngineRunning.Store(false)
  309. OneStep.Store(false)
  310. }
  311. sendMessageToBrowser("status", "", "Engine " + messageSubType + "<br />", "")
  312. default:
  313. Log.Warning("Unknown message type", messageType)
  314. }
  315. }
  316. }()
  317. // We continue with engine. Things may happen in the background, and theoretically we
  318. // will be able to catch them. (20170703)
  319. // load whole database in memory. Really. It's so much faster that way! (20170722)
  320. var (
  321. Agent AgentType // temporary way to store what comes from database
  322. lastAgentToRunUUID string // temporary storage of the last UUID agent that ran, when we pick one randomly, so we give others a chance (20170807)
  323. // Agents map[string]AgentType // we OUGHT to have a type without those strange zero.String, but it's tough to keep two structs in perfect sync (20170722); this is mapped by Agent UUID (20170725)
  324. Position PositionType
  325. Cubes map[string]PositionType // name to be compatible with PHP version; mapped by UUID (20170725).
  326. Object ObjectType
  327. Obstacles []ObjectType
  328. masterController PositionType // set to the most recent Bot Master Controller to send commands (name is the same as in former PHP code).
  329. )
  330. // NOTE(gwyneth): The reason why we use maps and not slices (slices may be faster) is just because that way we can
  331. // directly address the element by UUID, instead of doing array searches (20170725)
  332. // prepare data to be saved as a CSV/XML file for later import into Excel and do nice graphics
  333. var export_rows []string // we place it here because of potential scope issues later on...
  334. // Theoretically endless loop follows (20170730)
  335. for {
  336. // Now, the problem with the approach of going through the list of Agents is that new Agents might appear, old
  337. // might be deleted, and then we're stuck! (Remember, the updating of the Agents table is done in parallel to this)
  338. // The idea of running goroutines for each Agent will also suffer from the same problem: what if the Agent dies and we don't know
  339. // about it? Of course we can check with a ping first. What about *new* Agents? How do we launch new goroutines for them if we
  340. // don't know about them beforehand? (20170801)
  341. // Second approach (20170801): initialise lastAgentRunning with NullUUID; pick one agent from the database; if it's the
  342. // same as before, pick a new one; if the user has provided us with an agent, use that one instead. This will at least provide
  343. // all agents with a chance of running, while allowing new Agents to appear and old ones to die (20170801). The cost of this
  344. // solution is that *some* Agents may not have a chance to run (since they're picked randomly), so we might be a little more
  345. // evil and use some magical pseudo-random generators from Go which allow a sequence of non-repeated random numbers to be
  346. // generated, and try to follow that order if possible, which means reloading the Agent table every cycle, but it might still be
  347. // worth it (20170801).
  348. // NOTE(gwyneth): From 20170807 onwards, the for loop runs forever, each cycle one Agent is picked to run
  349. // Note that the Agent table does not get reloaded each cycle, only a list of UUIDs, one of which is picked randomly and just one
  350. // Agent is loaded (20170807).
  351. if EngineRunning.Load().(bool) {
  352. // Open database
  353. // sanity check first, I have no idea why this happens sometimes:
  354. if PDO_Prefix == "" {
  355. PDO_Prefix = viper.GetString("gobot.PDO_Prefix")
  356. }
  357. if GoBotDSN == "" {
  358. GoBotDSN = viper.GetString("gobot.GoBotDSN")
  359. }
  360. db, err := sql.Open(PDO_Prefix, GoBotDSN)
  361. checkErr(err)
  362. defer db.Close() // needed?
  363. // load in Agents! We need them to call the movement algorithm for each one
  364. // 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
  365. // Changes 20170807: we now pick one agent randomly
  366. // First check if the end-user hasn't sent us an Agent UUID to use:
  367. userSetAgentUUID := curAgent.Load().(string)
  368. possibleAgentUUID := NullUUID
  369. // Log.Debug("userSetAgent is", userSetAgent)
  370. if userSetAgentUUID == NullUUID {
  371. // we need to pick one agent at random
  372. // Since apparenty MySQL is not very efficient at picking a row randomly, we load in a temporary number of UUIDs and
  373. // select one randomly in Go; then we just get the row from the database (20170807)
  374. rows, err := db.Query("SELECT UUID FROM Agents")
  375. if err != nil { // NOTE(gwyneth): caught that error when the grid is not operational yet! (20170816)
  376. sendMessageToBrowser("status", "error", fmt.Sprintf("Database error when selecting Agent to run: %v", err)," ")
  377. time.Sleep(10 * time.Second)
  378. continue // now we simply wait...
  379. }
  380. defer rows.Close() // needed? The problem here is with a continue on the check below...
  381. var agentUUIDs []string
  382. tempUUID := ""
  383. for rows.Next() {
  384. err = rows.Scan(&tempUUID)
  385. checkErr(err)
  386. agentUUIDs = append(agentUUIDs, tempUUID)
  387. }
  388. // if we have zero agents, we cannot go on!
  389. // TODO(gwyneth): be more graceful handling this, because the engine will stop forever this way
  390. // TODO(gwyneth): Better to randomly pick an agent from the database, and if none is available, skip a cycle (20170730).
  391. if len(agentUUIDs) == 0 {
  392. sendMessageToBrowser("status", "error", "Error: no Agents found. Engine cannot run. Aborted. Add an Agent and try sending a <code>SIGCONT</code> to restart engine again<br />"," ")
  393. time.Sleep(10 * time.Second)
  394. continue // now we simply wait...
  395. }
  396. // Log.Debug("We got a bunch of UUIDs:", agentUUIDs)
  397. possibleAgentUUID = agentUUIDs[0] // make sure we have at least a valid UUID!!
  398. // Generate a random index, search for it in agentUUIDs; if it's the same one as last time, try again; test for edge case,
  399. // i.e. that we have just 1 Agent in the database. (20170807)
  400. if len(agentUUIDs) > 1 && lastAgentToRunUUID != NullUUID { // edge case: on initialisation, both are set to NullID, so both are equal
  401. for index := 0; lastAgentToRunUUID == possibleAgentUUID; {
  402. index = rand.Intn(len(agentUUIDs))
  403. possibleAgentUUID = agentUUIDs[index]
  404. //if lastAgentToRunUUID != possibleAgentUUID {
  405. // break
  406. //}
  407. // Log.Debug("Index picked:", index, "possibleAgentUUID", possibleAgentUUID, "Last agent was", lastAgentToRunUUID)
  408. }
  409. }
  410. } else {
  411. possibleAgentUUID = userSetAgentUUID
  412. sendMessageToBrowser("status", "info", fmt.Sprintf("Using agent UUID %s set by end-user", possibleAgentUUID), "")
  413. }
  414. lastAgentToRunUUID = possibleAgentUUID
  415. if possibleAgentUUID == NullUUID {
  416. Log.Critical("My logic is still borked!!") // NOTE(gwyneth): if this situation still happens, I need to revisit this! (20170813)
  417. }
  418. err = db.QueryRow("SELECT * FROM Agents where UUID=?", possibleAgentUUID).Scan(
  419. &Agent.UUID,
  420. &Agent.Name,
  421. &Agent.OwnerName,
  422. &Agent.OwnerKey,
  423. &Agent.Location,
  424. &Agent.Position,
  425. &Agent.Rotation,
  426. &Agent.Velocity,
  427. &Agent.Energy,
  428. &Agent.Money,
  429. &Agent.Happiness,
  430. &Agent.Class,
  431. &Agent.SubType,
  432. &Agent.PermURL,
  433. &Agent.LastUpdate,
  434. &Agent.BestPath,
  435. &Agent.SecondBestPath,
  436. &Agent.CurrentTarget,
  437. )
  438. if err != nil || !Agent.OwnerKey.Valid {
  439. sendMessageToBrowser("status", "error", fmt.Sprintf("Error %v: no Agent found for UUID %s, or invalid OwnerKey for this agent. Engine cannot run. Aborted. Fix the database and try sending a <code>SIGCONT</code> to restart engine again<br />", err, possibleAgentUUID)," ")
  440. time.Sleep(10 * time.Second)
  441. continue // wait until situation improves...
  442. }
  443. // do the magic to extract the actual coords
  444. Agent.Coords_xyz = strings.Split(strings.Trim(*Agent.Position.Ptr(), "() \t\n\r"), ",")
  445. // we should extract the region name from Agent.Location, but I'm lazy!
  446. Log.Info("Starting to manipulate Agent", *Agent.Name.Ptr(), " (", *Agent.UUID.Ptr(), ")")
  447. // We need to refresh all the data about cubes and positions again!
  448. // do stuff while it runs, e.g. open databases, search for agents and so forth
  449. Log.Debug("Reloading database for Cubes (Positions) and Obstacles...")
  450. // Load in the 'special' objects (cubes). Because the Master Controllers can be somewhere in here, to save code.
  451. // and a database query, we simply skip all the Master Controllers until we get the most recent one, which gets saved
  452. // The rest of the objects are cubes, so we will need them in the ObjectType array (20170722).
  453. // 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).
  454. Cubes = make(map[string]PositionType) // clear array, let the Go garbage collector deal with the memory (20170723)
  455. rows, err := db.Query("SELECT * FROM Positions ORDER BY LastUpdate ASC")
  456. checkErr(err)
  457. for rows.Next() {
  458. err = rows.Scan(
  459. &Position.PermURL,
  460. &Position.UUID,
  461. &Position.Name,
  462. &Position.OwnerName,
  463. &Position.Location,
  464. &Position.Position,
  465. &Position.Rotation,
  466. &Position.Velocity,
  467. &Position.LastUpdate,
  468. &Position.OwnerKey,
  469. &Position.ObjectType,
  470. &Position.ObjectClass,
  471. &Position.RateEnergy,
  472. &Position.RateMoney,
  473. &Position.RateHappiness,
  474. )
  475. checkErr(err)
  476. Position.Coords_xyz = strings.Split(strings.Trim(*Position.Position.Ptr(), "() \t\n\r"), ",")
  477. // check if we got a Master Bot Controller!
  478. if (*Position.ObjectType.Ptr() == "Bot Controller") {
  479. masterController = Position // this will get overwritten until we get the last, most recent one
  480. } else {
  481. Cubes[*Position.UUID.Ptr()] = Position // if not a controller, it must be a cube! add it to array!
  482. }
  483. }
  484. // we need at least ONE masterController, this will be nil if got none (20170807).
  485. if !masterController.PermURL.Valid {
  486. Log.Error(funcName() + ": Major error with database, we need at least one valid masterController to proceed. Sleeping for 10 seconds for user to correct this...")
  487. time.Sleep(10 * time.Second)
  488. continue // go to next iteration, this one has borked data (20170801)
  489. }
  490. // load in everything we found out so far on our region(s) but ignore phantom objects
  491. // end-users ought to set their cubes to phantom as well, or else the agents will think of them as obstacles!
  492. Obstacles = nil
  493. rows, err = db.Query("SELECT * FROM Obstacles WHERE Phantom = 0")
  494. checkErr(err)
  495. for rows.Next() {
  496. err = rows.Scan(
  497. &Object.UUID,
  498. &Object.Name,
  499. &Object.BotKey,
  500. &Object.BotName,
  501. &Object.Type,
  502. &Object.Position,
  503. &Object.Rotation,
  504. &Object.Velocity,
  505. &Object.LastUpdate,
  506. &Object.Origin,
  507. &Object.Phantom,
  508. &Object.Prims,
  509. &Object.BBHi,
  510. &Object.BBLo,
  511. )
  512. checkErr(err)
  513. Object.Coords_xyz = strings.Split(strings.Trim(*Object.Position.Ptr(), "() \t\n\r"), ",")
  514. Obstacles = append(Obstacles, Object)
  515. }
  516. rows.Close()
  517. // Do not trust the database with the exact Agent position: ask the master controller directly
  518. // NOTE(gwyneth): Perhaps it's better to try asking the agent first, and if it refuses answering, try the master controller. (20170813)
  519. // I believe we go through the master controller because the agent might be too busy informing the database about sensor data.
  520. Log.Debug("master controller URL:", *masterController.PermURL.Ptr(), "Agent:", *Agent.Name.Ptr(), "Agent's OwnerKey:", *Agent.OwnerKey.Ptr())
  521. // WHY Agent.Ownerkey?!?! Why not Agent.UUID?!?!?
  522. // The answer is NOT obvious: NPCs created by the master controller are owned by the avatar owning the master controller
  523. // and somehow to contact them we need the ownerkey, which is weird; newer versions of OpenSim are supposed to have fixed
  524. // this by adding a flag for NPCs not to be owned by anyone. Using this might mean to change a lot of code! (20170806)
  525. curPos_raw, err := callURL(*masterController.PermURL.Ptr(), "npc=" + *Agent.OwnerKey.Ptr() + "&command=osNpcGetPos")
  526. // NOTE(gwyneth): Apparently the web server will reply to ALL possible requests, even if the Agent doesn't exist any more;
  527. // I still don't know what to do in that situation, so we skip this cycle and try the next one (20170730).
  528. if curPos_raw == "" || curPos_raw == "No response could be obtained" || err != nil {
  529. Log.Error("Error in figuring out the response for agent", *Agent.Name.Ptr(), "so we will try to skip this cycle...")
  530. continue
  531. }
  532. sendMessageToBrowser("status", "info", "Grid reports that agent '" + *Agent.Name.Ptr() + "' is at position: " + curPos_raw + "...</p>\n", "")
  533. // update database with new position
  534. _, err = db.Exec("UPDATE Agents SET Position =? WHERE OwnerKey =?", strings.Trim(curPos_raw, " ()<>"), *Agent.OwnerKey.Ptr())
  535. checkErr(err)
  536. db.Close()
  537. // sanitize
  538. Agent.Coords_xyz = strings.Split(strings.Trim(curPos_raw, " <>()\t\n\r"), ",")
  539. curPos := make([]float64, 3) // to be more similar to the PHP version
  540. Log.Debug("curPos_raw is", curPos_raw)
  541. _, err = fmt.Sscanf(curPos_raw, "<%f, %f, %f>", &curPos[0], &curPos[1], &curPos[2]) // best way to convert strings to floats! (20170728)
  542. checkErr(err)
  543. sendMessageToBrowser("status", "", fmt.Sprintf("Avatar '%s' (%s) raw position was %v; recalculated to: %v<br />", *Agent.Name.Ptr(), *Agent.Name.Ptr(), curPos_raw, curPos), "")
  544. // Now we select where to go to!
  545. // This will eventually become more complex and *possibly* part of the GA (20170811).
  546. // For now, we just see what attribute is more 'urgent' and choose a cube of the appropriate type.
  547. whatCubeTypeNext := "energy" // by default it will be energy
  548. // convert to floats, we could actually change that in the database but I'm lazy... (20170811)
  549. energyAgent, err := strconv.ParseFloat(*Agent.Energy.Ptr(), 64)
  550. checkErr(err)
  551. moneyAgent, err := strconv.ParseFloat(*Agent.Money.Ptr(), 64)
  552. checkErr(err)
  553. happinessAgent, err := strconv.ParseFloat(*Agent.Happiness.Ptr(), 64)
  554. checkErr(err)
  555. // Simple way to make a choice, but this will get much more complicated in the future (I hope!) (20170811)
  556. if moneyAgent < energyAgent {
  557. whatCubeTypeNext = "money"
  558. }
  559. if (happinessAgent < moneyAgent) && (happinessAgent < energyAgent) {
  560. whatCubeTypeNext = "happiness"
  561. }
  562. Log.Debug(*Agent.Name.Ptr(), "has energy:", energyAgent, "money:", moneyAgent, "happiness:", happinessAgent, "so obviously we will pick a", whatCubeTypeNext, "cube to move to.")
  563. // calculate distances to nearest obstacles and cubes
  564. // TODO(gwyneth): these might become globals, outside the loop, so we don't need to declare them
  565. var smallestDistanceToObstacle = 1024.0 // will be used later on
  566. var nearestObstacle ObjectType
  567. var smallestDistanceToCube = 1024.0 // will be used later on
  568. var nearestCube PositionType
  569. obstaclePosition := make([]float64, 3)
  570. cubePosition := make([]float64, 3)
  571. var distance float64
  572. // pretty-print some nice tables for nearest obstacles and nearest cubes (20170806).
  573. outputBuffer := "<div class='table-responsive'><table class='table table-striped table-bordered table-hover'><caption>Obstacles</caption><thead><tr><th>#</th><th>Name</th><th>Position</th><th>Distance</th></tr></thead><tbody>\n"
  574. for k, point := range Obstacles {
  575. _, err = fmt.Sscanf(*point.Position.Ptr(), "%f, %f, %f", &obstaclePosition[0], &obstaclePosition[1], &obstaclePosition[2])
  576. checkErr(err)
  577. distance = calcDistance(curPos, obstaclePosition)
  578. outputBuffer += fmt.Sprintf("<tr><td>%v</td><td>%s</td><td>%v</td><td>%.4f</td></tr>\n", k, *point.Name.Ptr(), *point.Position.Ptr(), distance)
  579. if distance < smallestDistanceToObstacle {
  580. smallestDistanceToObstacle = distance
  581. nearestObstacle = point
  582. }
  583. }
  584. outputBuffer += "</tbody><tfoot><tr><th>#</th><th>Name</th><th>Position</th><th>Distance</th></tr></tfoot></table></div>\n"
  585. sendMessageToBrowser("status", "", outputBuffer, "")
  586. sendMessageToBrowser("status", "info", fmt.Sprintf("Nearest obstacle to agent %s: '%s' (distance: %.4f m)<br />", *Agent.Name.Ptr(), *nearestObstacle.Name.Ptr(), smallestDistanceToObstacle), "")
  587. // now pretty-print nearest cubes (20170806).
  588. outputBuffer = "<div class='table-responsive'><table class='table table-striped table-bordered table-hover'><caption>Cubes (Positions)</caption><thead><tr><th>UUID</th><th>Name</th><th>Position</th><th>ObjectType</th><th>Distance</th></tr></thead><tbody>\n"
  589. for k, point := range Cubes {
  590. _, err = fmt.Sscanf(*point.Position.Ptr(), "%f, %f, %f", &cubePosition[0], &cubePosition[1], &cubePosition[2])
  591. checkErr(err)
  592. distance = calcDistance(curPos, cubePosition)
  593. point.DistanceToAgent = distance // hope this works, we're saving the distance so that later on we can use this as a weight
  594. outputBuffer += fmt.Sprintf("<tr><td>%v</td><td>%s</td><td>%v</td><td>%s</td><td>%.4f</td></tr>\n", k, *point.Name.Ptr(), *point.Position.Ptr(), *point.ObjectType.Ptr(), point.DistanceToAgent)
  595. if distance < smallestDistanceToCube && *point.ObjectType.Ptr() == whatCubeTypeNext {
  596. smallestDistanceToCube = distance
  597. nearestCube = point
  598. }
  599. }
  600. outputBuffer += "</tbody><tfoot><tr><th>UUID</th><th>Name</th><th>Position</th><th>ObjectType</th><th>Distance</th></tr></tfoot></table></div>\n"
  601. sendMessageToBrowser("status", "", outputBuffer, "")
  602. sendMessageToBrowser("status", "info", fmt.Sprintf("Nearest %s cube to agent %s: '%s' (distance: %.4f m)<br />", whatCubeTypeNext, *Agent.Name.Ptr(), *nearestCube.Name.Ptr(), smallestDistanceToCube), "")
  603. /* Idea for the GA:
  604. 1. Start with a 20x20 matrix (based loosely on Cosío and Castañeda) around the bot, which contain sensor data (we just sense up to 10 m around the bot). This might need adjustment (i.e. smaller size).
  605. - This represents the space of possible solutions
  606. - Active cube will determine attraction point (see later)
  607. - Chromosomes: randomly generated points (inside the 20x20 matrix) that the robot has to travel. Start perhaps with 50 with a length of 28 (Castañeda use 7 for 10x10 matrix). Points are bounded within the 20x20 matrix
  608. Now evaluate each chromosome with fitness function:
  609. - for each point: see if it's "too near" to an obstacle (potential collision)
  610. - ray casts are more precise, so give it a highest weight (not implemented yet)
  611. - normal sensor data give lower weigth
  612. - we can add modifiers: see number of prims of each obstacle (more prims, more weight, because object might be bigger than predicted); see if the obstacle is an agent (initially: agents might act as deflectors; later: interaction matrix will see if the bot goes nearer to the agent or avoids it)
  613. - for each point: see if it's closest to the cube. Lowest distance reduces weight. In theory, we wish to find the path with the least distance (less energy wasted)
  614. - sort chromosomes according to fitness
  615. - do 20 generations and find next expected point. Move bot to it. Reduce energy calculation on bot. See if it dies!
  616. - repeat for next bot position
  617. 20130520 — Results don't converge. It's hard to get the 'bot in less than a 10m radius.
  618. Attempt #2 - use a 10x10 matrix, just 7 points, like Castañeda
  619. Gotshall & Rylander (2002) suggest a population size of about 100-200 for 7 chromosomes
  620. Attempt #3 - Algorithm from Ismail & Sheta was badly implemented!!
  621. Attempt #4 - (to-do) implement Shi & Cui (2010) algorithm for fitness function
  622. Attempt #5 - Shi & Cui (2010) use a strange way to calculate path smoothness. Attempting Qu, Xing & Alexander (2013) which use angles. Modified mutation function, instead of the classical approach (switching two elements in the path), add random ±x, ±y to point
  623. André Neubauer (circular schema theorem, cited by Qu et al.) suggest two-point crossover
  624. Qu et al. suggest sorting path points, after crossover/mutation
  625. */
  626. /* goal/target/attractor: where the 'bot is going to go next
  627. at some point, this ought to be included in the chromosome as well
  628. for now, we'll hard-code it (walk to the nearest cube)
  629. on stage two, we'll do a simple check:
  630. - see what attributes are lowest
  631. - go to the nearest cube that replenishes the attribute
  632. - since this will be iterated every time the 'bot moves, we hope it won't die from starvation,
  633. as moving elsewhere becomes prioritary
  634. */
  635. // nearestCube is where we go (20140526 changing it to selected cube by user, named destCube)
  636. var destCube PositionType
  637. // BUG(gwyneth): Somehow, the code below will just be valid once! (20170728) - this needs more testing, I think
  638. // it was a `clear` somewhere at the end of the iteration, but we got to check it. Also, the submit button for
  639. // changing cube/agent does not go away and the visual feedback is weird (20170806).
  640. // Still working on it, it somehow works sometimes, but it's hard to debug because the GA does so many things (20170813).
  641. Log.Info("User-set destination cube for", *Agent.Name.Ptr(), ":", userDestCube.Load().(string), "(NullUUID means no destination manually set)")
  642. if userDestCube.Load().(string) != NullUUID {
  643. destCube = Cubes[userDestCube.Load().(string)]
  644. Log.Info("User has supplied us with a destination cube for", *Agent.Name.Ptr(), "named:", *destCube.Name.Ptr())
  645. } else {
  646. destCube = nearestCube
  647. Log.Info("Automatically selecting nearest cube for", *Agent.Name.Ptr(), "to go:", *destCube.Name.Ptr())
  648. }
  649. // This is just a test without the GA (20170725)
  650. // Commented out in 20170730 — forgot completely about this!!
  651. /*
  652. sendMessageToBrowser("status", "info", "GA will attempt to move agent '" + *Agent.Name.Ptr() + "' to cube '" + *destCube.Name.Ptr() + "' at position " + *destCube.Position.Ptr(), "")
  653. _, err = callURL(*masterController.PermURL.Ptr(), "npc=" + *Agent.OwnerKey.Ptr() + "&command=osNpcMoveToTarget&vector=<" + *destCube.Position.Ptr() + ">&integer=1")
  654. checkErr(err)
  655. */
  656. time_start := time.Now()
  657. // Genetic algorithm for movement
  658. // generate 50 strings (= individuals in the population) with 28 random points (= 1 chromosome) at curpos ± 10m
  659. population := make([]popType, POPULATION_SIZE) // create a population; unlike PHP, Go has to have a few clues about what is being created (20170726)
  660. // Log.Debug("population len", len(population))
  661. // initialise the slices of chromosomes; Go needs this to know how much memory to allocate (unlike PHP)
  662. for k := range population {
  663. population[k].chromosomes = make([]chromosomeType, CHROMOSOMES)
  664. // Log.Debug("Chromosome", k, "population len", len(population[k].chromosomes))
  665. }
  666. // We calculate now the distance from each point to the destination
  667. // Because this is computationally intensive, we will not repeat it every time during each generation
  668. // Works well unless the destination moves! Then our calculations might be wrong
  669. // But we will catch up on the _next_ iteration (hopefully, unless it moves too fast)
  670. // We also use the best and second best path from a previous run of the GA
  671. // get from the database the last two 'best paths' (if it makes sense)
  672. start_pop := 0 // if we have no best paths, we will generate everything from scratch
  673. // Maybe it makes sense to keep around the last best paths if we're still moving towards the same
  674. // cube; so check for this first, and discard the last best paths if the destination changed
  675. // NOTE(gwyneth): Unlike the PHP version, the Go version deals simultaneously with an automated choice of path as well as manual
  676. // setting of destination, through user input; so the code here is slightly different. We *already* have the
  677. // destination cube in destCube (20170726).
  678. // We already have the cubePosition with the correct data (array of 3 float64 values for x,y,z).
  679. // NOTE(gwyneth): I have a doubt here, when the algorithm runs again, should the agent keep the CurrentTarget in mind? (20170726)
  680. // The PHP code seems to assume that, but it wasn't ready yet for automated runs...
  681. // calculate the center point between current position and target
  682. // needs to be global for path sorting function (Ruhe's algorithm)
  683. // NOTE(gwyneth): in PHP we had a global $centerPoint; Go uses capital letters to designate globality (20170726).
  684. // NOTE(gwyneth): Ruhe's algorithm is not used any more, so we can safely forget this declaration (20170805).
  685. /*
  686. CenterPoint := struct {
  687. x, y, z float64
  688. }{
  689. x: 0.5 * (cubePosition[0] + curPos[0]),
  690. y: 0.5 * (cubePosition[1] + curPos[1]),
  691. z: 0.5 * (cubePosition[2] + curPos[2]),
  692. }
  693. sendMessageToBrowser("status", "", fmt.Sprintf("Center point for this iteration is <%f, %f, %f><br/>", CenterPoint.x, CenterPoint.y, CenterPoint.z), "")
  694. */
  695. // Now generate from scratch the remaining population
  696. for i := start_pop; i < POPULATION_SIZE; i++ {
  697. population[i].Fitness = 0.0
  698. for y := 0; y < CHROMOSOMES; y++ {
  699. // Ismail & Sheta recommend to use the distance between points as part of the fitness
  700. // edge cases: first point, which is the distance to the current position of the agent
  701. // and last point, which is the distance between the last point and the target
  702. // that's why the first and last point have been inserted differently in the population
  703. // Log.Debug("i", i, "y", y)
  704. if y == 0 { // first point is (approx.) current position
  705. population[i].chromosomes[y].x = math.Trunc(curPos[0])
  706. population[i].chromosomes[y].y = math.Trunc(curPos[1])
  707. population[i].chromosomes[y].z = math.Trunc(curPos[2])
  708. } else if y == (CHROMOSOMES - 1) { // last point is (approx.) position of target
  709. population[i].chromosomes[y].x = math.Trunc(cubePosition[0])
  710. population[i].chromosomes[y].y = math.Trunc(cubePosition[1])
  711. population[i].chromosomes[y].z = math.Trunc(cubePosition[2])
  712. } else { // others are scattered around the current position
  713. population[i].chromosomes[y].x = math.Trunc(curPos[0] + (rand.Float64() * 2)*RADIUS - RADIUS)
  714. if population[i].chromosomes[y].x < 0.0 {
  715. population[i].chromosomes[y].x = 0.0
  716. } else if population[i].chromosomes[y].x > 255.0 {
  717. population[i].chromosomes[y].x = 255.0
  718. }
  719. population[i].chromosomes[y].y = math.Trunc(curPos[1] + (rand.Float64() * 2)*RADIUS - RADIUS)
  720. if population[i].chromosomes[y].y < 0.0 {
  721. population[i].chromosomes[y].y = 0.0
  722. } else if population[i].chromosomes[y].y > 255.0 {
  723. population[i].chromosomes[y].y = 255.0
  724. }
  725. population[i].chromosomes[y].z = math.Trunc((cubePosition[2] + curPos[2])/2) // will work for flat terrain but not more
  726. }
  727. // To implement Shi & Cui (2010) or Qu et al. (2013) we add these distances to obstacles together
  728. // If there are no obstacles in our radius, then we keep it clear
  729. population[i].chromosomes[y].obstacle = RADIUS // anything beyond that we don't care
  730. var point ObjectType
  731. for _, point = range Obstacles {
  732. _, err = fmt.Sscanf(*point.Position.Ptr(), "%f, %f, %f", &obstaclePosition[0], &obstaclePosition[1], &obstaclePosition[2])
  733. checkErr(err)
  734. distance = calcDistance([]float64 {population[i].chromosomes[y].x,
  735. population[i].chromosomes[y].y,
  736. population[i].chromosomes[y].z },
  737. obstaclePosition)
  738. // Shi & Cui and Qu et al. apparently just uses the distance to the nearest obstacle
  739. if distance < population[i].chromosomes[y].obstacle {
  740. population[i].chromosomes[y].obstacle = 1/distance
  741. // we use the inverse here, because if we have many distant obstacles it's
  742. // better than a single one that is close by
  743. }
  744. // TODO(gwyneth): obstacles flagged as ray-casting are far more precise, so they ought to be
  745. // more weighted.
  746. // TODO(gwyneth): obstacles could also have bounding box calculations: bigger objects should
  747. // be more weighted. However, HUGE objects might have holes in it. We ought to
  748. // include the bounding box only for ray-casting, or else navigation would be impossible!
  749. // Note that probably OpenSim raycasts only via bounding boxes (need confirmation)
  750. // so maybe this is never a good approach. Lots of tests to be done here!
  751. // NOTE(gwyneth): The latest version of llRayCast, v3 on BulletSim, does NOT use bounding boxes. Confirmed 20170730.
  752. }
  753. if RADIUS - population[i].chromosomes[y].obstacle < 0.00001 { // we use a delta to deal with rounding errors with floats
  754. population[i].chromosomes[y].obstacle = 0.0
  755. }
  756. // calculate, for this point, its distance to the destination, currently $destCube
  757. // (exploded to array $cubePosition)
  758. // might not need this
  759. population[i].chromosomes[y].distance = calcDistance([]float64 { population[i].chromosomes[y].x,
  760. population[i].chromosomes[y].y,
  761. population[i].chromosomes[y].z},
  762. cubePosition)
  763. // abandoned: initialize smoothness for Shi & Cui
  764. // adopted (20140523): smoothness using angles, like Qu et al.
  765. population[i].chromosomes[y].smoothness = 0.0
  766. population[i].chromosomes[y].angle = 0.0 // maybe initialize it here
  767. } // endfor y
  768. // now sort this path. According to Qu et al. (2013) this gets us a smoother path
  769. // hope it's true, because it's computationally intensive
  770. // (20140523) we're using Ruhe's algorithm for sorting according to angle, hope it works
  771. // (20140524) Abandoned Ruhe, using a simple comparison like Qu et al.
  772. //echo "Before sorting, point i is: "; var_dump(population[i]); echo "<br />\n";
  773. /*
  774. $popsort = substr(population[i], 1, -1);
  775. $first_individual = population[i][0];
  776. $last_individual = population[i][CHROMOSOMES - 1];
  777. usort($popsort, 'point_cmp');
  778. population[i] = array_merge((array)$first_individual, $popsort, (array)$last_individual);
  779. */
  780. pop := population[i].chromosomes
  781. sort.Slice(pop, func(a, b int) bool {
  782. // NOTE(gwyneth): these is still the old PHP comments, kept here for historical reasons
  783. // global $centerPoint;
  784. /* Attempt #1: Ruhe's algorithm
  785. $theta_a = atan2($a.y - $centerPoint.y, $a.x - $centerPoint.x);
  786. $angle_a = fmod(M_PI - M_PI_4 + $theta_a, 2 * M_PI);
  787. $theta_b = atan2($b.y - $centerPoint.y, $b.x - $centerPoint.x);
  788. $angle_b = fmod(M_PI - M_PI_4 + $theta_a, 2 * M_PI);
  789. if ($angle_a == $angle_b)
  790. return 0;
  791. return ($angle_a < $angle_b) ? -1 : 1;
  792. */
  793. /*
  794. // Attempt #2: just angles
  795. if ($a.angle == $b.angle)
  796. return 0;
  797. return (abs($a.angle) < abs($b.angle)) ? -1 : 1;
  798. */
  799. // Attempt #3: Just compare x,y! This is a terrible solution but better than nothing
  800. // using algorithm from Anonymous on http://www.php.net/manual/en/function.usort.php
  801. /*
  802. if ($a.x == $b.x)
  803. {
  804. if ($a.y == $b.y)
  805. {
  806. return 0;
  807. }
  808. elseif ($a.y > $b.y)
  809. {
  810. return 1;
  811. }
  812. elseif ($a.y < $b.y)
  813. {
  814. return -1;
  815. }
  816. }
  817. elseif ($a.x > $b.x)
  818. {
  819. return 1;
  820. }
  821. elseif ($a.x < $b.x)
  822. {
  823. return -1;
  824. }
  825. */
  826. // Attempt #4: order by shortest distance to the target?
  827. return pop[a].distance < pop[b].distance
  828. })
  829. } // endfor i
  830. // testing printing the current population (with json we get strange results!)
  831. // showPopulation(population, "Current population")
  832. // We're not finished yet! We need to calculate angles between all points (duh!) to establish smoothness
  833. // Let's do it from scratch:
  834. for i := 0; i < POPULATION_SIZE; i++ {
  835. population[i].chromosomes[0].angle = 0.0 // curPos has (obviously) angle 0
  836. for j := 1; j < CHROMOSOMES; j++ {
  837. population[i].chromosomes[j].angle =
  838. math.Atan2(population[i].chromosomes[j].y - population[i].chromosomes[j-1].y,
  839. population[i].chromosomes[j].x - population[i].chromosomes[j-1].x)
  840. // sendMessageToBrowser("status", "", fmt.Sprintf("Pop %v, Chromosome %v - Angle is %v<br />", i, j, population[i].chromosomes[j].angle), "")
  841. }
  842. }
  843. // Initial population done; now loop over generations
  844. for generation := 0; generation < GENERATIONS; generation++ {
  845. // Calculate fitness
  846. // Log.Debug("Generating fitness for generation ", generation, " (out of ", GENERATIONS, ") for agent", *Agent.Name.Ptr(), "...")
  847. // When calculating a new population, each element will have its chromosomes reordered
  848. // So we have no choice but to calculate fitness for all population elements _again_
  849. for i := 0; i < POPULATION_SIZE; i++ {
  850. fitnessW1 := 0.0
  851. fitnessW2 := 0.0
  852. fitnessW3 := 0.0
  853. // note that first point is current location; we start from the second point onwards
  854. for y := 1; y < CHROMOSOMES; y++ {
  855. // Sub-function of Path Length (using Shi & Cui)
  856. distLastPoint := calcDistance([]float64 {
  857. population[i].chromosomes[y].x,
  858. population[i].chromosomes[y].y,
  859. population[i].chromosomes[y].z,
  860. },
  861. []float64 {
  862. population[i].chromosomes[y-1].x,
  863. population[i].chromosomes[y-1].y,
  864. population[i].chromosomes[y-1].z,
  865. })
  866. // Eduardo: suggests using square distance, means path will have more
  867. // distributed points. (20140704 - 2004)
  868. fitnessW1 += distLastPoint * distLastPoint
  869. // Sub-function of Path Security (using Shi & Cui) — obstacle proximity
  870. fitnessW2 += population[i].chromosomes[y].obstacle
  871. // Sub-function of Smoothness (using Shi & Cui)
  872. // This measures how zig-zaggy the path is, namely, if points are pointing back etc.
  873. // We want a smooth path towards the goal
  874. // Possibly here is where the weight will be added
  875. // Attempt #5: Qu et al. suggest the angle between line segments
  876. // used http://stackoverflow.com/questions/20395547/sorting-an-array-of-x-and-y-vertice-points-ios-objective-c
  877. /* Shi & Cui; abandoned
  878. population[i][$y]["smoothness"] =
  879. (
  880. (population[i][$y-1].y - population[i][$y].y) /
  881. (population[i][$y-1].x - population[i][$y].x)
  882. )
  883. -
  884. (
  885. (population[i][$y].y - population[i][$y-1].y) /
  886. (population[i][$y].x - population[i][$y-1].x)
  887. );
  888. */
  889. // even though Shi & Cui was abandoned, we calculate smoothness nevertheless, only to make sure these calculations work!
  890. // (20170806)
  891. population[i].chromosomes[y].smoothness = ( population[i].chromosomes[y-1].y - population[i].chromosomes[y].y /
  892. population[i].chromosomes[y-1].x - population[i].chromosomes[y].x ) -
  893. ( population[i].chromosomes[y].y - population[i].chromosomes[y-1].y /
  894. population[i].chromosomes[y].x - population[i].chromosomes[y-1].x )
  895. fitnessW3 += population[i].chromosomes[y].angle // clever, huh? check if abs makes sense
  896. // I don't think abs of an angle is a good idea (20170806)
  897. // and we'll also use the overall distance to the attractor
  898. //population[i]["fitness"] += population[i][$y]["distance"];
  899. } // end for y
  900. population[i].Fitness = W1 * fitnessW1 + W2 * fitnessW2 + W3 * fitnessW3
  901. } // end for i
  902. // note that the most critical point is the first: it's the one the 'bot will try to walk to. But we need
  903. // to calculate the rest of the path, too, which is "the best path so far which the bot plans to travel"
  904. // even if at every iteration, it will get calculated over and over again
  905. Log.Debug("CPU time used after fitness calculations for generation ", generation, ": ", time.Since(time_start))
  906. /*
  907. showPopulation(population, fmt.Sprintf("Generation %v] - Before ordering:", generation))
  908. */
  909. // Now we do genetics!
  910. // To pick the 'best' population elements, we need to sort this by fitness, so that the best
  911. // elements are at the top
  912. // order by fitness
  913. sort.Slice(population, func(a, b int) bool {
  914. return population[a].Fitness < population[b].Fitness
  915. })
  916. // TODO(gwyneth): to comment out later (20170727)
  917. showPopulation(population, fmt.Sprintf("Population for agent '%s' [generation %v] after calculating fitness and ordering by fitness follows:", *Agent.Name.Ptr(), generation))
  918. sendMessageToBrowser("status", "", fmt.Sprintf("CPU time used after sorting generation %v for agent '%s': %v<br />\n", generation, *Agent.Name.Ptr(), time.Since(time_start)), "")
  919. // Selection step. We're using fitness rank
  920. newPopulation := make([]popType, POPULATION_SIZE) // create a new population; see comments above
  921. for k := range newPopulation {
  922. newPopulation[k].chromosomes = make([]chromosomeType, CHROMOSOMES)
  923. }
  924. // To introduce elitism, we will move the first 2 elements to the new population:
  925. newPopulation[0] = population[0]
  926. newPopulation[1] = population[1]
  927. // we could also delete the remaining two
  928. // for the remaining population:
  929. for i := 2; i < POPULATION_SIZE; i += 2 {
  930. // establish if we do crossover
  931. if rand.Float64() * 100 < CROSSOVER_RATE {
  932. // find a crossover point; according to André Neubauer, we might need two crossover points
  933. crossover_point := int(math.Trunc(rand.Float64() * CHROMOSOMES))
  934. child0 := make([]chromosomeType, CHROMOSOMES)
  935. child1 := make([]chromosomeType, CHROMOSOMES)
  936. // Log.Debug("Generation ", generation, " - Crossover for ", i, " and ", (i + 1), " happening at crossover point: ", crossover_point)
  937. // now copy the chromosomes from the first parent, up to the crossover point, to child0
  938. // and the remaining chromosomes go to the second child
  939. // simultaneously, do the reverse for the second parent
  940. // there are probably better/faster string manipulation techniques but this is easy to debug
  941. for chromosome := 0; chromosome < CHROMOSOMES; chromosome++ {
  942. if chromosome <= crossover_point {
  943. child0[chromosome] = population[i].chromosomes[chromosome]
  944. child1[chromosome] = population[i+1].chromosomes[chromosome]
  945. } else {
  946. child0[chromosome] = population[i+1].chromosomes[chromosome]
  947. child1[chromosome] = population[i].chromosomes[chromosome]
  948. }
  949. /*
  950. Log.Debug("Pop ", i, ", chromosome: ", chromosome, " Original chromosome: ", population[i].chromosomes[chromosome],
  951. "Child 0 chromosome: ", child0[chromosome])
  952. */
  953. } // endif chromosomes
  954. // test for mutation; note that this is permille and not percent
  955. if rand.Float64() * 1000 < MUTATION_RATE {
  956. /*
  957. Abandoned mutation implementation, which was a classical formulation
  958. for value-based GA (as opposed to bit-based)
  959. // pick two chromosomes for first child, two for second child
  960. // see http://obitko.com/tutorials/genetic-algorithms/crossover-mutation.php
  961. first_chromosome = mt_rand(0, CHROMOSOMES-1);
  962. second_chromosome = mt_rand(0, CHROMOSOMES-1);
  963. // exchange them, by using a temporary holder (this is mostly because
  964. // the exchange might be for the same chromosome!
  965. $temp_first_chromosome = child0[first_chromosome];
  966. $temp_second_chromosome = child0[second_chromosome];
  967. child0[first_chromosome] = $temp_second_chromosome;
  968. child0[second_chromosome] = $temp_first_chromosome;
  969. // echo "Generation " . $generation . " - Mutation happening for child " . i . " — exchanging chromosomes " . first_chromosome . " and " . second_chromosome . "</br>\n";
  970. // same for second child
  971. first_chromosome = mt_rand(0, CHROMOSOMES-1);
  972. second_chromosome = mt_rand(0, CHROMOSOMES-1);
  973. // exchange them, by using a temporary holder (this is mostly because
  974. // the exchange might be for the same chromosome!
  975. $temp_first_chromosome = child1[first_chromosome];
  976. $temp_second_chromosome = child1[second_chromosome];
  977. child1[first_chromosome] = $temp_second_chromosome;
  978. child1[second_chromosome] = $temp_first_chromosome;
  979. // echo "Generation " . $generation . " - Mutation happening for child " . (i + 1) . " — exchanging chromosomes " . first_chromosome . " and " . second_chromosome . "</br>\n";
  980. */
  981. /*
  982. (20140523) Instead, as Qu et al. do, just pick a point and add some
  983. random distance to it
  984. */
  985. first_chromosome := int(math.Trunc(rand.Float64() * (CHROMOSOMES-1)))
  986. second_chromosome := int(math.Trunc(rand.Float64() * (CHROMOSOMES-1)))
  987. child0[first_chromosome].x += (rand.Float64() * 2)*RADIUS/2 - RADIUS/2
  988. if child0[first_chromosome].x < 0 {
  989. child0[first_chromosome].x = 0
  990. } else if child0[first_chromosome].x > 255 {
  991. child0[first_chromosome].x = 255
  992. }
  993. child0[first_chromosome].y += (rand.Float64() * 2)*RADIUS/2 - RADIUS/2
  994. if child0[first_chromosome].y < 0 {
  995. child0[first_chromosome].y = 0
  996. } else if child0[first_chromosome].y > 255 {
  997. child0[first_chromosome].y = 255
  998. }
  999. child1[second_chromosome].x += (rand.Float64() * 2)*RADIUS/2 - RADIUS/2
  1000. if child1[second_chromosome].x < 0 {
  1001. child1[second_chromosome].x = 0
  1002. } else if child1[second_chromosome].x > 255 {
  1003. child1[second_chromosome].x = 255
  1004. }
  1005. child1[second_chromosome].y += (rand.Float64() * 2)*RADIUS/2 - RADIUS/2
  1006. if child1[second_chromosome].y < 0 {
  1007. child1[second_chromosome].y = 0
  1008. } else if child1[second_chromosome].y > 255 {
  1009. child1[second_chromosome].y = 255
  1010. }
  1011. } // endif mutation
  1012. /*
  1013. Log.Debug("Generation ", generation, " - New children for population ", i, ": ", child0, "\nand ",
  1014. (i + 1), ": ", child1)
  1015. */
  1016. /* we need to sort the points on the two childs AGAIN. Duh. And recalculate the angles.
  1017. Duh, duh, duh */
  1018. /*
  1019. child0sort = substr(child0, 1, -1);
  1020. child1sort = substr(child1, 1, -1);
  1021. $first_individual_child0 = child0[0];
  1022. $first_individual_child1 = child1[0];
  1023. $last_individual_child0 = child0[CHROMOSOMES - 1];
  1024. $last_individual_child1 = child1[CHROMOSOMES - 1];
  1025. usort(child0sort, 'point_cmp');
  1026. usort(child1sort, 'point_cmp');
  1027. child0 = array_merge((array)$first_individual_child0, child0sort,
  1028. (array)$last_individual_child0);
  1029. child1 = array_merge((array)$first_individual_child1, child1sort,
  1030. (array)$last_individual_child1);
  1031. */
  1032. sort.Slice(child0, func(a, b int) bool {
  1033. return child0[a].distance < child0[b].distance
  1034. })
  1035. sort.Slice(child1, func(a, b int) bool {
  1036. return child1[a].distance < child1[b].distance
  1037. })
  1038. child0[0].angle = 0.0;
  1039. child0[1].angle = 0.0;
  1040. for j := 1; j < CHROMOSOMES; j++ {
  1041. child0[j].angle = math.Atan2(child0[j].y - child0[j-1].y,
  1042. child0[j].x - child0[j-1].x)
  1043. child1[j].angle = math.Atan2(child1[j].y - child1[j-1].y,
  1044. child1[j].x - child1[j-1].x)
  1045. }
  1046. // add the two children to the new population; fitness will be calculated on next iteration
  1047. newPopulation[i].chromosomes = child0
  1048. newPopulation[i+1].chromosomes = child1
  1049. // endif crossover
  1050. } else {
  1051. // no crossover, just move them directly
  1052. newPopulation[i].chromosomes = population[i].chromosomes
  1053. newPopulation[i+1].chromosomes = population[i+1].chromosomes
  1054. // echo "No crossover for " . i . " and " . (i + 1) . " - moving parents to new population<br />\n";
  1055. }
  1056. // Log.Debug("Pop ", i, "finished")
  1057. }
  1058. Log.Debug("Generation ", generation, " finished")
  1059. population = newPopulation; // prepare population
  1060. // Log.Debug("CPU time used after crossover and mutation up to generation ", generation, ": ", time.Since(time_start))
  1061. } // for generation
  1062. //showPopulation(population, fmt.Sprintf("Final result (%v generation(s)):", GENERATIONS))
  1063. // at the end, the first point (after the current position) for the last population should give us the nearest point to move to
  1064. // ideally, the remaining points should also have converged
  1065. // obviously, as the avatar moves and finds about new obstacles etc. the population will change
  1066. // move to target; integer=1 means "walking" (never "flying")
  1067. //
  1068. // Solution by Eduardo 20140704 — if we're close to the destination, within its radius, then we should
  1069. // move to the last point — which is our current destination!
  1070. // Calculate where we are before we move
  1071. distanceToTarget := calcDistance(curPos, cubePosition)
  1072. var target int // declared here for scope issues (PHP has the ternary operator for dealing with that, Go hasn't)
  1073. if distanceToTarget < RADIUS {
  1074. target = CHROMOSOMES
  1075. } else {
  1076. target = CHROMOSOMES -1
  1077. }
  1078. sendMessageToBrowser("status", "info", fmt.Sprintf("Solution: move agent %s first to (%v, %v, %v) [and follow with %v points]. Distance is %.4f m", *Agent.Name.Ptr(), population[0].chromosomes[1].x, population[0].chromosomes[1].y, population[0].chromosomes[1].z, target - 1, distanceToTarget), "")
  1079. // BUG(gwyneth): Major bug here! Possibly corrected with new approach. (20170805)
  1080. // Basically, we generate the path for the next CHROMOSOME points. But we just need to move the avatar to the NEXT point
  1081. // (i.e. chromosomes[1], because we will recalculate the whole path from then on. On the other hand, we need to print out
  1082. // what the best path was so far, etc. and this will enter the calculations for the next batch of generations and so forth.
  1083. // Nevertheless, the command to move the avatar is just for the NEXT point. (20170805) This should eliminate the 'quirks' of
  1084. // the avatar moving in zig-zag and sometimes even backwards... and hopefully it will avoid them to move towards 0,0,Z...
  1085. // Possibly Go is much faster at calculating new paths than the avatar is in moving to the next one.
  1086. // because Go is so fast at calculating generations, we need to push the commands to give on a separate goroutine
  1087. // which acts as a worker to consume points and wait on them until the avatar has finished walking to the point.
  1088. // We begin with a channel with the capacity of allowing CHROMOSOMES points, maybe this needs to be adjusted in the future
  1089. // Possibly we just need to move to the NEXT point, so the channel capacity would be just one! (20170805)
  1090. movementJobChannel <- movementJob {
  1091. agentUUID: *Agent.OwnerKey.Ptr(),
  1092. masterControllerPermURL: *masterController.PermURL.Ptr(),
  1093. agentPermURL: *Agent.PermURL.Ptr(),
  1094. destPoint: population[0].chromosomes[1],
  1095. }
  1096. for p := 1; p < target; p++ { // (skip first point — current location)
  1097. // This is added for the CSV export, but I don't know if it makes more sense here or in the movementWorker goroutine
  1098. export_rows = append(export_rows, fmt.Sprintf("%f,%f,%f;", population[0].chromosomes[p].x, population[0].chromosomes[p].y, population[0].chromosomes[p].z))
  1099. }
  1100. // Save two best solutions for next iteration; attempts to avoid to recalculate always from scratch
  1101. // We do it after moving because the avatar needs a few seconds to reach destination
  1102. // Reopen database, we need to write out the new Agent data
  1103. db, err = sql.Open(PDO_Prefix, GoBotDSN)
  1104. checkErr(err)
  1105. // now update our database with the best paths and the target
  1106. stmt, err := db.Prepare("UPDATE Agents SET BestPath=?, SecondBestPath=?, CurrentTarget=? WHERE UUID=?")
  1107. if err != nil {
  1108. sendMessageToBrowser("status", "error", fmt.Sprintf("%v: Updating database with best path, second best path, and current target for agent %s - prepare failed: %s",
  1109. funcName(), *Agent.Name.Ptr(), err), "")
  1110. }
  1111. marshalled0, err := json.Marshal(population[0])
  1112. checkErr(err)
  1113. marshalled1, err := json.Marshal(population[1])
  1114. checkErr(err)
  1115. _, err = stmt.Exec(marshalled0,
  1116. marshalled1,
  1117. strings.Trim(*destCube.Position.Ptr(), " \t"),
  1118. *Agent.UUID.Ptr())
  1119. if err != nil {
  1120. sendMessageToBrowser("status", "error", fmt.Sprintf("%v: Updating database with best path, second best path, and current target for agent %s failed: %s",
  1121. funcName(), *Agent.Name.Ptr(), err), "")
  1122. }
  1123. stmt.Close()
  1124. db.Close()
  1125. // See if we're close to the target; absolute precision might be impossible
  1126. // first, read position again, just to see what we get
  1127. curposResult, err := callURL(*masterController.PermURL.Ptr(), "npc=" + *Agent.OwnerKey.Ptr() + "&command=osNpcGetPos")
  1128. if err == nil {
  1129. sendMessageToBrowser("status", "info", fmt.Sprintf("Grid reports that agent %s is at position: %v",
  1130. *Agent.Name.Ptr(), curposResult), "")
  1131. } else {
  1132. sendMessageToBrowser("status", "error", "We cannot get report from the grid for the position of agent " + *Agent.Name.Ptr(), "")
  1133. }
  1134. /*
  1135. echo "This is how Destination Cube looks like: <br \>\n";
  1136. var_dump($destCube);
  1137. echo "<br />\n";
  1138. */
  1139. currentPosition := make([]float64, 3) // see comment above for doing the same to curPos (20170728)
  1140. _, err = fmt.Sscanf(strings.Trim(curposResult, " ()<>"), "%f,%f,%f", &currentPosition[0], &currentPosition[1], &currentPosition[2])
  1141. checkErr(err)
  1142. /*
  1143. echo "Distance comparison: Current Position:<br />\n";
  1144. var_dump($currentPosition);
  1145. echo "Target Cube<br />\n";
  1146. var_dump($targetCube);
  1147. echo "<br />\n";
  1148. */
  1149. distance = calcDistance(cubePosition, currentPosition) // this is how much is missing to reach destination
  1150. if distance < 1.1 { // we might never get closer than this due to rounding errors
  1151. sendMessageToBrowser("status", "info", fmt.Sprintf("Within rounding errors of %s, distance is merely %.4f m; let's sit %s down", *destCube.Name.Ptr(), distance, *Agent.Name.Ptr()), "")
  1152. // if we're close enough, sit on it
  1153. sitResult, err := callURL(*masterController.PermURL.Ptr(), "npc=" + *Agent.OwnerKey.Ptr() + "&command=osNpcSit&key=" + *destCube.UUID.Ptr() + "&integer=" + OS_NPC_SIT_NOW)
  1154. if (err == nil) {
  1155. sendMessageToBrowser("status", "info", "Result from " + *Agent.Name.Ptr() + " sitting: " + sitResult, "")
  1156. } else {
  1157. sendMessageToBrowser("status", "error", "Grid error when trying to sit " + *Agent.Name.Ptr(), "")
  1158. }
  1159. } else if distance < 2.5 {
  1160. sendMessageToBrowser("status", "warning", fmt.Sprintf("%s is very close to %s, distance is now %.4f m", *Agent.Name.Ptr(), *destCube.Name.Ptr(), distance), "")
  1161. } else {
  1162. sendMessageToBrowser("status", "warning", fmt.Sprintf("%s is still %.4f m away from %s (%v, %v, %v)",
  1163. *Agent.Name.Ptr(),
  1164. distance,
  1165. *destCube.Name.Ptr(),
  1166. cubePosition[0],
  1167. cubePosition[1],
  1168. cubePosition[2]), "")
  1169. }
  1170. // Now place a button to save/export to CSV or XML
  1171. // TODO(gwyneth): this was on the original code but no button was there; need to see where it is (20170728)
  1172. // If the user had set agent + cube, clean them up for now
  1173. // They never get cleaned! That's the whole point! Unless of course the user WANTS them cleaned (20170729)
  1174. //userDestCube.Store(NullUUID)
  1175. //curAgent.Store(NullUUID)
  1176. sendMessageToBrowser("status", "info", fmt.Sprintf("CPU time used: %v", time.Since(time_start)), "")
  1177. // output something to console so that we know this is being run in parallel
  1178. /*
  1179. fmt.Print("\r|")
  1180. time.Sleep(1000 * time.Millisecond)
  1181. fmt.Print("\r/")
  1182. time.Sleep(1000 * time.Millisecond)
  1183. fmt.Print("\r-")
  1184. time.Sleep(1000 * time.Millisecond)
  1185. fmt.Print("\r\\")
  1186. time.Sleep(1000 * time.Millisecond)
  1187. */
  1188. // if we're set to run only once then stop, change atomic values accordingly
  1189. if OneStep.Load().(bool) {
  1190. OneStep.Store(false)
  1191. EngineRunning.Store(false)
  1192. sendMessageToBrowser("htmlControl", "enable", "", "startEngine")
  1193. sendMessageToBrowser("htmlControl", "enable", "", "oneStep")
  1194. sendMessageToBrowser("htmlControl", "disable", "", "stopEngine")
  1195. }
  1196. } else {
  1197. // stop everything!!!
  1198. // in theory this is used to deal with reconfigurations etc.
  1199. fmt.Print("\r𝔷")
  1200. time.Sleep(1000 * time.Millisecond)
  1201. fmt.Print("\rz")
  1202. time.Sleep(1000 * time.Millisecond)
  1203. fmt.Print("\rZ")
  1204. time.Sleep(1000 * time.Millisecond)
  1205. fmt.Print("\rℤ")
  1206. time.Sleep(1000 * time.Millisecond)
  1207. }
  1208. } // end for (endless loop here)
  1209. // Why should we ever stop? :)
  1210. sendMessageToBrowser("status", "success", "this is the engine <i>stopping</i>", "")
  1211. }
  1212. // sendMessageToBrowser sends a string to the internal, global channel which is picked up by the websocket handling goroutine.
  1213. // In the case of special status messages (info, success, warning, error) we also send the same message to the log.
  1214. // If no WebSocket is active (and we check that in two different ways!) the message simply goes to the log instead.
  1215. func sendMessageToBrowser(msgType string, msgSubType string, msgText string, msgId string) {
  1216. text, err := html2text.FromString(msgText, html2text.Options{PrettyTables: true}) // prettify eventual HTML inside msgText
  1217. checkErr(err)
  1218. if webSocketActive.Load() != nil && webSocketActive.Load().(bool) { // no point in sending if nobody is there to receive
  1219. var msgToSend WsMessageType
  1220. msgToSend.New(msgType, msgSubType, msgText, msgId)
  1221. // Go idiomatic programming: 'select' parallels the output of the two cases and picks the one which finishes; in this case, either
  1222. // we are able to send a message via the channel, or there is a timeout, and Go picks what happens first
  1223. select {
  1224. case wsSendMessage <- msgToSend:
  1225. // we use this so often as info/warning/error message that we may better send it also to the log
  1226. if msgType == "status" && msgSubType != "" {
  1227. switch msgSubType {
  1228. case "info":
  1229. Log.Info("(connected via WebSocket)", msgType, "-", msgSubType, "-", text, "-", msgId)
  1230. case "notice":
  1231. Log.Notice("(connected via WebSocket)", msgType, "-", msgSubType, "-", text, "-", msgId)
  1232. case "success":
  1233. Log.Notice("(connected via WebSocket)", msgType, "-", msgSubType, "-", text, "-", msgId)
  1234. case "warning":
  1235. Log.Warning("(connected via WebSocket)", msgType, "-", msgSubType, "-", text, "-", msgId)
  1236. case "error":
  1237. Log.Error("(connected via WebSocket)", msgType, "-", msgSubType, "-", text, "-", msgId)
  1238. case "critical":
  1239. Log.Critical("(connected via WebSocket)", msgType, "-", msgSubType, "-", text, "-", msgId)
  1240. default:
  1241. Log.Debug("(connected via WebSocket)", msgType, "-", msgSubType, "-", text, "-", msgId)
  1242. }
  1243. } else {
  1244. Log.Debug("(connected via WebSocket)", msgType, "-", msgSubType, "-", text, "-", msgId)
  1245. }
  1246. // 'common' messages have the nil string subtype, so we ignore these and don't log them
  1247. // we might have a Debug facility in the future which allows for more verbosity!
  1248. case <-time.After(time.Second * 10):
  1249. // this case exists only if we failed to figure out if the WebSocket is active or not; in most cases, we will
  1250. // be able to know that in advance, but here we catch the edge cases.
  1251. Log.Warning("WebSocket timeout after 10 seconds; coudn't send message:", msgType, "-", msgSubType, "-", text, "-", msgId)
  1252. }
  1253. } else {
  1254. // No active WebSocket? Just dump it to the log. Note that this will be the most usual case, since we hardly expect users to be 24/7 in
  1255. // front of their browsers...
  1256. if msgType == "status" && msgSubType != "" {
  1257. switch msgSubType {
  1258. case "info":
  1259. Log.Info("(no WebSocket connection)", msgType, "-", msgSubType, "-", text, "-", msgId)
  1260. case "notice":
  1261. Log.Notice("(no WebSocket connection)", msgType, "-", msgSubType, "-", text, "-", msgId)
  1262. case "success":
  1263. Log.Notice("(no WebSocket connection)", msgType, "-", msgSubType, "-", text, "-", msgId)
  1264. case "warning":
  1265. Log.Warning("(no WebSocket connection)", msgType, "-", msgSubType, "-", text, "-", msgId)
  1266. case "error":
  1267. Log.Error("(no WebSocket connection)", msgType, "-", msgSubType, "-", text, "-", msgId)
  1268. case "critical":
  1269. Log.Critical("(no WebSocket connection)", msgType, "-", msgSubType, "-", text, "-", msgId)
  1270. default:
  1271. Log.Debug("(no WebSocket connection)", msgType, "-", msgSubType, "-", text, "-", msgId)
  1272. }
  1273. } else {
  1274. Log.Debug("(no WebSocket connection)", msgType, "-", msgSubType, "-", text, "-", msgId)
  1275. }
  1276. }
  1277. }
  1278. // callURL encapsulates a call to an URL. It exists as an analogy to the PHP version (20170723).
  1279. func callURL(url string, encodedRequest string) (string, error) {
  1280. // HTTP request as per http://moazzam-khan.com/blog/golang-make-http-requests/
  1281. body := []byte(encodedRequest)
  1282. // Log.Debugf("%s: URL: %s Encoded Request: %s\n", funcName(), url, encodedRequest)
  1283. rs, err := http.Post(url, "application/x-www-form-urlencoded", bytes.NewBuffer(body))
  1284. if err != nil { errMsg := fmt.Sprintf("HTTP call to %s failed; error was: '%v'", url, err)
  1285. Log.Error(errMsg)
  1286. return errMsg, err
  1287. }
  1288. defer rs.Body.Close()
  1289. rsBody, err := ioutil.ReadAll(rs.Body)
  1290. // Check for errors; if errors found, then send the error message back to the caller
  1291. if err != nil {
  1292. errMsg := fmt.Sprintf("error response from in-world object: '%v'", err)
  1293. Log.Error(errMsg)
  1294. return errMsg, err
  1295. } else {
  1296. if string(rsBody) == "No response could be obtained" { // weird case, but apparently it can happen!
  1297. err = errors.New("No response could be obtained")
  1298. }
  1299. Log.Debugf("Reply from in-world object: '%s'; error was %v\n", rsBody, err)
  1300. return string(rsBody), err
  1301. }
  1302. }
  1303. // showPopulation is adapted from the PHP code to pretty-print a whole population
  1304. // new version creates HTML tables
  1305. func showPopulation(popul []popType, popCaption string) {
  1306. if !ShowPopulation { // this might be the beginning of a debug level configuration type; currently we have the options from the go-logging pkg (20170813).
  1307. return
  1308. }
  1309. outputBuffer := "<div class='table-responsive'><table class='table table-striped table-bordered table-hover'><caption>" + popCaption + "</caption><thead><tr><th>Pop #</th><th>Fitness</th><th>Chromossomes</th></tr></thead><tbody>\n"
  1310. for p, pop := range popul {
  1311. outputBuffer += fmt.Sprintf("<tr><td>%v</td><td>%.4f</td>", p, pop.Fitness)
  1312. for _, chr := range pop.chromosomes {
  1313. outputBuffer += fmt.Sprintf("<td>(%v, %v, %v)<br />Distance: %.4f<br />Obstacle: %.4f<br />Angle: %.4f<br />Smoothness %.4f</td>",
  1314. chr.x, chr.y, chr.z, chr.distance, chr.obstacle, chr.angle, chr.smoothness)
  1315. }
  1316. outputBuffer += "</tr>\n"
  1317. }
  1318. outputBuffer += "</tbody><tfoot><tr><th>Pop #</th><th>Fitness</th><th>Chromossomes</th></tr></tfoot></table></div>\n"
  1319. sendMessageToBrowser("status", "", outputBuffer, "")
  1320. }
  1321. // movementWorker reads one point from the movementJobChannel and sends a command to the avatar to move to it, and recalculates energy.
  1322. // Then it blocks for the necessary amount of estimated time for the avatar to reach that destination until it reads the next
  1323. // point. Right now, the channel accepts CHROMOSOME jobs at a time (20170730).
  1324. // Finally, it calculates how much energy the avatar has spent for the distance travelled (20170811).
  1325. // TODO(gwyneth): probably happiness is also affected, we have to think about a new formula for that (20170811).
  1326. func movementWorker() {
  1327. var nextPoint movementJob
  1328. curPos := make([]float64, 3) // no need to allocate this over and over again
  1329. newPos := make([]float64, 3) // this is the position that the avatar managed to travel to after this iteration
  1330. for { // once started, never stops?
  1331. nextPoint = <-movementJobChannel // consume one point from the channel
  1332. // these must be set, nothing like a bit of error checking for eventual bugs elsewhere
  1333. if nextPoint.masterControllerPermURL == "" || nextPoint.agentUUID == "" || nextPoint.agentPermURL == "" {
  1334. continue // either the next job comes valid, or we continue to consume points until a valid one comes along
  1335. }
  1336. // The code below was commented in the PHP code, but we reuse it here as it was because it makes sense! (20170730)
  1337. // How much should we wait? Well, we calculate the distance to the next point! And since avatars move pretty much
  1338. // at the same speed all the time, we can make a rough estimate of the time they will take.
  1339. // ask the avatar where it is
  1340. curPosResult, err := callURL(nextPoint.agentPermURL, "command=osNpcGetPos")
  1341. checkErr(err)
  1342. if err != nil {
  1343. // we have to assume this will never work, so we skip to the next case
  1344. continue
  1345. }
  1346. // convert string result to array of floats
  1347. _, err = fmt.Sscanf(strings.Trim(curPosResult, " ()<>"), "%f,%f,%f", &curPos[0], &curPos[1], &curPos[2])
  1348. checkErr(err)
  1349. walkingDistance := calcDistance(
  1350. curPos, []float64 { nextPoint.destPoint.x, nextPoint.destPoint.y, nextPoint.destPoint.z })
  1351. timeToTravel := walkingDistance / WALKING_SPEED // we might adjust this to assume the in-world calls took some time as well
  1352. // do not wait too long, though!
  1353. if timeToTravel > 5.0 {
  1354. timeToTravel = 5.0
  1355. }
  1356. sendMessageToBrowser("status", "", fmt.Sprintf("[%s]: Next point at %.4f metres; waiting %.4f secs for avatar %s to go to next point...<br />",
  1357. funcName(), walkingDistance, timeToTravel, nextPoint.agentUUID), "")
  1358. moveResult, err := callURL(nextPoint.agentPermURL,
  1359. fmt.Sprintf("command=osNpcMoveToTarget&vector=<%v,%v,%v>&integer=1",
  1360. nextPoint.destPoint.x, nextPoint.destPoint.y, nextPoint.destPoint.z))
  1361. checkErr(err)
  1362. if err != nil {
  1363. // we lost the ability to send messages; what to do now? Well, we can ignore this, the GA will
  1364. // just require a new cycle
  1365. continue
  1366. }
  1367. sendMessageToBrowser("status", "", fmt.Sprintf("[%s]: In-world result call from moving %s to (%v, %v, %v): %s<br />",
  1368. funcName(), nextPoint.agentUUID, nextPoint.destPoint.x, nextPoint.destPoint.y, nextPoint.destPoint.z, moveResult), "")
  1369. time.Sleep(time.Second * time.Duration(timeToTravel))
  1370. // ask the avatar AGAIN where it is, since it MIGHT not have reached the destination we expect it to reach (20170811).
  1371. newPosResult, err := callURL(nextPoint.agentPermURL, "command=osNpcGetPos")
  1372. checkErr(err)
  1373. // convert string result to array of floats
  1374. _, err = fmt.Sscanf(strings.Trim(newPosResult, " ()<>"), "%f,%f,%f", &newPos[0], &newPos[1], &newPos[2])
  1375. checkErr(err)
  1376. travelled := calcDistance(newPos, curPos) // see how much we've actually travelled
  1377. // calculate how much energy we've lost so far
  1378. energyLost := travelled / WALKING_SPEED // some stupid formula, it doesn't matter, it's just to affect the counters
  1379. // let the bloody avatar subtract some energy!
  1380. energyResult, err := callURL(nextPoint.agentPermURL, "command=getEnergy")
  1381. checkErr(err)
  1382. energyAgent, err := strconv.ParseFloat(energyResult, 64)
  1383. checkErr(err)
  1384. sendMessageToBrowser("status", "", fmt.Sprintf("[%s]: %s had %f energy; lost %f on movement<br />", funcName(), nextPoint.agentUUID, energyAgent, energyLost), "")
  1385. energyAgent -= energyLost
  1386. energyResult, err = callURL(nextPoint.agentPermURL, fmt.Sprintf("command=setEnergy&float=%f", energyAgent))
  1387. checkErr(err)
  1388. sendMessageToBrowser("status", "", fmt.Sprintf("[%s]: %s updated to new energy level %f; in.world reply: %v<br />", funcName(), nextPoint.agentUUID, energyAgent, energyResult), "")
  1389. // update on database as well
  1390. db, err := sql.Open(PDO_Prefix, GoBotDSN)
  1391. checkErr(err)
  1392. defer db.Close()
  1393. stmt, err := db.Prepare("UPDATE Agents SET `Energy`=? WHERE OwnerKey=?")
  1394. if (err != nil) {
  1395. sendMessageToBrowser("status", "error", fmt.Sprintf("Agent '%s' could not be prepared in database with new energy settings; database reply was: '%v'", nextPoint.agentUUID, err), "")
  1396. }
  1397. defer stmt.Close()
  1398. execResult, err := stmt.Exec(energyAgent, nextPoint.agentUUID)
  1399. if (err != nil) {
  1400. sendMessageToBrowser("status", "error", fmt.Sprintf("Agent '%s' could not be updated in database with new energy settings; database reply was: '%v'", nextPoint.agentUUID, err), "")
  1401. } else {
  1402. id, err := execResult.LastInsertId()
  1403. rowsAffected, err2 := execResult.RowsAffected()
  1404. Log.Debug("Result from executing the energy update was:", id, err, "Rows affected:", rowsAffected, err2)
  1405. }
  1406. Log.Debug("Agent", nextPoint.agentUUID, "updated database with new energy:", energyAgent)
  1407. stmt.Close()
  1408. db.Close()
  1409. }
  1410. }