Skip to content

Commit 931beb1

Browse files
committed
Replaced sleeps with awaits
1 parent fd921e8 commit 931beb1

File tree

4 files changed

+138
-58
lines changed

4 files changed

+138
-58
lines changed

src/main/java/bwapi/BWClient.java

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -50,48 +50,40 @@ public void startGame(BWClientConfiguration configuration) {
5050
client.reconnect();
5151

5252
do {
53-
ClientData.GameData gameData = client.clientData().gameData();
54-
long lastUpdateTimestampMillis = 0;
53+
ClientData liveClientData = client.clientData();
54+
ClientData.GameData liveGameData = liveClientData.gameData();
5555
System.out.println("Client: Beginning game loop");
56-
while (!gameData.isInGame()) {
56+
while (!liveGameData.isInGame()) {
5757
botWrapper = null;
5858
if (client.isConnected()) {
5959
System.out.println("Client: Not in game; Connected.");
6060
} else {
6161
System.out.println("Client: Not in game; Not connected.");
6262
return;
6363
}
64-
lastUpdateTimestampMillis = System.currentTimeMillis();
6564
client.update();
6665
}
67-
while (gameData.isInGame()) {
68-
System.out.println("Client: In game on frame " + gameData.getFrameCount());
66+
while (liveGameData.isInGame()) {
67+
System.out.println("Client: In game on frame " + liveGameData.getFrameCount());
6968
if (botWrapper == null) {
7069
botWrapper = new BotWrapper(configuration, eventListener, client.mapFile());
7170
}
7271
botWrapper.step();
7372

74-
// Proceed immediately once framebuffer is empty
73+
// Proceed immediately once frame buffer is empty
7574
// Otherwise, wait for bot to catch up
76-
// TODO: Replace with a wait instead of a sleep
77-
while(true) {
78-
if (botWrapper.botIdle()) {
79-
System.out.println("Client: Proceeding because bot is idle.");
80-
break;
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();
8180
}
82-
long frameDurationMillis = System.currentTimeMillis() - lastUpdateTimestampMillis;
83-
if (frameDurationMillis > configuration.asyncFrameDurationMillis && (client.clientData().gameData().getFrameCount() > 0 || ! configuration.asyncWaitOnFrameZero)) {
84-
System.out.println("Client: Proceeding because frame " + botWrapper.getGame().getFrameCount() + " lasted " + frameDurationMillis + "ms");
85-
break;
86-
}
87-
try { Thread.sleep(1); } catch (InterruptedException ignored) {}
81+
} finally {
82+
botWrapper.idleLock.unlock();
8883
}
8984

90-
long currentTimeMillis = System.currentTimeMillis();
91-
long frameDurationMillis = currentTimeMillis - lastUpdateTimestampMillis;
92-
lastUpdateTimestampMillis = currentTimeMillis;
93-
System.out.println("Client: Ending frame after " + frameDurationMillis + "ms");
94-
getGame().sideEffects.flush(client.clientData());
85+
System.out.println("Client: Sending commands on frame " + liveGameData.getFrameCount());
86+
getGame().sideEffects.flushTo(liveClientData);
9587
client.update();
9688
if (!client.isConnected()) {
9789
System.out.println("Reconnecting...");

src/main/java/bwapi/BotWrapper.java

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ 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;
2932

3033
/**
3134
* Manages invocation of bot event handlers
@@ -36,7 +39,10 @@ class BotWrapper {
3639
private final Game game;
3740
private FrameBuffer frameBuffer = null;
3841
private Thread botThread = null;
39-
private volatile boolean idle = false;
42+
private boolean idle = false;
43+
44+
Lock idleLock = new ReentrantLock();
45+
Condition idleCondition = idleLock.newCondition();
4046

4147
BotWrapper(BWClientConfiguration configuration, BWEventListener eventListener, ByteBuffer dataSource) {
4248
this.configuration = configuration;
@@ -62,30 +68,49 @@ boolean botIdle() {
6268
return idle || ! configuration.async;
6369
}
6470

65-
/**
66-
* True if there is a frame buffer with free capacity.
67-
*/
68-
boolean canBuffer() {
69-
return configuration.async && ! frameBuffer.full();
70-
}
71-
7271
void step() {
7372
if (configuration.async) {
7473
frameBuffer.enqueueFrame();
7574
if (botThread == null) {
7675
botThread = new Thread(() -> {
7776
//noinspection InfiniteLoopStatement
7877
while(true) {
79-
while(frameBuffer.empty()) try {
80-
idle = true;
81-
Thread.sleep(0, 100);
82-
} catch (InterruptedException ignored) {}
83-
idle = false;
84-
System.out.println("Bot thread: Dequeuing frame. There are " + frameBuffer.framesBuffered() + " frames buffered.");
78+
// Await non-empty frame buffer
79+
frameBuffer.lockCapacity.lock();
80+
try {
81+
while(frameBuffer.empty()) {
82+
// Signal idleness
83+
idleLock.lock();
84+
try {
85+
idle = true;
86+
idleCondition.signalAll();
87+
} finally {
88+
idleLock.unlock();
89+
}
90+
frameBuffer.conditionEmpty.awaitUninterruptibly();
91+
}
92+
} finally {
93+
frameBuffer.lockCapacity.unlock();
94+
}
95+
96+
// Signal non-idleness
97+
idleLock.lock();
98+
try {
99+
idle = false;
100+
idleCondition.signalAll();
101+
} finally {
102+
idleLock.unlock();
103+
}
104+
85105
game.clientData().setBuffer(frameBuffer.dequeueFrame());
86-
System.out.println("Bot thread: Handling events.");
106+
System.out.println("Bot thread: Handling events while " + frameBuffer.framesBuffered() + " frames behind.");
87107
handleEvents();
88108
System.out.println("Bot thread: Handled events.");
109+
110+
if ( ! game.clientData().gameData().isInGame()) {
111+
System.out.println("Bot thread: Ending because game is over.");
112+
return;
113+
}
89114
}});
90115
botThread.setName("JBWAPI Bot");
91116
botThread.start();

src/main/java/bwapi/FrameBuffer.java

Lines changed: 82 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ of this software and associated documentation files (the "Software"), to deal
2727

2828
import java.nio.ByteBuffer;
2929
import java.util.ArrayList;
30+
import java.util.concurrent.locks.Condition;
31+
import java.util.concurrent.locks.Lock;
32+
import java.util.concurrent.locks.ReentrantLock;
3033

3134
/**
3235
* Circular buffer of game states.
@@ -40,7 +43,11 @@ class FrameBuffer {
4043
private ArrayList<ByteBuffer> dataBuffer = new ArrayList<>();
4144

4245
// Synchronization locks
43-
private Object stepCount = new Object();
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();
4451

4552
FrameBuffer(int size, ByteBuffer source) {
4653
this.size = size;
@@ -60,47 +67,103 @@ synchronized int framesBuffered() {
6067
/**
6168
* @return Whether the frame buffer is empty and has no frames available for the bot to consume.
6269
*/
63-
synchronized boolean empty() {
64-
return framesBuffered() <= 0;
70+
boolean empty() {
71+
lockCapacity.lock();
72+
try {
73+
return framesBuffered() <= 0;
74+
} finally {
75+
lockCapacity.unlock();
76+
}
6577
}
6678

6779
/**
6880
* @return Whether the frame buffer is full and can not buffer any additional frames.
6981
* When the frame buffer is full, JBWAPI must wait for the bot to complete a frame before returning control to StarCraft.
7082
*/
71-
synchronized boolean full() {
72-
return framesBuffered() >= size - 1;
83+
boolean full() {
84+
lockCapacity.lock();
85+
try {
86+
return framesBuffered() >= size - 1;
87+
} finally {
88+
lockCapacity.unlock();
89+
}
7390
}
7491

75-
synchronized private int indexGame() {
92+
private int indexGame() {
7693
return stepGame % size;
7794
}
7895

79-
synchronized private int indexBot() {
96+
private int indexBot() {
8097
return stepBot % size;
8198
}
8299

83100
/**
84101
* Copy dataBuffer from shared memory into the head of the frame buffer.
85102
*/
86103
void enqueueFrame() {
87-
while(full()) try { Thread.sleep(0, 100); } catch (InterruptedException ignored) {}
88-
System.out.println("FrameBuffer: Enqueuing buffer " + indexGame() + " on game step #" + stepGame + " with " + framesBuffered() + " frames buffered.");
89-
ByteBuffer dataTarget = dataBuffer.get(indexGame());
90-
dataSource.rewind();
91-
dataTarget.rewind();
92-
dataTarget.put(dataSource);
93-
++stepGame;
104+
// In practice we don't particularly expect multiple threads to write to this, but support it just to be safe.
105+
writeLock.lock();
106+
try {
107+
// Wait for the buffer to have space to enqueue
108+
lockCapacity.lock();
109+
try {
110+
while (full()) {
111+
conditionFull.awaitUninterruptibly();
112+
}
113+
} finally {
114+
lockCapacity.unlock();
115+
}
116+
117+
System.out.println("FrameBuffer: Enqueuing buffer " + indexGame() + " on game step #" + stepGame + " with " + framesBuffered() + " frames buffered.");
118+
ByteBuffer dataTarget = dataBuffer.get(indexGame());
119+
dataSource.rewind();
120+
dataTarget.rewind();
121+
dataTarget.put(dataSource);
122+
++stepGame;
123+
124+
// Notify anyone waiting for something to dequeue
125+
lockCapacity.lock();
126+
try {
127+
conditionEmpty.signalAll();
128+
} finally {
129+
lockCapacity.unlock();
130+
}
131+
} finally {
132+
writeLock.unlock();
133+
}
94134
}
95135

96136
/**
97137
* Points the bot to the next frame in the buffer.
98138
*/
99139
ByteBuffer dequeueFrame() {
100-
while(empty()) try { Thread.sleep(0, 100); } catch (InterruptedException ignored) {}
101-
System.out.println("FrameBuffer: Dequeuing buffer " + indexBot() + " on bot step #" + stepBot);
102-
ByteBuffer output = dataBuffer.get(indexBot());
103-
++stepBot;
104-
return output;
140+
// In practice we don't particularly expect multiple threads to read from this, but support it just to be safe.
141+
readLock.lock();
142+
try {
143+
// Wait for the buffer to have something to dequeue
144+
lockCapacity.lock();
145+
try {
146+
while (empty()) {
147+
conditionEmpty.awaitUninterruptibly();
148+
}
149+
} finally {
150+
lockCapacity.unlock();
151+
}
152+
153+
System.out.println("FrameBuffer: Dequeuing buffer " + indexBot() + " on bot step #" + stepBot);
154+
ByteBuffer output = dataBuffer.get(indexBot());
155+
++stepBot;
156+
157+
// Notify anyone waiting for capacity to enqueue
158+
lockCapacity.lock();
159+
try {
160+
conditionFull.signalAll();
161+
return output;
162+
} finally {
163+
lockCapacity.unlock();
164+
}
165+
} finally {
166+
readLock.unlock();
167+
}
105168
}
106169
}

src/main/java/bwapi/SideEffectQueue.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ synchronized void enqueue(SideEffect sideEffect) {
2525
* @param liveClientData
2626
* The live game frame's data, using the BWAPI shared memory.
2727
*/
28-
synchronized void flush(ClientData liveClientData) {
28+
synchronized void flushTo(ClientData liveClientData) {
2929
queue.forEach(x -> x.apply(liveClientData));
3030
queue.clear();
3131
}

0 commit comments

Comments
 (0)