Skip to content

Commit 341056d

Browse files
committed
Fixed end-of-game behavior. Moved synchronization out of BWClient and removed 'idleness' concept from BotWrapper.
1 parent 5c9dc36 commit 341056d

File tree

3 files changed

+88
-133
lines changed

3 files changed

+88
-133
lines changed

src/main/java/bwapi/BWClient.java

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -62,35 +62,21 @@ public void startGame(BWClientConfiguration configuration) {
6262
}
6363
client.update();
6464
if (liveGameData.isInGame()) {
65-
botWrapper.initialize(client.mapFile());
65+
botWrapper.startNewGame(client.mapFile());
6666
}
6767
}
6868
while (liveGameData.isInGame()) {
6969
System.out.println("Client: In game on frame " + liveGameData.getFrameCount());
70-
71-
botWrapper.step();
72-
73-
// Proceed immediately once frame buffer is empty
74-
// Otherwise, wait for bot to catch up
75-
botWrapper.idleLock.lock();
76-
try {
77-
while ( ! botWrapper.botIdle()) { // and we have time remaining (with optional extra for frame 0) or no room left in frame buffer
78-
System.out.println("Client: Waiting for idle bot on frame " + liveGameData.getFrameCount());
79-
botWrapper.idleCondition.awaitUninterruptibly();
80-
}
81-
} finally {
82-
botWrapper.idleLock.unlock();
83-
}
84-
85-
System.out.println("Client: Sending commands on frame " + liveGameData.getFrameCount());
70+
botWrapper.onFrame();
71+
System.out.println("Client: Sending commands for frame " + liveGameData.getFrameCount());
8672
getGame().sideEffects.flushTo(liveGameData);
8773
client.update();
8874
if (!client.isConnected()) {
8975
System.out.println("Reconnecting...");
9076
client.reconnect();
9177
}
9278
}
93-
// TODO: Before exiting give async bot time to complete onEnd(), maybe via thread.join().
79+
botWrapper.endGame();
9480
} while (configuration.autoContinue); // lgtm [java/constant-loop-condition]
9581
}
9682
}

src/main/java/bwapi/BotWrapper.java

Lines changed: 46 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,6 @@ of this software and associated documentation files (the "Software"), to deal
2626
package bwapi;
2727

2828
import java.nio.ByteBuffer;
29-
import java.util.concurrent.locks.Condition;
30-
import java.util.concurrent.locks.Lock;
31-
import java.util.concurrent.locks.ReentrantLock;
3229

