Skip to content

Commit f21ec54

Browse files
Jasper GeurtzJasperGeurtz
authored andcommitted
decouple async from sync
1 parent faae4f1 commit f21ec54

File tree

3 files changed

+244
-214
lines changed

3 files changed

+244
-214
lines changed

src/main/java/bwapi/BWClient.java

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -88,14 +88,9 @@ public void startGame(boolean autoContinue) {
8888
public void startGame(BWClientConfiguration gameConfiguration) {
8989
this.configuration = gameConfiguration;
9090
this.performanceMetrics = new PerformanceMetrics(configuration);
91-
botWrapper = new BotWrapper(configuration, eventListener);
92-
93-
// Use reduced priority to encourage Windows to give priority to StarCraft.exe/BWAPI.
94-
// If BWAPI doesn't get priority, it may not detect completion of a frame on our end in timely fashion.
95-
Thread.currentThread().setName("JBWAPI Client");
96-
if (configuration.getAsync()) {
97-
Thread.currentThread().setPriority(4);
98-
}
91+
botWrapper = configuration.getAsync()
92+
? new BotWrapperAsync(configuration, eventListener)
93+
: new BotWrapper(configuration, eventListener);
9994

10095
if (client == null) {
10196
client = new Client(this);

src/main/java/bwapi/BotWrapper.java

Lines changed: 11 additions & 206 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,29 @@
11
package bwapi;
22

3-
import java.util.concurrent.locks.ReentrantLock;
4-
53
/**
64
* Manages invocation of bot event handlers
75
*/
86
class 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

Comments
 (0)