@@ -5,23 +5,23 @@ package scalatron.botwar
5
5
6
6
import scala .util .Random
7
7
import scalatron .scalatron .impl .TournamentRoundResult
8
- import akka .util .Duration
9
8
import akka .util .duration ._
10
9
import akka .dispatch ._
11
10
import java .util .concurrent .TimeoutException
11
+ import akka .actor .ActorSystem
12
12
13
13
14
14
/** Game dynamics. Function that, when applied to a game state, returns either a successor
15
15
* game state or a game result.
16
16
*/
17
- case object Dynamics extends ((State , Random , ExecutionContext ) => Either [State ,TournamentRoundResult ])
17
+ case object Dynamics extends ((State , Random , ActorSystem , ExecutionContext ) => Either [State ,TournamentRoundResult ])
18
18
{
19
- def apply (state : State , rnd : Random , executionContextForUntrustedCode : ExecutionContext ) = {
19
+ def apply (state : State , rnd : Random , actorSystem : ActorSystem , executionContextForUntrustedCode : ExecutionContext ) = {
20
20
// determine which bots are eligible to move in the nest step
21
21
val eligibleBots = computeEligibleBots(state)
22
22
23
23
// have each bot compute its command
24
- val botCommandsAndTimes = computeBotCommands(state, eligibleBots)(executionContextForUntrustedCode) // was: actorSystem
24
+ val botCommandsAndTimes = computeBotCommands(state, eligibleBots, actorSystem )(executionContextForUntrustedCode) // was: actorSystem
25
25
26
26
// store time taken with each bot
27
27
var updatedBoard = state.board
@@ -100,11 +100,12 @@ case object Dynamics extends ((State, Random, ExecutionContext) => Either[State,
100
100
}
101
101
})
102
102
103
+ type BotResponse = (Entity .Id ,(Long ,String ,Iterable [Command ]))
103
104
104
105
// returns a collection of tuples: (id, (nanoSeconds, commandList))
105
- def computeBotCommands (state : State , eligibleBots : Iterable [Bot ])(implicit executionContextForUntrustedCode : ExecutionContext ): Iterable [(Entity .Id ,(Long ,String ,Iterable [Command ]))] = {
106
- // use Akka to work this out
107
- val future = Future .traverse(eligibleBots)(bot => Future {
106
+ def computeBotCommands (state : State , eligibleBots : Iterable [Bot ], actorSystem : ActorSystem )(implicit executionContextForUntrustedCode : ExecutionContext ): Iterable [(Entity .Id ,(Long ,String ,Iterable [Command ]))] = {
107
+ // use Akka Futures to work this out
108
+ val outerFuture = Future .traverse(eligibleBots)(bot => Future {
108
109
try {
109
110
val timeBefore = System .nanoTime
110
111
val (inputString,commands) = bot.respondTo(state)
@@ -121,9 +122,71 @@ case object Dynamics extends ((State, Random, ExecutionContext) => Either[State,
121
122
}
122
123
})
123
124
125
+
126
+ /*
127
+ // This is based on Viktor Klang's proposal, see https://groups.google.com/forum/?fromgroups#!topic/akka-user/5MY8lsYGbYM
128
+ val TimeoutSentinel : Option[BotResponse] = None
129
+ val timeout = Promise[Option[BotResponse]]()
130
+ val c = actorSystem.scheduler.scheduleOnce(1000 millis){ timeout.success(TimeoutSentinel) }
131
+ timeout onComplete { case _ => c.cancel() }
132
+
133
+ val outerFuture = Future.traverse(eligibleBots)(bot =>
134
+ Promise[Option[BotResponse]]()
135
+ .completeWith(Future {
136
+ try {
137
+ val timeBefore = System.nanoTime
138
+ val (inputString,commands) = bot.respondTo(state)
139
+ val timeSpent = System.nanoTime - timeBefore
140
+ if(commands.isEmpty) None else Some((bot.id,(timeSpent,inputString,commands)))
141
+ } catch {
142
+ case t: NoClassDefFoundError =>
143
+ // we fake a Log() command issued by the bot to report the error into the browser UI:
144
+ Some((bot.id,(0L,"",Iterable[Command](Command.Disable("error: class not found: " + t.getMessage)))))
145
+
146
+ case t: Throwable =>
147
+ // we inject a Disable() command as-if-issued-by-the-bot to report the error into the browser UI:
148
+ Some((bot.id,(0L,"",Iterable[Command](Command.Disable(t.getMessage)))))
149
+ }
150
+ })
151
+ .completeWith(timeout)
152
+ )
153
+ */
154
+ /*
155
+ // this is an attempt based on nested Futures. Does not seems to work at all: every call times out. ?!?
156
+ val TimeoutSentinel : Option[BotResponse] = None
157
+ val outerFuture = Future.traverse(eligibleBots)(bot =>
158
+ Future {
159
+ val innerFuture = Future {
160
+ try {
161
+ val timeBefore = System.nanoTime
162
+ val (inputString,commands) = bot.respondTo(state)
163
+ val timeSpent = System.nanoTime - timeBefore
164
+ if(commands.isEmpty) None else Some((bot.id,(timeSpent,inputString,commands)))
165
+ } catch {
166
+ case t: NoClassDefFoundError =>
167
+ // we fake a Log() command issued by the bot to report the error into the browser UI:
168
+ Some((bot.id,(0L,"",Iterable[Command](Command.Disable("error: class not found: " + t.getMessage)))))
169
+
170
+ case t: Throwable =>
171
+ // we inject a Disable() command as-if-issued-by-the-bot to report the error into the browser UI:
172
+ Some((bot.id,(0L,"",Iterable[Command](Command.Disable(t.getMessage)))))
173
+ }
174
+ }
175
+ try {
176
+ Await.result(innerFuture, 5000 millis) // generous timeout - for now, we only want to capture infinite loops
177
+ } catch {
178
+ case t: TimeoutException =>
179
+ System.err.println("warning: timeout while invoking the control function of bot: " + bot.name)
180
+ TimeoutSentinel
181
+ }
182
+ }
183
+ )
184
+ */
185
+
124
186
// Note: an overall timeout across all bots is a temporary solution - we want timeouts PER BOT
125
187
try {
126
- val result = Await .result(future, 2000 millis) // generous timeout - note that this is over ALL plug-ins
188
+ val result = Await .result(outerFuture, 10000 millis) // generous timeout - note that this is over ALL plug-ins
189
+ // result.filter(TimeoutSentinel ==).foreach(r => println("bot timed out"))
127
190
result.flatten
128
191
} catch {
129
192
case t : TimeoutException =>
0 commit comments