3330
/**
3431
* Manages invocation of bot event handlers
@@ -40,10 +37,7 @@ class BotWrapper {
4037
private final FrameBuffer frameBuffer;
4138
private Game game;
4239
private Thread botThread;
43-
private boolean idle = false;
44-
45-
Lock idleLock = new ReentrantLock();
46-
Condition idleCondition = idleLock.newCondition();
40+
private boolean gameOver;
4741

4842
BotWrapper(BWClientConfiguration configuration, BWEventListener eventListener) {
4943
this.configuration = configuration;
@@ -54,12 +48,12 @@ class BotWrapper {
5448
/**
5549
* Resets the BotWrapper for a new game.
5650
*/
57-
void initialize(ByteBuffer dataSource) {
51+
void startNewGame(ByteBuffer dataSource) {
5852
frameBuffer.initialize(dataSource);
5953
game = new Game(liveClientData);
6054
liveClientData.setBuffer(dataSource);
6155
botThread = null;
62-
idle = false;
56+
gameOver = false;
6357
}
6458

6559
/**
@@ -71,68 +65,63 @@ Game getGame() {
7165
}
7266

7367
/**
74-
* True if the bot has handled all enqueued frames and is waiting for a new frame from StarCraft.
68+
* Handles the arrival of a new frame from BWAPI
7569
*/
76-
boolean botIdle() {
77-
return idle || ! configuration.async;
78-
}
79-
80-
void step() {
70+
void onFrame() {
8171
if (configuration.async) {
82-
frameBuffer.enqueueFrame();
8372
if (botThread == null) {
84-
botThread = new Thread(() -> {
85-
//noinspection InfiniteLoopStatement
86-
while(true) {
87-
// Await non-empty frame buffer
88-
frameBuffer.lockCapacity.lock();
89-
try {
90-
while(frameBuffer.empty()) {
91-
// Signal idleness
92-
idleLock.lock();
93-
try {
94-
idle = true;
95-
idleCondition.signalAll();
96-
} finally {
97-
idleLock.unlock();
98-
}
99-
frameBuffer.conditionEmpty.awaitUninterruptibly();
100-
}
101-
} finally {
102-
frameBuffer.lockCapacity.unlock();
103-
}
104-
105-
// Signal non-idleness
106-
idleLock.lock();
107-
try {
108-
idle = false;
109-
idleCondition.signalAll();
110-
} finally {
111-
idleLock.unlock();
112-
}
113-
114-
game.clientData().setBuffer(frameBuffer.dequeueFrame());
115-
System.out.println("Bot thread: Handling events while " + frameBuffer.framesBuffered() + " frames behind.");
116-
handleEvents();
117-
System.out.println("Bot thread: Handled events.");
118-
119-
if ( ! game.clientData().gameData().isInGame()) {
120-
System.out.println("Bot thread: Ending because game is over.");
121-
return;
122-
}
123-
}});
73+
System.out.println("Creating bot thread");
74+
botThread = createBotThread();
12475
botThread.setName("JBWAPI Bot");
12576
botThread.start();
12677
}
78+
frameBuffer.enqueueFrame();
79+
frameBuffer.lockSize.lock();
80+
try {
81+
while ( ! frameBuffer.empty()) frameBuffer.conditionSize.awaitUninterruptibly();
82+
} finally {
83+
frameBuffer.lockSize.unlock();
84+
}
12785
} else {
12886
handleEvents();
12987
}
13088
}
13189

90+
/**
91+
* Allows an asynchronous bot time to finish operation
92+
*/
93+
void endGame() {
94+
if (botThread != null) {
95+
try {
96+
botThread.join();
97+
} catch (InterruptedException ignored) {}
98+
}
99+
}
100+
101+
private Thread createBotThread() {
102+
return new Thread(() -> {
103+
while ( ! gameOver) {
104+
frameBuffer.lockSize.lock();
105+
try { while (frameBuffer.empty()) frameBuffer.conditionSize.awaitUninterruptibly(); } finally { frameBuffer.lockSize.unlock(); }
106+
107+
game.clientData().setBuffer(frameBuffer.peek());
108+
System.out.println("Bot thread: Handling events.");
109+
handleEvents();
110+
System.out.println("Bot thread: Handled events.");
111+
frameBuffer.dequeue();
112+
}
113+
System.out.println("Bot thread: Ending because game is over.");
114+
});
115+
}
116+
132117
private void handleEvents() {
133118
ClientData.GameData gameData = game.clientData().gameData();
134119
for (int i = 0; i < gameData.getEventCount(); i++) {
135-
EventHandler.operation(eventListener, game, gameData.getEvents(i));
120+
ClientData.Event event = gameData.getEvents(i);
121+
EventHandler.operation(eventListener, game, event);
122+
if (event.getType() == EventType.MatchEnd) {
123+
gameOver = true;
124+
}
136125
}
137126
}
138127
}

src/main/java/bwapi/FrameBuffer.java

Lines changed: 38 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,10 @@ class FrameBuffer {
4343
private ArrayList<ByteBuffer> dataBuffer = new ArrayList<>();
4444

4545
// Synchronization locks
46-
private final Lock writeLock = new ReentrantLock();
47-
private final Lock readLock = writeLock;
48-
final Lock lockCapacity = new ReentrantLock();
49-
final Condition conditionFull = lockCapacity.newCondition();
50-
final Condition conditionEmpty = lockCapacity.newCondition();
46+
private final Lock lockWrite = new ReentrantLock();
47+
private final Lock lockRead = lockWrite;
48+
final Lock lockSize = new ReentrantLock();
49+
final Condition conditionSize = lockSize.newCondition();
5150

5251
FrameBuffer(int size) {
5352
this.size = size;
@@ -76,11 +75,11 @@ synchronized int framesBuffered() {
7675
* @return Whether the frame buffer is empty and has no frames available for the bot to consume.
7776
*/
7877
boolean empty() {
79-
lockCapacity.lock();
78+
lockSize.lock();
8079
try {
8180
return framesBuffered() <= 0;
8281
} finally {
83-
lockCapacity.unlock();
82+
lockSize.unlock();
8483
}
8584
}
8685

@@ -89,11 +88,11 @@ boolean empty() {
8988
* When the frame buffer is full, JBWAPI must wait for the bot to complete a frame before returning control to StarCraft.
9089
*/
9190
boolean full() {
92-
lockCapacity.lock();
91+
lockSize.lock();
9392
try {
9493
return framesBuffered() >= size - 1;
9594
} finally {
96-
lockCapacity.unlock();
95+
lockSize.unlock();
9796
}
9897
}
9998

@@ -109,69 +108,50 @@ private int indexBot() {
109108
* Copy dataBuffer from shared memory into the head of the frame buffer.
110109
*/
111110
void enqueueFrame() {
112-
// In practice we don't particularly expect multiple threads to write to this, but support it just to be safe.
113-
writeLock.lock();
111+
lockWrite.lock();
114112
try {
115-
// Wait for the buffer to have space to enqueue
116-
lockCapacity.lock();
117-
try {
118-
while (full()) {
119-
conditionFull.awaitUninterruptibly();
120-
}
121-
} finally {
122-
lockCapacity.unlock();
123-
}
124-
125-
System.out.println("FrameBuffer: Enqueuing buffer " + indexGame() + " on game step #" + stepGame + " with " + framesBuffered() + " frames buffered.");
113+
lockSize.lock();
114+
try { while (full()) conditionSize.awaitUninterruptibly(); } finally { lockSize.unlock(); };
126115
ByteBuffer dataTarget = dataBuffer.get(indexGame());
127116
dataSource.rewind();
128117
dataTarget.rewind();
129118
dataTarget.put(dataSource);
130-
++stepGame;
131119

132-
// Notify anyone waiting for something to dequeue
133-
lockCapacity.lock();
120+
lockSize.lock();
134121
try {
135-
conditionEmpty.signalAll();
136-
} finally {
137-
lockCapacity.unlock();
138-
}
139-
} finally {
140-
writeLock.unlock();
141-
}
122+
++stepGame;
123+
System.out.println("FrameBuffer: Enqueued buffer " + indexGame() + " on game step #" + stepGame);
124+
if (framesBuffered() > 0) {
125+
System.out.println("FrameBuffer: There are now " + framesBuffered() + " frames buffered.");
126+
}
127+
conditionSize.signalAll();
128+
} finally { lockSize.unlock(); }
129+
} finally { lockWrite.unlock(); }
142130
}
143131

144132
/**
145-
* Points the bot to the next frame in the buffer.
133+
* Peeks the frontmost value in the buffer.
146134
*/
147-
ByteBuffer dequeueFrame() {
148-
// In practice we don't particularly expect multiple threads to read from this, but support it just to be safe.
149-
readLock.lock();
135+
ByteBuffer peek() {
136+
lockSize.lock();
150137
try {
151-
// Wait for the buffer to have something to dequeue
152-
lockCapacity.lock();
153-
try {
154-
while (empty()) {
155-
conditionEmpty.awaitUninterruptibly();
156-
}
157-
} finally {
158-
lockCapacity.unlock();
159-
}
138+
while(empty()) conditionSize.awaitUninterruptibly();
139+
System.out.println("FrameBuffer: Sharing buffer " + indexBot() + " on bot step #" + stepBot);
140+
return dataBuffer.get(indexBot());
141+
} finally { lockSize.unlock(); }
160142

143+
}
144+
145+
/**
146+
* Removes the frontmost frame in the buffer.
147+
*/
148+
void dequeue() {
149+
lockSize.lock();
150+
try {
151+
while(empty()) conditionSize.awaitUninterruptibly();
161152
System.out.println("FrameBuffer: Dequeuing buffer " + indexBot() + " on bot step #" + stepBot);
162-
ByteBuffer output = dataBuffer.get(indexBot());
163153
++stepBot;
164-
165-
// Notify anyone waiting for capacity to enqueue
166-
lockCapacity.lock();
167-
try {
168-
conditionFull.signalAll();
169-
return output;
170-
} finally {
171-
lockCapacity.unlock();
172-
}
173-
} finally {
174-
readLock.unlock();
175-
}
154+
conditionSize.signalAll();
155+
} finally { lockSize.unlock(); }
176156
}
177157
}

0 commit comments

Comments
 (0)