Skip to content

Commit ba8afc3

Browse files
committed
Make BWClientConfiguration use the builder pattern
1 parent 4677f15 commit ba8afc3

File tree

9 files changed

+299
-286
lines changed

9 files changed

+299
-286
lines changed

src/main/java/bwapi/BWClient.java

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* Client class to connect to the game with.
77
*/
88
public class BWClient {
9-
private BWClientConfiguration configuration = new BWClientConfiguration();
9+
private BWClientConfiguration configuration;
1010
private final BWEventListener eventListener;
1111
private BotWrapper botWrapper;
1212
private Client client;
@@ -19,7 +19,7 @@ public BWClient(final BWEventListener eventListener) {
1919

2020
/**
2121
* Get the {@link Game} instance of the currently running game.
22-
* When running in asynchronous mode, this is the game from the bot's perspective, eg. potentially a previous frame.
22+
* When running in asynchronous mode, this is the game from the bot's perspective, e.g. potentially a previous frame.
2323
*/
2424
public Game getGame() {
2525
return botWrapper == null ? null : botWrapper.getGame();
@@ -65,8 +65,7 @@ Client getClient() {
6565
* Start the game with default settings.
6666
*/
6767
public void startGame() {
68-
BWClientConfiguration configuration = new BWClientConfiguration();
69-
startGame(configuration);
68+
startGame(BWClientConfiguration.DEFAULT);
7069
}
7170

7271
/**
@@ -76,9 +75,9 @@ public void startGame() {
7675
*/
7776
@Deprecated
7877
public void startGame(boolean autoContinue) {
79-
BWClientConfiguration configuration = new BWClientConfiguration();
80-
configuration.withAutoContinue(autoContinue);
81-
startGame(configuration);
78+
startGame(new BWClientConfiguration.Builder()
79+
.withAutoContinue(autoContinue)
80+
.build());
8281
}
8382

8483
/**
@@ -87,7 +86,6 @@ public void startGame(boolean autoContinue) {
8786
* @param gameConfiguration Settings for playing games with this client.
8887
*/
8988
public void startGame(BWClientConfiguration gameConfiguration) {
90-
gameConfiguration.validateAndLock();
9189
this.configuration = gameConfiguration;
9290
this.performanceMetrics = new PerformanceMetrics(configuration);
9391
botWrapper = new BotWrapper(configuration, eventListener);

src/main/java/bwapi/BWClientConfiguration.java

Lines changed: 116 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -3,157 +3,157 @@
33
/**
44
* Configuration for constructing a BWClient
55
*/
6-
public class BWClientConfiguration {
6+
public final class BWClientConfiguration {
7+
public final static BWClientConfiguration DEFAULT = new BWClientConfiguration();
8+
9+
private boolean debugConnection = false;
10+
private boolean autoContinue = false;
11+
private boolean unlimitedFrameZero = true;
12+
private int maxFrameDurationMs = 40;
13+
private boolean async = false;
14+
private int asyncFrameBufferCapacity = 10;
15+
private boolean asyncUnsafe = false;
16+
private boolean logVerbosely = false;
717

818
/**
9-
* Set to `true` for more explicit error messages (which might spam the terminal).
19+
* Use the Builder to build a valid BWClientConfiguration object.
1020
*/
11-
public BWClientConfiguration withDebugConnection(boolean value) {
12-
throwIfLocked();
13-
debugConnection = value;
14-
return this;
21+
private BWClientConfiguration() {}
22+
public static class Builder {
23+
final BWClientConfiguration bwClientConfiguration = new BWClientConfiguration();
24+
25+
/**
26+
* Set to `true` for more explicit error messages (which might spam the terminal).
27+
*/
28+
public Builder withDebugConnection(boolean value) {
29+
bwClientConfiguration.debugConnection = value;
30+
return this;
31+
}
32+
33+
/**
34+
* When true, restarts the client loop when a game ends, allowing the client to play multiple games without restarting.
35+
*/
36+
public Builder withAutoContinue(boolean value) {
37+
bwClientConfiguration.autoContinue = value;
38+
return this;
39+
}
40+
41+
/**
42+
* Most bot tournaments allow bots to take an indefinite amount of time on frame #0 (the first frame of the game) to analyze the map and load data,
43+
* as the bot has no prior access to BWAPI or game information.
44+
*
45+
* This flag indicates that taking arbitrarily long on frame zero is acceptable.
46+
* Performance metrics omit the frame as an outlier.
47+
* Asynchronous operation will block until the bot's event handlers are complete.
48+
*/
49+
public Builder withUnlimitedFrameZero(boolean value) {
50+
bwClientConfiguration.unlimitedFrameZero = value;
51+
return this;
52+
}
53+
54+
/**
55+
* The maximum amount of time the bot is supposed to spend on a single frame.
56+
* In asynchronous mode, JBWAPI will attempt to let the bot use up to this much time to process all frames before returning control to BWAPI.
57+
* In synchronous mode, JBWAPI is not empowered to prevent the bot to exceed this amount, but will record overruns in performance metrics.
58+
* Real-time human play typically uses the "fastest" game speed, which has 42.86ms (42,860ns) between frames.
59+
*/
60+
public Builder withMaxFrameDurationMs(int value) {
61+
if (value < 0) {
62+
throw new IllegalArgumentException("maxFrameDurationMs needs to be a non-negative number (it's how long JBWAPI waits for a bot response before returning control to BWAPI).");
63+
}
64+
bwClientConfiguration.maxFrameDurationMs = value;
65+
return this;
66+
}
67+
68+
/**
69+
* Runs the bot in asynchronous mode. Asynchronous mode helps attempt to ensure that the bot adheres to real-time performance constraints.
70+
*
71+
* Humans playing StarCraft (and some tournaments) expect bots to return commands within a certain period of time; ~42ms for humans ("fastest" game speed),
72+
* and some tournaments enforce frame-wise time limits (at time of writing, 55ms for COG and AIIDE; 85ms for SSCAIT).
73+
*
74+
* Asynchronous mode invokes bot event handlers in a separate thread, and if all event handlers haven't returned by a specified period of time,
75+
* returns control to StarCraft, allowing the game to proceed while the bot continues to step in the background. This increases the likelihood of meeting
76+
* real-time performance requirements, while not fully guaranteeing it (subject to the whims of the JVM thread scheduler), at a cost of the bot possibly
77+
* issuing commands later than intended, and a marginally larger memory footprint.
78+
*
79+
* Asynchronous mode is not compatible with latency compensation. Enabling asynchronous mode automatically disables latency compensation.
80+
*/
81+
public Builder withAsync(boolean value) {
82+
bwClientConfiguration.async = value;
83+
return this;
84+
}
85+
86+
/**
87+
* The maximum number of frames to buffer while waiting on a bot.
88+
* Each frame buffered adds about 33 megabytes to JBWAPI's memory footprint.
89+
*/
90+
public Builder withAsyncFrameBufferCapacity(int size) {
91+
if (size < 1) {
92+
throw new IllegalArgumentException("asyncFrameBufferCapacity needs to be a positive number (There needs to be at least one frame buffer).");
93+
}
94+
bwClientConfiguration.asyncFrameBufferCapacity = size;
95+
return this;
96+
}
97+
98+
/**
99+
* Enables thread-unsafe async mode.
100+
* In this mode, the bot is allowed to read directly from shared memory until shared memory has been copied into the frame buffer,
101+
* at which point the bot switches to using the frame buffer.
102+
* This should enhance performance by allowing the bot to act while the frame is copied, but poses unidentified risk due to
103+
* the non-thread-safe switch from shared memory reads to frame buffer reads.
104+
*/
105+
public Builder withAsyncUnsafe(boolean value) {
106+
bwClientConfiguration.asyncUnsafe = value;
107+
return this;
108+
}
109+
110+
/**
111+
* Toggles verbose logging, particularly of synchronization steps.
112+
*/
113+
public Builder withLogVerbosely(boolean value) {
114+
bwClientConfiguration.logVerbosely = value;
115+
return this;
116+
}
117+
118+
public BWClientConfiguration build() {
119+
if (bwClientConfiguration.asyncUnsafe && ! bwClientConfiguration.async) {
120+
throw new IllegalArgumentException("asyncUnsafe mode needs async mode.");
121+
}
122+
return bwClientConfiguration;
123+
}
15124
}
125+
16126
public boolean getDebugConnection() {
17127
return debugConnection;
18128
}
19-
private boolean debugConnection;
20129

21-
/**
22-
* When true, restarts the client loop when a game ends, allowing the client to play multiple games without restarting.
23-
*/
24-
public BWClientConfiguration withAutoContinue(boolean value) {
25-
throwIfLocked();
26-
autoContinue = value;
27-
return this;
28-
}
29130
public boolean getAutoContinue() {
30131
return autoContinue;
31132
}
32-
private boolean autoContinue = false;
33133

34-
/**
35-
* Most bot tournaments allow bots to take an indefinite amount of time on frame #0 (the first frame of the game) to analyze the map and load data,
36-
* as the bot has no prior access to BWAPI or game information.
37-
*
38-
* This flag indicates that taking arbitrarily long on frame zero is acceptable.
39-
* Performance metrics omit the frame as an outlier.
40-
* Asynchronous operation will block until the bot's event handlers are complete.
41-
*/
42-
public BWClientConfiguration withUnlimitedFrameZero(boolean value) {
43-
throwIfLocked();
44-
unlimitedFrameZero = value;
45-
return this;
46-
}
47134
public boolean getUnlimitedFrameZero() {
48135
return unlimitedFrameZero;
49136
}
50-
private boolean unlimitedFrameZero = true;
51137

52-
/**
53-
* The maximum amount of time the bot is supposed to spend on a single frame.
54-
* In asynchronous mode, JBWAPI will attempt to let the bot use up to this much time to process all frames before returning control to BWAPI.
55-
* In synchronous mode, JBWAPI is not empowered to prevent the bot to exceed this amount, but will record overruns in performance metrics.
56-
* Real-time human play typically uses the "fastest" game speed, which has 42.86ms (42,860ns) between frames.
57-
*/
58-
public BWClientConfiguration withMaxFrameDurationMs(int value) {
59-
throwIfLocked();
60-
maxFrameDurationMs = value;
61-
return this;
62-
}
63138
public int getMaxFrameDurationMs() {
64139
return maxFrameDurationMs;
65140
}
66-
private int maxFrameDurationMs = 40;
67141

68-
/**
69-
* Runs the bot in asynchronous mode. Asynchronous mode helps attempt to ensure that the bot adheres to real-time performance constraints.
70-
*
71-
* Humans playing StarCraft (and some tournaments) expect bots to return commands within a certain period of time; ~42ms for humans ("fastesT" game speed),
72-
* and some tournaments enforce frame-wise time limits (at time of writing, 55ms for COG and AIIDE; 85ms for SSCAIT).
73-
*
74-
* Asynchronous mode invokes bot event handlers in a separate thread, and if all event handlers haven't returned by a specified period of time, sends an
75-
* returns control to StarCraft, allowing the game to proceed while the bot continues to step in the background. This increases the likelihood of meeting
76-
* real-time performance requirements, while not fully guaranteeing it (subject to the whims of the JVM thread scheduler), at a cost of the bot possibly
77-
* issuing commands later than intended, and a marginally larger memory footprint.
78-
*
79-
* Asynchronous mode is not compatible with latency compensation. Enabling asynchronous mode automatically disables latency compensation.
80-
*/
81-
public BWClientConfiguration withAsync(boolean value) {
82-
throwIfLocked();
83-
async = value;
84-
return this;
85-
}
86142
public boolean getAsync() {
87143
return async;
88144
}
89-
private boolean async = false;
90145

91-
/**
92-
* The maximum number of frames to buffer while waiting on a bot.
93-
* Each frame buffered adds about 33 megabytes to JBWAPI's memory footprint.
94-
*/
95-
public BWClientConfiguration withAsyncFrameBufferCapacity(int size) {
96-
throwIfLocked();
97-
asyncFrameBufferCapacity = size;
98-
return this;
99-
}
100146
public int getAsyncFrameBufferCapacity() {
101147
return asyncFrameBufferCapacity;
102148
}
103-
private int asyncFrameBufferCapacity = 10;
104149

105-
/**
106-
* Enables thread-unsafe async mode.
107-
* In this mode, the bot is allowed to read directly from shared memory until shared memory has been copied into the frame buffer,
108-
* at wihch point the bot switches to using the frame buffer.
109-
* This should enhance performance by allowing the bot to act while the frame is copied, but poses unidentified risk due to
110-
* the non-thread-safe switc from shared memory reads to frame buffer reads.
111-
*/
112-
public BWClientConfiguration withAsyncUnsafe(boolean value) {
113-
throwIfLocked();
114-
asyncUnsafe = value;
115-
return this;
116-
}
117150
public boolean getAsyncUnsafe() {
118151
return asyncUnsafe;
119152
}
120-
private boolean asyncUnsafe = false;
121153

122-
/**
123-
* Toggles verbose logging, particularly of synchronization steps.
124-
*/
125-
public BWClientConfiguration withLogVerbosely(boolean value) {
126-
throwIfLocked();
127-
logVerbosely = value;
128-
return this;
129-
}
130154
public boolean getLogVerbosely() {
131155
return logVerbosely;
132156
}
133-
private boolean logVerbosely = false;
134-
135-
/**
136-
* Checks that the configuration is in a valid state. Throws an IllegalArgumentException if it isn't.
137-
*/
138-
void validateAndLock() {
139-
if (asyncUnsafe && ! async) {
140-
throw new IllegalArgumentException("asyncUnsafe mode needs async mode.");
141-
}
142-
if (async && maxFrameDurationMs < 0) {
143-
throw new IllegalArgumentException("maxFrameDurationMs needs to be a non-negative number (it's how long JBWAPI waits for a bot response before returning control to BWAPI).");
144-
}
145-
if (async && asyncFrameBufferCapacity < 1) {
146-
throw new IllegalArgumentException("asyncFrameBufferCapacity needs to be a positive number (There needs to be at least one frame buffer).");
147-
}
148-
locked = true;
149-
}
150-
private boolean locked = false;
151-
152-
void throwIfLocked() {
153-
if (locked) {
154-
throw new RuntimeException("Configuration can not be modified after the game has started");
155-
}
156-
}
157157

158158
void log(String value) {
159159
if (logVerbosely) {

0 commit comments

Comments
 (0)