11package bwapi ;
22
3- import java .util .concurrent .locks .ReentrantLock ;
4-
53/**
64 * Manages invocation of bot event handlers
75 */
86class BotWrapper {
9- private final ClientData liveClientData = new ClientData ();
10- private final BWClientConfiguration configuration ;
11- private final BWEventListener eventListener ;
12- private final FrameBuffer frameBuffer ;
13- private WrappedBuffer liveData ;
14- private Game botGame ;
15- private Thread botThread ;
16- private boolean gameOver ;
17- private PerformanceMetrics performanceMetrics ;
18- private Throwable lastBotThrow ;
19- private final ReentrantLock lastBotThrowLock = new ReentrantLock ();
20- private final ReentrantLock unsafeReadReadyLock = new ReentrantLock ();
21- private boolean unsafeReadReady = false ;
7+ protected final BWClientConfiguration configuration ;
8+ protected final BWEventListener eventListener ;
9+
10+ protected Game botGame ;
11+ protected boolean gameOver ;
12+ protected PerformanceMetrics performanceMetrics ;
2213
2314 BotWrapper (BWClientConfiguration configuration , BWEventListener eventListener ) {
2415 this .configuration = configuration ;
2516 this .eventListener = eventListener ;
26- frameBuffer = configuration .getAsync () ? new FrameBuffer (configuration ) : null ;
2717 }
2818
2919 /**
3020 * Resets the BotWrapper for a new botGame.
3121 */
3222 void startNewGame (WrappedBuffer liveData , PerformanceMetrics performanceMetrics ) {
33- if (configuration .getAsync ()) {
34- frameBuffer .initialize (liveData , performanceMetrics );
35- }
3623 this .performanceMetrics = performanceMetrics ;
3724 botGame = new Game ();
3825 botGame .setConfiguration (configuration );
3926 botGame .botClientData ().setBuffer (liveData );
40- liveClientData .setBuffer (liveData );
41- this .liveData = liveData ;
42- botThread = null ;
4327 gameOver = false ;
4428 }
4529
@@ -51,204 +35,25 @@ Game getGame() {
5135 return botGame ;
5236 }
5337
54- private boolean isUnsafeReadReady () {
55- unsafeReadReadyLock .lock ();
56- try { return unsafeReadReady ; }
57- finally { unsafeReadReadyLock .unlock (); }
58- }
59-
60- private void setUnsafeReadReady (boolean value ) {
61- unsafeReadReadyLock .lock ();
62- try { unsafeReadReady = value ; }
63- finally { unsafeReadReadyLock .unlock (); }
64- frameBuffer .lockSize .lock ();
65- try {
66- frameBuffer .conditionSize .signalAll ();
67- } finally {
68- frameBuffer .lockSize .unlock ();
69- }
70- }
71-
7238 /**
7339 * Handles the arrival of a new frame from BWAPI
7440 */
7541 void onFrame () {
76- if (configuration .getAsync ()) {
77- configuration .log ("Main: onFrame asynchronous start" );
78- asyncOnFrame ();
79- configuration .log ("Main: onFrame asynchronous end" );
80- } else {
81- configuration .log ("Main: onFrame synchronous start" );
82- handleEvents ();
83- configuration .log ("Main: onFrame synchronous end" );
84- }
42+ configuration .log ("Main: onFrame synchronous start" );
43+ handleEvents ();
44+ configuration .log ("Main: onFrame synchronous end" );
8545 }
8646
87- void asyncOnFrame () {
88- long startNanos = System .nanoTime ();
89- long endNanos = startNanos + (long ) configuration .getMaxFrameDurationMs () * 1000000 ;
90- if (botThread == null ) {
91- configuration .log ("Main: Starting bot thread" );
92- botThread = createBotThread ();
93- botThread .setName ("JBWAPI Bot" );
94- // Reduced priority helps ensure that StarCraft.exe/BWAPI pick up on our frame completion in timely fashion
95- botThread .setPriority (3 );
96- botThread .start ();
97- }
47+ void endGame () { }
9848
99- // Unsafe mode:
100- // If the frame buffer is empty (meaning the bot must be idle)
101- // allow the bot to read directly from shared memory while we copy it over
102- if (configuration .getAsyncUnsafe ()) {
103- frameBuffer .lockSize .lock ();
104- try {
105- if (frameBuffer .empty ()) {
106- configuration .log ("Main: Putting bot on live data" );
107- botGame .botClientData ().setBuffer (liveData );
108- setUnsafeReadReady (true );
109- } else {
110- setUnsafeReadReady (false );
111- }
112- } finally {
113- frameBuffer .lockSize .unlock ();
114- }
115- }
116-
117- // Add a frame to buffer
118- // If buffer is full, will wait until it has capacity.
119- // Then wait for the buffer to empty or to run out of time in the frame.
120- int frame = liveClientData .gameData ().getFrameCount ();
121- configuration .log ("Main: Enqueuing frame #" + frame );
122- frameBuffer .enqueueFrame ();
123-
124- configuration .log ("Main: Enqueued frame #" + frame );
125- if (frame > 0 ) {
126- performanceMetrics .getClientIdle ().startTiming ();
127- }
128- frameBuffer .lockSize .lock ();
129- try {
130- while (!frameBuffer .empty ()) {
131- // Unsafe mode: Move the bot off of live data onto the frame buffer
132- // This is the unsafe step!
133- // We don't synchronize on calls which access the buffer
134- // (to avoid tens of thousands of synchronized calls per frame)
135- // so there's no guarantee of safety here.
136- if (configuration .getAsyncUnsafe () && frameBuffer .size () == 1 ) {
137- configuration .log ("Main: Weaning bot off live data" );
138- botGame .botClientData ().setBuffer (frameBuffer .peek ());
139- }
140-
141- // Make bot exceptions fall through to the main thread.
142- Throwable lastThrow = getLastBotThrow ();
143- if (lastThrow != null ) {
144- configuration .log ("Main: Rethrowing bot throwable" );
145- throw new RuntimeException (lastThrow );
146- }
147-
148- if (configuration .getUnlimitedFrameZero () && frame == 0 ) {
149- configuration .log ("Main: Waiting indefinitely on frame #" + frame );
150- frameBuffer .conditionSize .await ();
151- } else {
152- long remainingNanos = endNanos - System .nanoTime ();
153- if (remainingNanos <= 0 ) {
154- configuration .log ("Main: Out of time in frame #" + frame );
155- break ;
156- }
157- configuration .log ("Main: Waiting " + remainingNanos / 1000000 + "ms for bot on frame #" + frame );
158- frameBuffer .conditionSize .awaitNanos (remainingNanos );
159- long excessNanos = Math .max (0 , (System .nanoTime () - endNanos ) / 1000000 );
160- performanceMetrics .getExcessSleep ().record (excessNanos );
161- }
162- }
163- } catch (InterruptedException ignored ) {
164- } finally {
165- frameBuffer .lockSize .unlock ();
166- performanceMetrics .getClientIdle ().stopTiming ();
167- }
168- }
169-
170- /**
171- * Allows an asynchronous bot time to finish operation
172- */
173- void endGame () {
174- if (botThread != null ) {
175- try {
176- botThread .join ();
177- } catch (InterruptedException ignored ) {}
178- }
179- }
180-
181- Throwable getLastBotThrow () {
182- lastBotThrowLock .lock ();
183- Throwable output = lastBotThrow ;
184- lastBotThrowLock .unlock ();
185- return output ;
186- }
187-
188- private Thread createBotThread () {
189- return new Thread (() -> {
190- try {
191- configuration .log ("Bot: Thread started" );
192- while (!gameOver ) {
193-
194- boolean doUnsafeRead = false ;
195- configuration .log ("Bot: Ready for another frame" );
196- performanceMetrics .getBotIdle ().startTiming ();
197- frameBuffer .lockSize .lock ();
198- try {
199- doUnsafeRead = isUnsafeReadReady ();
200- while ( ! doUnsafeRead && frameBuffer .empty ()) {
201- configuration .log ("Bot: Waiting for a frame" );
202- frameBuffer .conditionSize .awaitUninterruptibly ();
203- doUnsafeRead = isUnsafeReadReady ();
204- }
205- } finally {
206- frameBuffer .lockSize .unlock ();
207- }
208- performanceMetrics .getBotIdle ().stopTiming ();
209-
210- if (doUnsafeRead ) {
211- configuration .log ("Bot: Reading live frame" );
212- setUnsafeReadReady (false );
213- } else {
214- configuration .log ("Bot: Peeking next frame from buffer" );
215- botGame .botClientData ().setBuffer (frameBuffer .peek ());
216- }
217-
218- configuration .log ("Bot: Handling events on frame #" + botGame .getFrameCount ());
219- handleEvents ();
220-
221- configuration .log ("Bot: Events handled. Dequeuing frame #" + botGame .getFrameCount ());
222- frameBuffer .dequeue ();
223- }
224- } catch (Throwable throwable ) {
225- // Record the throw,
226- // Then allow the thread to terminate silently.
227- // The main thread will look for the stored throw.
228- lastBotThrowLock .lock ();
229- lastBotThrow = throwable ;
230- lastBotThrowLock .unlock ();
231-
232- // Awaken any threads waiting on bot progress
233- while (!frameBuffer .empty ()) {
234- frameBuffer .dequeue ();
235- }
236- }
237- });
238- }
239-
240- private void handleEvents () {
49+ protected void handleEvents () {
24150 ClientData .GameData botGameData = botGame .botClientData ().gameData ();
24251
24352 // Populate gameOver before invoking event handlers (in case the bot throws)
24453 for (int i = 0 ; i < botGameData .getEventCount (); i ++) {
24554 gameOver = gameOver || botGameData .getEvents (i ).getType () == EventType .MatchEnd ;
24655 }
24756
248- if (configuration .getAsync ()) {
249- performanceMetrics .getFramesBehind ().record (Math .max (1 , frameBuffer .framesBuffered ()) - 1 );
250- }
251-
25257 performanceMetrics .getBotResponse ().timeIf (
25358 ! gameOver && (botGameData .getFrameCount () > 0 || ! configuration .getUnlimitedFrameZero ()),
25459 () -> {
0 commit comments