Skip to content

Commit 5c9dc36

Browse files
committed
Was getting memory allocation exceptions when replacing FrameBuffer's direct ByteBuffers on successive games, due either to fragmentation or leaking (or both). Now reusing the FrameBuffer across games so we only have to allocate once, for the first game. Replaced some unnecessary references to ClientData with GameData. Cleaned up BWClient game loop.
1 parent 931beb1 commit 5c9dc36

File tree

5 files changed

+55
-41
lines changed

5 files changed

+55
-41
lines changed

src/main/java/bwapi/BWClient.java

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,28 +46,28 @@ public void startGame(boolean autoContinue) {
4646
*/
4747
public void startGame(BWClientConfiguration configuration) {
4848
configuration.validate();
49+
botWrapper = new BotWrapper(configuration, eventListener);
4950
Client client = new Client(configuration);
5051
client.reconnect();
5152

5253
do {
53-
ClientData liveClientData = client.clientData();
54-
ClientData.GameData liveGameData = liveClientData.gameData();
5554
System.out.println("Client: Beginning game loop");
55+
ClientData.GameData liveGameData = client.clientData().gameData();
5656
while (!liveGameData.isInGame()) {
57-
botWrapper = null;
5857
if (client.isConnected()) {
5958
System.out.println("Client: Not in game; Connected.");
6059
} else {
6160
System.out.println("Client: Not in game; Not connected.");
6261
return;
6362
}
6463
client.update();
64+
if (liveGameData.isInGame()) {
65+
botWrapper.initialize(client.mapFile());
66+
}
6567
}
6668
while (liveGameData.isInGame()) {
6769
System.out.println("Client: In game on frame " + liveGameData.getFrameCount());
68-
if (botWrapper == null) {
69-
botWrapper = new BotWrapper(configuration, eventListener, client.mapFile());
70-
}
70+
7171
botWrapper.step();
7272

7373
// Proceed immediately once frame buffer is empty
@@ -83,16 +83,14 @@ public void startGame(BWClientConfiguration configuration) {
8383
}
8484

8585
System.out.println("Client: Sending commands on frame " + liveGameData.getFrameCount());
86-
getGame().sideEffects.flushTo(liveClientData);
86+
getGame().sideEffects.flushTo(liveGameData);
8787
client.update();
8888
if (!client.isConnected()) {
8989
System.out.println("Reconnecting...");
9090
client.reconnect();
9191
}
9292
}
93-
9493
// TODO: Before exiting give async bot time to complete onEnd(), maybe via thread.join().
95-
9694
} while (configuration.autoContinue); // lgtm [java/constant-loop-condition]
9795
}
9896
}

src/main/java/bwapi/BotWrapper.java

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,29 +34,38 @@ of this software and associated documentation files (the "Software"), to deal
3434
* Manages invocation of bot event handlers
3535
*/
3636
class BotWrapper {
37+
private final ClientData liveClientData = new ClientData();
3738
private final BWClientConfiguration configuration;
3839
private final BWEventListener eventListener;
39-
private final Game game;
40-
private FrameBuffer frameBuffer = null;
41-
private Thread botThread = null;
40+
private final FrameBuffer frameBuffer;
41+
private Game game;
42+
private Thread botThread;
4243
private boolean idle = false;
4344

4445
Lock idleLock = new ReentrantLock();
4546
Condition idleCondition = idleLock.newCondition();
4647

47-
BotWrapper(BWClientConfiguration configuration, BWEventListener eventListener, ByteBuffer dataSource) {
48+
BotWrapper(BWClientConfiguration configuration, BWEventListener eventListener) {
4849
this.configuration = configuration;
4950
this.eventListener = eventListener;
51+
frameBuffer = configuration.async ? new FrameBuffer(configuration.asyncFrameBufferSize) : null;
52+
}
5053

51-
ClientData currentClientData = new ClientData();
52-
currentClientData.setBuffer(dataSource);
53-
game = new Game(currentClientData);
54-
55-
if (configuration.async) {
56-
frameBuffer = new FrameBuffer(configuration.asyncFrameBufferSize, dataSource);
57-
}
54+
/**
55+
* Resets the BotWrapper for a new game.
56+
*/
57+
void initialize(ByteBuffer dataSource) {
58+
frameBuffer.initialize(dataSource);
59+
game = new Game(liveClientData);
60+
liveClientData.setBuffer(dataSource);
61+
botThread = null;
62+
idle = false;
5863
}
5964

65+
/**
66+
* @return The Game object used by the bot
67+
* In asynchronous mode this Game object may point at a copy of a previous frame.
68+
*/
6069
Game getGame() {
6170
return game;
6271
}

src/main/java/bwapi/FrameBuffer.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,22 @@ class FrameBuffer {
4949
final Condition conditionFull = lockCapacity.newCondition();
5050
final Condition conditionEmpty = lockCapacity.newCondition();
5151

52-
FrameBuffer(int size, ByteBuffer source) {
52+
FrameBuffer(int size) {
5353
this.size = size;
54-
this.dataSource = source;
5554
while(dataBuffer.size() < size) {
5655
dataBuffer.add(ByteBuffer.allocateDirect(ClientData.GameData.SIZE));
5756
}
5857
}
5958

59+
/**
60+
* Resets for a new game
61+
*/
62+
void initialize(ByteBuffer dataSource) {
63+
this.dataSource = dataSource;
64+
stepGame = 0;
65+
stepBot = 0;
66+
}
67+
6068
/**
6169
* @return The number of frames currently buffered ahead of the bot's current frame
6270
*/

src/main/java/bwapi/SideEffect.java

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,25 @@
11
package bwapi;
22

33
import java.util.function.Consumer;
4-
import java.util.function.Function;
54

65
/**
76
* A side effect is an interaction that a bot attempts to have with the game.
87
* This entails sending a game or unit command, or drawing a shape.
98
*/
109
class SideEffect {
1110

12-
private Consumer<ClientData> application;
11+
private Consumer<ClientData.GameData> application;
1312

14-
void apply(ClientData clientData) {
15-
application.accept(clientData);
13+
void apply(ClientData.GameData gameData) {
14+
application.accept(gameData);
1615
}
1716

1817
private SideEffect() {}
1918

2019
static SideEffect addUnitCommand(final int type, final int unit, final int target, final int x, final int y, final int extra) {
2120
SideEffect output = new SideEffect();
22-
output.application = (ClientData clientData) -> {
23-
ClientData.UnitCommand unitCommand = GameDataUtils.addUnitCommand(clientData.gameData());
21+
output.application = (ClientData.GameData gameData) -> {
22+
ClientData.UnitCommand unitCommand = GameDataUtils.addUnitCommand(gameData);
2423
unitCommand.setTid(type);
2524
unitCommand.setUnitIndex(unit);
2625
unitCommand.setTargetIndex(target);
@@ -33,8 +32,8 @@ static SideEffect addUnitCommand(final int type, final int unit, final int targe
3332

3433
static SideEffect addCommand(final CommandType type, final int value1, final int value2) {
3534
SideEffect output = new SideEffect();
36-
output.application = (ClientData clientData) -> {
37-
ClientData.Command command = GameDataUtils.addCommand(clientData.gameData());
35+
output.application = (ClientData.GameData gameData) -> {
36+
ClientData.Command command = GameDataUtils.addCommand(gameData);
3837
command.setType(type);
3938
command.setValue1(value1);
4039
command.setValue2(value2);
@@ -44,19 +43,19 @@ static SideEffect addCommand(final CommandType type, final int value1, final int
4443

4544
static SideEffect addCommand(final CommandType type, final String text, final int value2) {
4645
SideEffect output = new SideEffect();
47-
output.application = (ClientData clientData) -> {
48-
ClientData.Command command = GameDataUtils.addCommand(clientData.gameData());
46+
output.application = (ClientData.GameData gameData) -> {
47+
ClientData.Command command = GameDataUtils.addCommand(gameData);
4948
command.setType(type);
50-
command.setValue1(GameDataUtils.addString(clientData.gameData(), text));
49+
command.setValue1(GameDataUtils.addString(gameData, text));
5150
command.setValue2(value2);
5251
};
5352
return output;
5453
}
5554

5655
static SideEffect addShape(final ShapeType type, final CoordinateType coordType, final int x1, final int y1, final int x2, final int y2, final int extra1, final int extra2, final int color, final boolean isSolid) {
5756
SideEffect output = new SideEffect();
58-
output.application = (ClientData clientData) -> {
59-
ClientData.Shape shape = GameDataUtils.addShape(clientData.gameData());
57+
output.application = (ClientData.GameData gameData) -> {
58+
ClientData.Shape shape = GameDataUtils.addShape(gameData);
6059
shape.setType(type);
6160
shape.setCtype(coordType);
6261
shape.setX1(x1);
@@ -73,15 +72,15 @@ static SideEffect addShape(final ShapeType type, final CoordinateType coordType,
7372

7473
static SideEffect addShape(final ShapeType type, final CoordinateType coordType, final int x1, final int y1, final int x2, final int y2, final String text, final int extra2, final int color, final boolean isSolid) {
7574
SideEffect output = new SideEffect();
76-
output.application = (ClientData clientData) -> {
77-
ClientData.Shape shape = GameDataUtils.addShape(clientData.gameData());
75+
output.application = (ClientData.GameData gameData) -> {
76+
ClientData.Shape shape = GameDataUtils.addShape(gameData);
7877
shape.setType(type);
7978
shape.setCtype(coordType);
8079
shape.setX1(x1);
8180
shape.setY1(y1);
8281
shape.setX2(x2);
8382
shape.setY2(y2);
84-
shape.setExtra1(GameDataUtils.addString(clientData.gameData(), text));
83+
shape.setExtra1(GameDataUtils.addString(gameData, text));
8584
shape.setExtra2(extra2);
8685
shape.setColor(color);
8786
shape.setIsSolid(isSolid);

src/main/java/bwapi/SideEffectQueue.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ synchronized void enqueue(SideEffect sideEffect) {
2222
/**
2323
* Applies all enqueued side effects to the current BWAPI frame.
2424
*
25-
* @param liveClientData
25+
* @param liveGameData
2626
* The live game frame's data, using the BWAPI shared memory.
2727
*/
28-
synchronized void flushTo(ClientData liveClientData) {
29-
queue.forEach(x -> x.apply(liveClientData));
28+
synchronized void flushTo(ClientData.GameData liveGameData) {
29+
queue.forEach(x -> x.apply(liveGameData));
3030
queue.clear();
3131
}
3232
}

0 commit comments

Comments
 (0)