From 3808f73166f87f3d91f6f8161e70d6955d62835c Mon Sep 17 00:00:00 2001 From: dgant Date: Fri, 17 Jul 2020 12:53:05 -0400 Subject: [PATCH 01/56] Added client configuration object to replace individual constructor arguments. --- src/main/java/bwapi/BWClient.java | 29 +- .../java/bwapi/BWClientConfiguration.java | 55 + src/main/java/bwapi/Client.java | 22 +- src/main/java/bwapi/ClientData.java | 2270 ++++++++--------- 4 files changed, 1217 insertions(+), 1159 deletions(-) create mode 100644 src/main/java/bwapi/BWClientConfiguration.java diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index b2b594dd..cdacf523 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -7,20 +7,10 @@ */ public class BWClient { private final BWEventListener eventListener; - private final boolean debugConnection; private EventHandler handler; public BWClient(final BWEventListener eventListener) { - this(eventListener, false); - } - - /** - * @param debugConnection set to `true` for more explicit error messages (might spam the terminal). - * `false` by default - */ - public BWClient(final BWEventListener eventListener, final boolean debugConnection) { Objects.requireNonNull(eventListener); - this.debugConnection = debugConnection; this.eventListener = eventListener; } @@ -32,7 +22,8 @@ public Game getGame() { } public void startGame() { - startGame(false); + BWClientConfiguration configuration = new BWClientConfiguration(); + startGame(configuration); } /** @@ -40,8 +31,20 @@ public void startGame() { * * @param autoContinue automatically continue playing the next game(s). false by default */ + @Deprecated public void startGame(boolean autoContinue) { - Client client = new Client(debugConnection); + BWClientConfiguration configuration = new BWClientConfiguration(); + configuration.autoContinue = autoContinue; + startGame(configuration); + } + + /** + * Start the game. + * + * @param configuration Settings for playing games with this client. + */ + public void startGame(BWClientConfiguration configuration) { + Client client = new Client(configuration); client.reconnect(); handler = new EventHandler(eventListener, client); @@ -59,6 +62,6 @@ public void startGame(boolean autoContinue) { client.reconnect(); } } - } while (autoContinue); // lgtm [java/constant-loop-condition] + } while (configuration.autoContinue); // lgtm [java/constant-loop-condition] } } \ No newline at end of file diff --git a/src/main/java/bwapi/BWClientConfiguration.java b/src/main/java/bwapi/BWClientConfiguration.java new file mode 100644 index 00000000..bee742d1 --- /dev/null +++ b/src/main/java/bwapi/BWClientConfiguration.java @@ -0,0 +1,55 @@ +package bwapi; + +/** + * Configuration for constructing a BWClient + */ +public class BWClientConfiguration { + + /** + * When true, restarts the client loop when a game ends, allowing the client to play multiple games without restarting. + */ + public boolean autoContinue = false; + + /** + * Runs the bot in asynchronous mode. Asynchronous mode helps attempt to ensure that the bot adheres to real-time performance constraints. + * + * Humans playing StarCraft (and some tournaments) expect bots to return commands within a certain period of time; ~42ms for humans ("fastesT" game speed), + * and some tournaments enforce frame-wise time limits (at time of writing, 55ms for COG and AIIDE; 85ms for SSCAIT). + * + * 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 + * returns control to StarCraft, allowing the game to proceed while the bot continues to run in the background. This increases the likelihood of meeting + * 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 + * issuing commands later than intended, and a marginally larger memory footprint. + */ + public boolean async = false; + + /** + * If JBWAPI detects that this much time (in nanoseconds) has passed since a bot's event handlers began, returns control back to BWAPI. + * Real-time human play typically uses the "fastest" game speed, which has 42.86ms (42,860ns) between frames. + */ + public int asyncFrameDurationNanosMax = 40000; + + /** + * How frequently (in nanoseconds) to poll for the bot's event handlers completing. Acts as a floor on the bot's frame duration. + */ + public int asyncFrameDurationNanosMin = 500; + + /** + * The maximum number of frames to buffer while waiting on a bot + */ + public int asyncFrameBufferSize = 10; + + /** + * 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, + * as the bot has no prior access to BWAPI or game information. + * + * This flag causes JBWAPI to wait for the bot's event handlers to return on the first frame of the game, even if operating in asynchronous mode, + * respecting the time bots are typically allowed on frame 0. + */ + public boolean asyncWaitOnFrameZero = true; + + /** + * Set to `true` for more explicit error messages (which might spam the terminal). + */ + public boolean debugConnection; +} \ No newline at end of file diff --git a/src/main/java/bwapi/Client.java b/src/main/java/bwapi/Client.java index 37c17a8f..53c89693 100644 --- a/src/main/java/bwapi/Client.java +++ b/src/main/java/bwapi/Client.java @@ -60,10 +60,10 @@ public interface EventHandler { private ByteBuffer mapFileHandle = null; private ByteBuffer gameTableFileHandle = null; - private boolean debugConnection = false; + private BWClientConfiguration configuration = new BWClientConfiguration(); - Client(boolean debugConnection) { - this.debugConnection = debugConnection; + Client(BWClientConfiguration configuration) { + this.configuration = configuration; } /** @@ -93,7 +93,7 @@ void reconnect(){ } void disconnect() { - if (debugConnection) { + if (configuration.debugConnection) { System.err.print("Disconnect called by: "); System.err.println(Thread.currentThread().getStackTrace()[2]); } @@ -143,7 +143,7 @@ boolean connect() { } catch (Exception e) { System.err.println("Unable to map Game table."); - if (debugConnection) { + if (configuration.debugConnection) { e.printStackTrace(); } return false; @@ -177,7 +177,7 @@ boolean connect() { } catch (Exception e) { System.err.println("Unable to open communications pipe: " + communicationPipe); - if (debugConnection) { + if (configuration.debugConnection) { e.printStackTrace(); } gameTableFileHandle = null; @@ -192,7 +192,7 @@ boolean connect() { } catch (Exception e) { System.err.println("Unable to open shared memory mapping: " + sharedMemoryName); - if (debugConnection) { + if (configuration.debugConnection) { e.printStackTrace(); } pipeObjectHandle = null; @@ -205,7 +205,7 @@ boolean connect() { } catch (Exception e) { System.err.println("Unable to map game data."); - if (debugConnection) { + if (configuration.debugConnection) { e.printStackTrace(); } return false; @@ -226,7 +226,7 @@ boolean connect() { } catch (Exception e) { System.err.println("Unable to read pipe object."); - if (debugConnection) { + if (configuration.debugConnection) { e.printStackTrace(); } disconnect(); @@ -246,7 +246,7 @@ void update(final EventHandler handler) { } catch (Exception e) { System.err.println("failed, disconnecting"); - if (debugConnection) { + if (configuration.debugConnection) { e.printStackTrace(); } disconnect(); @@ -258,7 +258,7 @@ void update(final EventHandler handler) { } catch (Exception e) { System.err.println("failed, disconnecting"); - if (debugConnection) { + if (configuration.debugConnection) { e.printStackTrace(); } disconnect(); diff --git a/src/main/java/bwapi/ClientData.java b/src/main/java/bwapi/ClientData.java index bfcdc931..dd77bfa5 100644 --- a/src/main/java/bwapi/ClientData.java +++ b/src/main/java/bwapi/ClientData.java @@ -1,1993 +1,1993 @@ -package bwapi; -import java.nio.ByteBuffer; -final class ClientData { - final WrappedBuffer buffer; - ClientData(final ByteBuffer buffer) { - this.buffer = new WrappedBuffer(buffer); - } +package bwapi; +import java.nio.ByteBuffer; +final class ClientData { + final WrappedBuffer buffer; + ClientData(final ByteBuffer buffer) { + this.buffer = new WrappedBuffer(buffer); + } class UnitCommand { static final int SIZE = 24; - private int myOffset; + private int myOffset; public UnitCommand(int myOffset) { - this.myOffset = myOffset; - } + this.myOffset = myOffset; + } int getTid() { int offset = myOffset + 0; - return buffer.getInt(offset); - } - void setTid(int value) { + return buffer.getInt(offset); + } + void setTid(int value) { buffer.putInt(myOffset + 0, value); - } + } int getUnitIndex() { int offset = myOffset + 4; - return buffer.getInt(offset); - } - void setUnitIndex(int value) { + return buffer.getInt(offset); + } + void setUnitIndex(int value) { buffer.putInt(myOffset + 4, value); - } + } int getTargetIndex() { int offset = myOffset + 8; - return buffer.getInt(offset); - } - void setTargetIndex(int value) { + return buffer.getInt(offset); + } + void setTargetIndex(int value) { buffer.putInt(myOffset + 8, value); - } + } int getX() { int offset = myOffset + 12; - return buffer.getInt(offset); - } - void setX(int value) { + return buffer.getInt(offset); + } + void setX(int value) { buffer.putInt(myOffset + 12, value); - } + } int getY() { int offset = myOffset + 16; - return buffer.getInt(offset); - } - void setY(int value) { + return buffer.getInt(offset); + } + void setY(int value) { buffer.putInt(myOffset + 16, value); - } + } int getExtra() { int offset = myOffset + 20; - return buffer.getInt(offset); - } - void setExtra(int value) { + return buffer.getInt(offset); + } + void setExtra(int value) { buffer.putInt(myOffset + 20, value); - } - } + } + } class GameData { static final int SIZE = 33017048; - private int myOffset; + private int myOffset; public GameData(int myOffset) { - this.myOffset = myOffset; - } + this.myOffset = myOffset; + } int getClient_version() { int offset = myOffset + 0; - return buffer.getInt(offset); - } - void setClient_version(int value) { + return buffer.getInt(offset); + } + void setClient_version(int value) { buffer.putInt(myOffset + 0, value); - } + } int getRevision() { int offset = myOffset + 4; - return buffer.getInt(offset); - } - void setRevision(int value) { + return buffer.getInt(offset); + } + void setRevision(int value) { buffer.putInt(myOffset + 4, value); - } + } boolean isDebug() { int offset = myOffset + 8; - return buffer.getByte(offset) != 0; - } - void setIsDebug(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsDebug(boolean value) { buffer.putByte(myOffset + 8, (byte) (value ? 1 : 0)); - } + } int getInstanceID() { int offset = myOffset + 12; - return buffer.getInt(offset); - } - void setInstanceID(int value) { + return buffer.getInt(offset); + } + void setInstanceID(int value) { buffer.putInt(myOffset + 12, value); - } + } int getBotAPM_noselects() { int offset = myOffset + 16; - return buffer.getInt(offset); - } - void setBotAPM_noselects(int value) { + return buffer.getInt(offset); + } + void setBotAPM_noselects(int value) { buffer.putInt(myOffset + 16, value); - } + } int getBotAPM_selects() { int offset = myOffset + 20; - return buffer.getInt(offset); - } - void setBotAPM_selects(int value) { + return buffer.getInt(offset); + } + void setBotAPM_selects(int value) { buffer.putInt(myOffset + 20, value); - } + } int getForceCount() { int offset = myOffset + 24; - return buffer.getInt(offset); - } - void setForceCount(int value) { + return buffer.getInt(offset); + } + void setForceCount(int value) { buffer.putInt(myOffset + 24, value); - } + } ForceData getForces(int i) { int offset = myOffset + 28 + 32 * 1 * i; - return new ForceData(offset); - } + return new ForceData(offset); + } int getPlayerCount() { int offset = myOffset + 188; - return buffer.getInt(offset); - } - void setPlayerCount(int value) { + return buffer.getInt(offset); + } + void setPlayerCount(int value) { buffer.putInt(myOffset + 188, value); - } + } PlayerData getPlayers(int i) { int offset = myOffset + 192 + 5788 * 1 * i; - return new PlayerData(offset); - } + return new PlayerData(offset); + } int getInitialUnitCount() { int offset = myOffset + 69648; - return buffer.getInt(offset); - } - void setInitialUnitCount(int value) { + return buffer.getInt(offset); + } + void setInitialUnitCount(int value) { buffer.putInt(myOffset + 69648, value); - } + } UnitData getUnits(int i) { int offset = myOffset + 69656 + 336 * 1 * i; - return new UnitData(offset); - } + return new UnitData(offset); + } int getUnitArray(int i) { int offset = myOffset + 3429656 + 4 * 1 * i; - return buffer.getInt(offset); - } - void setUnitArray(int i, int value) { + return buffer.getInt(offset); + } + void setUnitArray(int i, int value) { buffer.putInt(myOffset + 3429656 + 4 * 1 * i, value); - } + } BulletData getBullets(int i) { int offset = myOffset + 3436456 + 80 * 1 * i; - return new BulletData(offset); - } + return new BulletData(offset); + } int getNukeDotCount() { int offset = myOffset + 3444456; - return buffer.getInt(offset); - } - void setNukeDotCount(int value) { + return buffer.getInt(offset); + } + void setNukeDotCount(int value) { buffer.putInt(myOffset + 3444456, value); - } + } Position getNukeDots(int i) { int offset = myOffset + 3444460 + 8 * 1 * i; - return new Position(offset); - } + return new Position(offset); + } int getGameType() { int offset = myOffset + 3446060; - return buffer.getInt(offset); - } - void setGameType(int value) { + return buffer.getInt(offset); + } + void setGameType(int value) { buffer.putInt(myOffset + 3446060, value); - } + } int getLatency() { int offset = myOffset + 3446064; - return buffer.getInt(offset); - } - void setLatency(int value) { + return buffer.getInt(offset); + } + void setLatency(int value) { buffer.putInt(myOffset + 3446064, value); - } + } int getLatencyFrames() { int offset = myOffset + 3446068; - return buffer.getInt(offset); - } - void setLatencyFrames(int value) { + return buffer.getInt(offset); + } + void setLatencyFrames(int value) { buffer.putInt(myOffset + 3446068, value); - } + } int getLatencyTime() { int offset = myOffset + 3446072; - return buffer.getInt(offset); - } - void setLatencyTime(int value) { + return buffer.getInt(offset); + } + void setLatencyTime(int value) { buffer.putInt(myOffset + 3446072, value); - } + } int getRemainingLatencyFrames() { int offset = myOffset + 3446076; - return buffer.getInt(offset); - } - void setRemainingLatencyFrames(int value) { + return buffer.getInt(offset); + } + void setRemainingLatencyFrames(int value) { buffer.putInt(myOffset + 3446076, value); - } + } int getRemainingLatencyTime() { int offset = myOffset + 3446080; - return buffer.getInt(offset); - } - void setRemainingLatencyTime(int value) { + return buffer.getInt(offset); + } + void setRemainingLatencyTime(int value) { buffer.putInt(myOffset + 3446080, value); - } + } boolean getHasLatCom() { int offset = myOffset + 3446084; - return buffer.getByte(offset) != 0; - } - void setHasLatCom(boolean value) { + return buffer.getByte(offset) != 0; + } + void setHasLatCom(boolean value) { buffer.putByte(myOffset + 3446084, (byte) (value ? 1 : 0)); - } + } boolean getHasGUI() { int offset = myOffset + 3446085; - return buffer.getByte(offset) != 0; - } - void setHasGUI(boolean value) { + return buffer.getByte(offset) != 0; + } + void setHasGUI(boolean value) { buffer.putByte(myOffset + 3446085, (byte) (value ? 1 : 0)); - } + } int getReplayFrameCount() { int offset = myOffset + 3446088; - return buffer.getInt(offset); - } - void setReplayFrameCount(int value) { + return buffer.getInt(offset); + } + void setReplayFrameCount(int value) { buffer.putInt(myOffset + 3446088, value); - } + } int getRandomSeed() { int offset = myOffset + 3446092; - return buffer.getInt(offset); - } - void setRandomSeed(int value) { + return buffer.getInt(offset); + } + void setRandomSeed(int value) { buffer.putInt(myOffset + 3446092, value); - } + } int getFrameCount() { int offset = myOffset + 3446096; - return buffer.getInt(offset); - } - void setFrameCount(int value) { + return buffer.getInt(offset); + } + void setFrameCount(int value) { buffer.putInt(myOffset + 3446096, value); - } + } int getElapsedTime() { int offset = myOffset + 3446100; - return buffer.getInt(offset); - } - void setElapsedTime(int value) { + return buffer.getInt(offset); + } + void setElapsedTime(int value) { buffer.putInt(myOffset + 3446100, value); - } + } int getCountdownTimer() { int offset = myOffset + 3446104; - return buffer.getInt(offset); - } - void setCountdownTimer(int value) { + return buffer.getInt(offset); + } + void setCountdownTimer(int value) { buffer.putInt(myOffset + 3446104, value); - } + } int getFps() { int offset = myOffset + 3446108; - return buffer.getInt(offset); - } - void setFps(int value) { + return buffer.getInt(offset); + } + void setFps(int value) { buffer.putInt(myOffset + 3446108, value); - } + } double getAverageFPS() { int offset = myOffset + 3446112; - return buffer.getDouble(offset); - } - void setAverageFPS(double value) { + return buffer.getDouble(offset); + } + void setAverageFPS(double value) { buffer.putDouble(myOffset + 3446112, value); - } + } int getMouseX() { int offset = myOffset + 3446120; - return buffer.getInt(offset); - } - void setMouseX(int value) { + return buffer.getInt(offset); + } + void setMouseX(int value) { buffer.putInt(myOffset + 3446120, value); - } + } int getMouseY() { int offset = myOffset + 3446124; - return buffer.getInt(offset); - } - void setMouseY(int value) { + return buffer.getInt(offset); + } + void setMouseY(int value) { buffer.putInt(myOffset + 3446124, value); - } + } boolean getMouseState(int i) { int offset = myOffset + 3446128 + 1 * 1 * i; - return buffer.getByte(offset) != 0; - } - void setMouseState(int i, boolean value) { + return buffer.getByte(offset) != 0; + } + void setMouseState(int i, boolean value) { buffer.putByte(myOffset + 3446128 + 1 * 1 * i, (byte) (value ? 1 : 0)); - } + } boolean getKeyState(int i) { int offset = myOffset + 3446131 + 1 * 1 * i; - return buffer.getByte(offset) != 0; - } - void setKeyState(int i, boolean value) { + return buffer.getByte(offset) != 0; + } + void setKeyState(int i, boolean value) { buffer.putByte(myOffset + 3446131 + 1 * 1 * i, (byte) (value ? 1 : 0)); - } + } int getScreenX() { int offset = myOffset + 3446388; - return buffer.getInt(offset); - } - void setScreenX(int value) { + return buffer.getInt(offset); + } + void setScreenX(int value) { buffer.putInt(myOffset + 3446388, value); - } + } int getScreenY() { int offset = myOffset + 3446392; - return buffer.getInt(offset); - } - void setScreenY(int value) { + return buffer.getInt(offset); + } + void setScreenY(int value) { buffer.putInt(myOffset + 3446392, value); - } + } boolean getFlags(int i) { int offset = myOffset + 3446396 + 1 * 1 * i; - return buffer.getByte(offset) != 0; - } - void setFlags(int i, boolean value) { + return buffer.getByte(offset) != 0; + } + void setFlags(int i, boolean value) { buffer.putByte(myOffset + 3446396 + 1 * 1 * i, (byte) (value ? 1 : 0)); - } + } int getMapWidth() { int offset = myOffset + 3446400; - return buffer.getInt(offset); - } - void setMapWidth(int value) { + return buffer.getInt(offset); + } + void setMapWidth(int value) { buffer.putInt(myOffset + 3446400, value); - } + } int getMapHeight() { int offset = myOffset + 3446404; - return buffer.getInt(offset); - } - void setMapHeight(int value) { + return buffer.getInt(offset); + } + void setMapHeight(int value) { buffer.putInt(myOffset + 3446404, value); - } + } String getMapFileName() { int offset = myOffset + 3446408; - return buffer.getString(offset, 261); - } - void setMapFileName(String value) { + return buffer.getString(offset, 261); + } + void setMapFileName(String value) { buffer.putString(myOffset + 3446408, 261, value); - } + } String getMapPathName() { int offset = myOffset + 3446669; - return buffer.getString(offset, 261); - } - void setMapPathName(String value) { + return buffer.getString(offset, 261); + } + void setMapPathName(String value) { buffer.putString(myOffset + 3446669, 261, value); - } + } String getMapName() { int offset = myOffset + 3446930; - return buffer.getString(offset, 33); - } - void setMapName(String value) { + return buffer.getString(offset, 33); + } + void setMapName(String value) { buffer.putString(myOffset + 3446930, 33, value); - } + } String getMapHash() { int offset = myOffset + 3446963; - return buffer.getString(offset, 41); - } - void setMapHash(String value) { + return buffer.getString(offset, 41); + } + void setMapHash(String value) { buffer.putString(myOffset + 3446963, 41, value); - } + } int getGroundHeight(int i, int j) { int offset = myOffset + 3447004 + 4 * 1 * j + 4 * 256 * i; - return buffer.getInt(offset); - } - void setGetGroundHeight(int i, int j, int value) { + return buffer.getInt(offset); + } + void setGetGroundHeight(int i, int j, int value) { buffer.putInt(myOffset + 3447004 + 4 * 1 * j + 4 * 256 * i, value); - } + } boolean isWalkable(int i, int j) { int offset = myOffset + 3709148 + 1 * 1 * j + 1 * 1024 * i; - return buffer.getByte(offset) != 0; - } - void setIsWalkable(int i, int j, boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsWalkable(int i, int j, boolean value) { buffer.putByte(myOffset + 3709148 + 1 * 1 * j + 1 * 1024 * i, (byte) (value ? 1 : 0)); - } + } boolean isBuildable(int i, int j) { int offset = myOffset + 4757724 + 1 * 1 * j + 1 * 256 * i; - return buffer.getByte(offset) != 0; - } - void setIsBuildable(int i, int j, boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsBuildable(int i, int j, boolean value) { buffer.putByte(myOffset + 4757724 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); - } + } boolean isVisible(int i, int j) { int offset = myOffset + 4823260 + 1 * 1 * j + 1 * 256 * i; - return buffer.getByte(offset) != 0; - } - void setIsVisible(int i, int j, boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsVisible(int i, int j, boolean value) { buffer.putByte(myOffset + 4823260 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); - } + } boolean isExplored(int i, int j) { int offset = myOffset + 4888796 + 1 * 1 * j + 1 * 256 * i; - return buffer.getByte(offset) != 0; - } - void setIsExplored(int i, int j, boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsExplored(int i, int j, boolean value) { buffer.putByte(myOffset + 4888796 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); - } + } boolean getHasCreep(int i, int j) { int offset = myOffset + 4954332 + 1 * 1 * j + 1 * 256 * i; - return buffer.getByte(offset) != 0; - } - void setHasCreep(int i, int j, boolean value) { + return buffer.getByte(offset) != 0; + } + void setHasCreep(int i, int j, boolean value) { buffer.putByte(myOffset + 4954332 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); - } + } boolean isOccupied(int i, int j) { int offset = myOffset + 5019868 + 1 * 1 * j + 1 * 256 * i; - return buffer.getByte(offset) != 0; - } - void setIsOccupied(int i, int j, boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsOccupied(int i, int j, boolean value) { buffer.putByte(myOffset + 5019868 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); - } + } short getMapTileRegionId(int i, int j) { int offset = myOffset + 5085404 + 2 * 1 * j + 2 * 256 * i; - return buffer.getShort(offset); - } - void setMapTileRegionId(int i, int j, short value) { + return buffer.getShort(offset); + } + void setMapTileRegionId(int i, int j, short value) { buffer.putShort(myOffset + 5085404 + 2 * 1 * j + 2 * 256 * i, value); - } + } short getMapSplitTilesMiniTileMask(int i) { int offset = myOffset + 5216476 + 2 * 1 * i; - return buffer.getShort(offset); - } - void setMapSplitTilesMiniTileMask(int i, short value) { + return buffer.getShort(offset); + } + void setMapSplitTilesMiniTileMask(int i, short value) { buffer.putShort(myOffset + 5216476 + 2 * 1 * i, value); - } + } short getMapSplitTilesRegion1(int i) { int offset = myOffset + 5226476 + 2 * 1 * i; - return buffer.getShort(offset); - } - void setMapSplitTilesRegion1(int i, short value) { + return buffer.getShort(offset); + } + void setMapSplitTilesRegion1(int i, short value) { buffer.putShort(myOffset + 5226476 + 2 * 1 * i, value); - } + } short getMapSplitTilesRegion2(int i) { int offset = myOffset + 5236476 + 2 * 1 * i; - return buffer.getShort(offset); - } - void setMapSplitTilesRegion2(int i, short value) { + return buffer.getShort(offset); + } + void setMapSplitTilesRegion2(int i, short value) { buffer.putShort(myOffset + 5236476 + 2 * 1 * i, value); - } + } int getRegionCount() { int offset = myOffset + 5246476; - return buffer.getInt(offset); - } - void setRegionCount(int value) { + return buffer.getInt(offset); + } + void setRegionCount(int value) { buffer.putInt(myOffset + 5246476, value); - } + } RegionData getRegions(int i) { int offset = myOffset + 5246480 + 1068 * 1 * i; - return new RegionData(offset); - } + return new RegionData(offset); + } int getStartLocationCount() { int offset = myOffset + 10586480; - return buffer.getInt(offset); - } - void setStartLocationCount(int value) { + return buffer.getInt(offset); + } + void setStartLocationCount(int value) { buffer.putInt(myOffset + 10586480, value); - } + } Position getStartLocations(int i) { int offset = myOffset + 10586484 + 8 * 1 * i; - return new Position(offset); - } + return new Position(offset); + } boolean isInGame() { int offset = myOffset + 10586548; - return buffer.getByte(offset) != 0; - } - void setIsInGame(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsInGame(boolean value) { buffer.putByte(myOffset + 10586548, (byte) (value ? 1 : 0)); - } + } boolean isMultiplayer() { int offset = myOffset + 10586549; - return buffer.getByte(offset) != 0; - } - void setIsMultiplayer(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsMultiplayer(boolean value) { buffer.putByte(myOffset + 10586549, (byte) (value ? 1 : 0)); - } + } boolean isBattleNet() { int offset = myOffset + 10586550; - return buffer.getByte(offset) != 0; - } - void setIsBattleNet(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsBattleNet(boolean value) { buffer.putByte(myOffset + 10586550, (byte) (value ? 1 : 0)); - } + } boolean isPaused() { int offset = myOffset + 10586551; - return buffer.getByte(offset) != 0; - } - void setIsPaused(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsPaused(boolean value) { buffer.putByte(myOffset + 10586551, (byte) (value ? 1 : 0)); - } + } boolean isReplay() { int offset = myOffset + 10586552; - return buffer.getByte(offset) != 0; - } - void setIsReplay(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsReplay(boolean value) { buffer.putByte(myOffset + 10586552, (byte) (value ? 1 : 0)); - } + } int getSelectedUnitCount() { int offset = myOffset + 10586556; - return buffer.getInt(offset); - } - void setSelectedUnitCount(int value) { + return buffer.getInt(offset); + } + void setSelectedUnitCount(int value) { buffer.putInt(myOffset + 10586556, value); - } + } int getSelectedUnits(int i) { int offset = myOffset + 10586560 + 4 * 1 * i; - return buffer.getInt(offset); - } - void setSelectedUnits(int i, int value) { + return buffer.getInt(offset); + } + void setSelectedUnits(int i, int value) { buffer.putInt(myOffset + 10586560 + 4 * 1 * i, value); - } + } int getSelf() { int offset = myOffset + 10586608; - return buffer.getInt(offset); - } - void setSelf(int value) { + return buffer.getInt(offset); + } + void setSelf(int value) { buffer.putInt(myOffset + 10586608, value); - } + } int getEnemy() { int offset = myOffset + 10586612; - return buffer.getInt(offset); - } - void setEnemy(int value) { + return buffer.getInt(offset); + } + void setEnemy(int value) { buffer.putInt(myOffset + 10586612, value); - } + } int getNeutral() { int offset = myOffset + 10586616; - return buffer.getInt(offset); - } - void setNeutral(int value) { + return buffer.getInt(offset); + } + void setNeutral(int value) { buffer.putInt(myOffset + 10586616, value); - } + } int getEventCount() { int offset = myOffset + 10586620; - return buffer.getInt(offset); - } - void setEventCount(int value) { + return buffer.getInt(offset); + } + void setEventCount(int value) { buffer.putInt(myOffset + 10586620, value); - } + } Event getEvents(int i) { int offset = myOffset + 10586624 + 12 * 1 * i; - return new Event(offset); - } + return new Event(offset); + } int getEventStringCount() { int offset = myOffset + 10706624; - return buffer.getInt(offset); - } - void setEventStringCount(int value) { + return buffer.getInt(offset); + } + void setEventStringCount(int value) { buffer.putInt(myOffset + 10706624, value); - } + } String getEventStrings(int i) { int offset = myOffset + 10706628 + 1 * 256 * i; - return buffer.getString(offset, 256); - } - void setEventStrings(int i, String value) { + return buffer.getString(offset, 256); + } + void setEventStrings(int i, String value) { buffer.putString(myOffset + 10706628 + 1 * 256 * i, 256, value); - } + } int getStringCount() { int offset = myOffset + 10962628; - return buffer.getInt(offset); - } - void setStringCount(int value) { + return buffer.getInt(offset); + } + void setStringCount(int value) { buffer.putInt(myOffset + 10962628, value); - } + } String getStrings(int i) { int offset = myOffset + 10962632 + 1 * 1024 * i; - return buffer.getString(offset, 1024); - } - void setStrings(int i, String value) { + return buffer.getString(offset, 1024); + } + void setStrings(int i, String value) { buffer.putString(myOffset + 10962632 + 1 * 1024 * i, 1024, value); - } + } int getShapeCount() { int offset = myOffset + 31442632; - return buffer.getInt(offset); - } - void setShapeCount(int value) { + return buffer.getInt(offset); + } + void setShapeCount(int value) { buffer.putInt(myOffset + 31442632, value); - } + } Shape getShapes(int i) { int offset = myOffset + 31442636 + 40 * 1 * i; - return new Shape(offset); - } + return new Shape(offset); + } int getCommandCount() { int offset = myOffset + 32242636; - return buffer.getInt(offset); - } - void setCommandCount(int value) { + return buffer.getInt(offset); + } + void setCommandCount(int value) { buffer.putInt(myOffset + 32242636, value); - } + } Command getCommands(int i) { int offset = myOffset + 32242640 + 12 * 1 * i; - return new Command(offset); - } + return new Command(offset); + } int getUnitCommandCount() { int offset = myOffset + 32482640; - return buffer.getInt(offset); - } - void setUnitCommandCount(int value) { + return buffer.getInt(offset); + } + void setUnitCommandCount(int value) { buffer.putInt(myOffset + 32482640, value); - } + } UnitCommand getUnitCommands(int i) { int offset = myOffset + 32482644 + 24 * 1 * i; - return new UnitCommand(offset); - } + return new UnitCommand(offset); + } int getUnitSearchSize() { int offset = myOffset + 32962644; - return buffer.getInt(offset); - } - void setUnitSearchSize(int value) { + return buffer.getInt(offset); + } + void setUnitSearchSize(int value) { buffer.putInt(myOffset + 32962644, value); - } + } unitFinder getXUnitSearch(int i) { int offset = myOffset + 32962648 + 8 * 1 * i; - return new unitFinder(offset); - } + return new unitFinder(offset); + } unitFinder getYUnitSearch(int i) { int offset = myOffset + 32989848 + 8 * 1 * i; - return new unitFinder(offset); - } - } + return new unitFinder(offset); + } + } class Shape { static final int SIZE = 40; - private int myOffset; + private int myOffset; public Shape(int myOffset) { - this.myOffset = myOffset; - } + this.myOffset = myOffset; + } ShapeType getType() { int offset = myOffset + 0; - return ShapeType.idToEnum[buffer.getInt(offset)]; - } + return ShapeType.idToEnum[buffer.getInt(offset)]; + } void setType(ShapeType value) { buffer.putInt(myOffset + 0, value.id); - } + } CoordinateType getCtype() { int offset = myOffset + 4; - return CoordinateType.idToEnum[buffer.getInt(offset)]; - } + return CoordinateType.idToEnum[buffer.getInt(offset)]; + } void setCtype(CoordinateType value) { buffer.putInt(myOffset + 4, value.id); - } + } int getX1() { int offset = myOffset + 8; - return buffer.getInt(offset); - } - void setX1(int value) { + return buffer.getInt(offset); + } + void setX1(int value) { buffer.putInt(myOffset + 8, value); - } + } int getY1() { int offset = myOffset + 12; - return buffer.getInt(offset); - } - void setY1(int value) { + return buffer.getInt(offset); + } + void setY1(int value) { buffer.putInt(myOffset + 12, value); - } + } int getX2() { int offset = myOffset + 16; - return buffer.getInt(offset); - } - void setX2(int value) { + return buffer.getInt(offset); + } + void setX2(int value) { buffer.putInt(myOffset + 16, value); - } + } int getY2() { int offset = myOffset + 20; - return buffer.getInt(offset); - } - void setY2(int value) { + return buffer.getInt(offset); + } + void setY2(int value) { buffer.putInt(myOffset + 20, value); - } + } int getExtra1() { int offset = myOffset + 24; - return buffer.getInt(offset); - } - void setExtra1(int value) { + return buffer.getInt(offset); + } + void setExtra1(int value) { buffer.putInt(myOffset + 24, value); - } + } int getExtra2() { int offset = myOffset + 28; - return buffer.getInt(offset); - } - void setExtra2(int value) { + return buffer.getInt(offset); + } + void setExtra2(int value) { buffer.putInt(myOffset + 28, value); - } + } int getColor() { int offset = myOffset + 32; - return buffer.getInt(offset); - } - void setColor(int value) { + return buffer.getInt(offset); + } + void setColor(int value) { buffer.putInt(myOffset + 32, value); - } + } boolean isSolid() { int offset = myOffset + 36; - return buffer.getByte(offset) != 0; - } - void setIsSolid(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsSolid(boolean value) { buffer.putByte(myOffset + 36, (byte) (value ? 1 : 0)); - } - } + } + } class Command { static final int SIZE = 12; - private int myOffset; + private int myOffset; public Command(int myOffset) { - this.myOffset = myOffset; - } + this.myOffset = myOffset; + } CommandType getType() { int offset = myOffset + 0; - return CommandType.idToEnum[buffer.getInt(offset)]; - } + return CommandType.idToEnum[buffer.getInt(offset)]; + } void setType(CommandType value) { buffer.putInt(myOffset + 0, value.id); - } + } int getValue1() { int offset = myOffset + 4; - return buffer.getInt(offset); - } - void setValue1(int value) { + return buffer.getInt(offset); + } + void setValue1(int value) { buffer.putInt(myOffset + 4, value); - } + } int getValue2() { int offset = myOffset + 8; - return buffer.getInt(offset); - } - void setValue2(int value) { + return buffer.getInt(offset); + } + void setValue2(int value) { buffer.putInt(myOffset + 8, value); - } - } + } + } class Position { static final int SIZE = 8; - private int myOffset; + private int myOffset; public Position(int myOffset) { - this.myOffset = myOffset; - } + this.myOffset = myOffset; + } int getX() { int offset = myOffset + 0; - return buffer.getInt(offset); - } - void setX(int value) { + return buffer.getInt(offset); + } + void setX(int value) { buffer.putInt(myOffset + 0, value); - } + } int getY() { int offset = myOffset + 4; - return buffer.getInt(offset); - } - void setY(int value) { + return buffer.getInt(offset); + } + void setY(int value) { buffer.putInt(myOffset + 4, value); - } - } + } + } class Event { static final int SIZE = 12; - private int myOffset; + private int myOffset; public Event(int myOffset) { - this.myOffset = myOffset; - } + this.myOffset = myOffset; + } EventType getType() { int offset = myOffset + 0; - return EventType.idToEnum[buffer.getInt(offset)]; - } + return EventType.idToEnum[buffer.getInt(offset)]; + } void setType(EventType value) { buffer.putInt(myOffset + 0, value.id); - } + } int getV1() { int offset = myOffset + 4; - return buffer.getInt(offset); - } - void setV1(int value) { + return buffer.getInt(offset); + } + void setV1(int value) { buffer.putInt(myOffset + 4, value); - } + } int getV2() { int offset = myOffset + 8; - return buffer.getInt(offset); - } - void setV2(int value) { + return buffer.getInt(offset); + } + void setV2(int value) { buffer.putInt(myOffset + 8, value); - } - } + } + } class RegionData { static final int SIZE = 1068; - private int myOffset; + private int myOffset; public RegionData(int myOffset) { - this.myOffset = myOffset; - } + this.myOffset = myOffset; + } int getId() { int offset = myOffset + 0; - return buffer.getInt(offset); - } - void setId(int value) { + return buffer.getInt(offset); + } + void setId(int value) { buffer.putInt(myOffset + 0, value); - } + } int islandID() { int offset = myOffset + 4; - return buffer.getInt(offset); - } - void setIslandID(int value) { + return buffer.getInt(offset); + } + void setIslandID(int value) { buffer.putInt(myOffset + 4, value); - } + } int getCenter_x() { int offset = myOffset + 8; - return buffer.getInt(offset); - } - void setCenter_x(int value) { + return buffer.getInt(offset); + } + void setCenter_x(int value) { buffer.putInt(myOffset + 8, value); - } + } int getCenter_y() { int offset = myOffset + 12; - return buffer.getInt(offset); - } - void setCenter_y(int value) { + return buffer.getInt(offset); + } + void setCenter_y(int value) { buffer.putInt(myOffset + 12, value); - } + } int getPriority() { int offset = myOffset + 16; - return buffer.getInt(offset); - } - void setPriority(int value) { + return buffer.getInt(offset); + } + void setPriority(int value) { buffer.putInt(myOffset + 16, value); - } + } int getLeftMost() { int offset = myOffset + 20; - return buffer.getInt(offset); - } - void setLeftMost(int value) { + return buffer.getInt(offset); + } + void setLeftMost(int value) { buffer.putInt(myOffset + 20, value); - } + } int getRightMost() { int offset = myOffset + 24; - return buffer.getInt(offset); - } - void setRightMost(int value) { + return buffer.getInt(offset); + } + void setRightMost(int value) { buffer.putInt(myOffset + 24, value); - } + } int getTopMost() { int offset = myOffset + 28; - return buffer.getInt(offset); - } - void setTopMost(int value) { + return buffer.getInt(offset); + } + void setTopMost(int value) { buffer.putInt(myOffset + 28, value); - } + } int getBottomMost() { int offset = myOffset + 32; - return buffer.getInt(offset); - } - void setBottomMost(int value) { + return buffer.getInt(offset); + } + void setBottomMost(int value) { buffer.putInt(myOffset + 32, value); - } + } int getNeighborCount() { int offset = myOffset + 36; - return buffer.getInt(offset); - } - void setNeighborCount(int value) { + return buffer.getInt(offset); + } + void setNeighborCount(int value) { buffer.putInt(myOffset + 36, value); - } + } int getNeighbors(int i) { int offset = myOffset + 40 + 4 * 1 * i; - return buffer.getInt(offset); - } - void setNeighbors(int i, int value) { + return buffer.getInt(offset); + } + void setNeighbors(int i, int value) { buffer.putInt(myOffset + 40 + 4 * 1 * i, value); - } + } boolean isAccessible() { int offset = myOffset + 1064; - return buffer.getByte(offset) != 0; - } - void setIsAccessible(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsAccessible(boolean value) { buffer.putByte(myOffset + 1064, (byte) (value ? 1 : 0)); - } + } boolean isHigherGround() { int offset = myOffset + 1065; - return buffer.getByte(offset) != 0; - } - void setIsHigherGround(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsHigherGround(boolean value) { buffer.putByte(myOffset + 1065, (byte) (value ? 1 : 0)); - } - } + } + } class ForceData { static final int SIZE = 32; - private int myOffset; + private int myOffset; public ForceData(int myOffset) { - this.myOffset = myOffset; - } + this.myOffset = myOffset; + } String getName() { int offset = myOffset + 0; - return buffer.getString(offset, 32); - } - void setName(String value) { + return buffer.getString(offset, 32); + } + void setName(String value) { buffer.putString(myOffset + 0, 32, value); - } - } + } + } class PlayerData { static final int SIZE = 5788; - private int myOffset; + private int myOffset; public PlayerData(int myOffset) { - this.myOffset = myOffset; - } + this.myOffset = myOffset; + } String getName() { int offset = myOffset + 0; - return buffer.getString(offset, 25); - } - void setName(String value) { + return buffer.getString(offset, 25); + } + void setName(String value) { buffer.putString(myOffset + 0, 25, value); - } + } int getRace() { int offset = myOffset + 28; - return buffer.getInt(offset); - } - void setRace(int value) { + return buffer.getInt(offset); + } + void setRace(int value) { buffer.putInt(myOffset + 28, value); - } + } int getType() { int offset = myOffset + 32; - return buffer.getInt(offset); - } - void setType(int value) { + return buffer.getInt(offset); + } + void setType(int value) { buffer.putInt(myOffset + 32, value); - } + } int getForce() { int offset = myOffset + 36; - return buffer.getInt(offset); - } - void setForce(int value) { + return buffer.getInt(offset); + } + void setForce(int value) { buffer.putInt(myOffset + 36, value); - } + } boolean isAlly(int i) { int offset = myOffset + 40 + 1 * 1 * i; - return buffer.getByte(offset) != 0; - } - void setIsAlly(int i, boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsAlly(int i, boolean value) { buffer.putByte(myOffset + 40 + 1 * 1 * i, (byte) (value ? 1 : 0)); - } + } boolean isEnemy(int i) { int offset = myOffset + 52 + 1 * 1 * i; - return buffer.getByte(offset) != 0; - } - void setIsEnemy(int i, boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsEnemy(int i, boolean value) { buffer.putByte(myOffset + 52 + 1 * 1 * i, (byte) (value ? 1 : 0)); - } + } boolean isNeutral() { int offset = myOffset + 64; - return buffer.getByte(offset) != 0; - } - void setIsNeutral(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsNeutral(boolean value) { buffer.putByte(myOffset + 64, (byte) (value ? 1 : 0)); - } + } int getStartLocationX() { int offset = myOffset + 68; - return buffer.getInt(offset); - } - void setStartLocationX(int value) { + return buffer.getInt(offset); + } + void setStartLocationX(int value) { buffer.putInt(myOffset + 68, value); - } + } int getStartLocationY() { int offset = myOffset + 72; - return buffer.getInt(offset); - } - void setStartLocationY(int value) { + return buffer.getInt(offset); + } + void setStartLocationY(int value) { buffer.putInt(myOffset + 72, value); - } + } boolean isVictorious() { int offset = myOffset + 76; - return buffer.getByte(offset) != 0; - } - void setIsVictorious(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsVictorious(boolean value) { buffer.putByte(myOffset + 76, (byte) (value ? 1 : 0)); - } + } boolean isDefeated() { int offset = myOffset + 77; - return buffer.getByte(offset) != 0; - } - void setIsDefeated(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsDefeated(boolean value) { buffer.putByte(myOffset + 77, (byte) (value ? 1 : 0)); - } + } boolean getLeftGame() { int offset = myOffset + 78; - return buffer.getByte(offset) != 0; - } - void setLeftGame(boolean value) { + return buffer.getByte(offset) != 0; + } + void setLeftGame(boolean value) { buffer.putByte(myOffset + 78, (byte) (value ? 1 : 0)); - } + } boolean isParticipating() { int offset = myOffset + 79; - return buffer.getByte(offset) != 0; - } - void setIsParticipating(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsParticipating(boolean value) { buffer.putByte(myOffset + 79, (byte) (value ? 1 : 0)); - } + } int getMinerals() { int offset = myOffset + 80; - return buffer.getInt(offset); - } - void setMinerals(int value) { + return buffer.getInt(offset); + } + void setMinerals(int value) { buffer.putInt(myOffset + 80, value); - } + } int getGas() { int offset = myOffset + 84; - return buffer.getInt(offset); - } - void setGas(int value) { + return buffer.getInt(offset); + } + void setGas(int value) { buffer.putInt(myOffset + 84, value); - } + } int getGatheredMinerals() { int offset = myOffset + 88; - return buffer.getInt(offset); - } - void setGatheredMinerals(int value) { + return buffer.getInt(offset); + } + void setGatheredMinerals(int value) { buffer.putInt(myOffset + 88, value); - } + } int getGatheredGas() { int offset = myOffset + 92; - return buffer.getInt(offset); - } - void setGatheredGas(int value) { + return buffer.getInt(offset); + } + void setGatheredGas(int value) { buffer.putInt(myOffset + 92, value); - } + } int getRepairedMinerals() { int offset = myOffset + 96; - return buffer.getInt(offset); - } - void setRepairedMinerals(int value) { + return buffer.getInt(offset); + } + void setRepairedMinerals(int value) { buffer.putInt(myOffset + 96, value); - } + } int getRepairedGas() { int offset = myOffset + 100; - return buffer.getInt(offset); - } - void setRepairedGas(int value) { + return buffer.getInt(offset); + } + void setRepairedGas(int value) { buffer.putInt(myOffset + 100, value); - } + } int getRefundedMinerals() { int offset = myOffset + 104; - return buffer.getInt(offset); - } - void setRefundedMinerals(int value) { + return buffer.getInt(offset); + } + void setRefundedMinerals(int value) { buffer.putInt(myOffset + 104, value); - } + } int getRefundedGas() { int offset = myOffset + 108; - return buffer.getInt(offset); - } - void setRefundedGas(int value) { + return buffer.getInt(offset); + } + void setRefundedGas(int value) { buffer.putInt(myOffset + 108, value); - } + } int getSupplyTotal(int i) { int offset = myOffset + 112 + 4 * 1 * i; - return buffer.getInt(offset); - } - void setSupplyTotal(int i, int value) { + return buffer.getInt(offset); + } + void setSupplyTotal(int i, int value) { buffer.putInt(myOffset + 112 + 4 * 1 * i, value); - } + } int getSupplyUsed(int i) { int offset = myOffset + 124 + 4 * 1 * i; - return buffer.getInt(offset); - } - void setSupplyUsed(int i, int value) { + return buffer.getInt(offset); + } + void setSupplyUsed(int i, int value) { buffer.putInt(myOffset + 124 + 4 * 1 * i, value); - } + } int getAllUnitCount(int i) { int offset = myOffset + 136 + 4 * 1 * i; - return buffer.getInt(offset); - } - void setAllUnitCount(int i, int value) { + return buffer.getInt(offset); + } + void setAllUnitCount(int i, int value) { buffer.putInt(myOffset + 136 + 4 * 1 * i, value); - } + } int getVisibleUnitCount(int i) { int offset = myOffset + 1072 + 4 * 1 * i; - return buffer.getInt(offset); - } - void setVisibleUnitCount(int i, int value) { + return buffer.getInt(offset); + } + void setVisibleUnitCount(int i, int value) { buffer.putInt(myOffset + 1072 + 4 * 1 * i, value); - } + } int getCompletedUnitCount(int i) { int offset = myOffset + 2008 + 4 * 1 * i; - return buffer.getInt(offset); - } - void setCompletedUnitCount(int i, int value) { + return buffer.getInt(offset); + } + void setCompletedUnitCount(int i, int value) { buffer.putInt(myOffset + 2008 + 4 * 1 * i, value); - } + } int getDeadUnitCount(int i) { int offset = myOffset + 2944 + 4 * 1 * i; - return buffer.getInt(offset); - } - void setDeadUnitCount(int i, int value) { + return buffer.getInt(offset); + } + void setDeadUnitCount(int i, int value) { buffer.putInt(myOffset + 2944 + 4 * 1 * i, value); - } + } int getKilledUnitCount(int i) { int offset = myOffset + 3880 + 4 * 1 * i; - return buffer.getInt(offset); - } - void setKilledUnitCount(int i, int value) { + return buffer.getInt(offset); + } + void setKilledUnitCount(int i, int value) { buffer.putInt(myOffset + 3880 + 4 * 1 * i, value); - } + } int getUpgradeLevel(int i) { int offset = myOffset + 4816 + 4 * 1 * i; - return buffer.getInt(offset); - } - void setUpgradeLevel(int i, int value) { + return buffer.getInt(offset); + } + void setUpgradeLevel(int i, int value) { buffer.putInt(myOffset + 4816 + 4 * 1 * i, value); - } + } boolean getHasResearched(int i) { int offset = myOffset + 5068 + 1 * 1 * i; - return buffer.getByte(offset) != 0; - } - void setHasResearched(int i, boolean value) { + return buffer.getByte(offset) != 0; + } + void setHasResearched(int i, boolean value) { buffer.putByte(myOffset + 5068 + 1 * 1 * i, (byte) (value ? 1 : 0)); - } + } boolean isResearching(int i) { int offset = myOffset + 5115 + 1 * 1 * i; - return buffer.getByte(offset) != 0; - } - void setIsResearching(int i, boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsResearching(int i, boolean value) { buffer.putByte(myOffset + 5115 + 1 * 1 * i, (byte) (value ? 1 : 0)); - } + } boolean isUpgrading(int i) { int offset = myOffset + 5162 + 1 * 1 * i; - return buffer.getByte(offset) != 0; - } - void setIsUpgrading(int i, boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsUpgrading(int i, boolean value) { buffer.putByte(myOffset + 5162 + 1 * 1 * i, (byte) (value ? 1 : 0)); - } + } int getColor() { int offset = myOffset + 5228; - return buffer.getInt(offset); - } - void setColor(int value) { + return buffer.getInt(offset); + } + void setColor(int value) { buffer.putInt(myOffset + 5228, value); - } + } int getTotalUnitScore() { int offset = myOffset + 5232; - return buffer.getInt(offset); - } - void setTotalUnitScore(int value) { + return buffer.getInt(offset); + } + void setTotalUnitScore(int value) { buffer.putInt(myOffset + 5232, value); - } + } int getTotalKillScore() { int offset = myOffset + 5236; - return buffer.getInt(offset); - } - void setTotalKillScore(int value) { + return buffer.getInt(offset); + } + void setTotalKillScore(int value) { buffer.putInt(myOffset + 5236, value); - } + } int getTotalBuildingScore() { int offset = myOffset + 5240; - return buffer.getInt(offset); - } - void setTotalBuildingScore(int value) { + return buffer.getInt(offset); + } + void setTotalBuildingScore(int value) { buffer.putInt(myOffset + 5240, value); - } + } int getTotalRazingScore() { int offset = myOffset + 5244; - return buffer.getInt(offset); - } - void setTotalRazingScore(int value) { + return buffer.getInt(offset); + } + void setTotalRazingScore(int value) { buffer.putInt(myOffset + 5244, value); - } + } int getCustomScore() { int offset = myOffset + 5248; - return buffer.getInt(offset); - } - void setCustomScore(int value) { + return buffer.getInt(offset); + } + void setCustomScore(int value) { buffer.putInt(myOffset + 5248, value); - } + } int getMaxUpgradeLevel(int i) { int offset = myOffset + 5252 + 4 * 1 * i; - return buffer.getInt(offset); - } - void setMaxUpgradeLevel(int i, int value) { + return buffer.getInt(offset); + } + void setMaxUpgradeLevel(int i, int value) { buffer.putInt(myOffset + 5252 + 4 * 1 * i, value); - } + } boolean isResearchAvailable(int i) { int offset = myOffset + 5504 + 1 * 1 * i; - return buffer.getByte(offset) != 0; - } - void setIsResearchAvailable(int i, boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsResearchAvailable(int i, boolean value) { buffer.putByte(myOffset + 5504 + 1 * 1 * i, (byte) (value ? 1 : 0)); - } + } boolean isUnitAvailable(int i) { int offset = myOffset + 5551 + 1 * 1 * i; - return buffer.getByte(offset) != 0; - } - void setIsUnitAvailable(int i, boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsUnitAvailable(int i, boolean value) { buffer.putByte(myOffset + 5551 + 1 * 1 * i, (byte) (value ? 1 : 0)); - } - } + } + } class BulletData { static final int SIZE = 80; - private int myOffset; + private int myOffset; public BulletData(int myOffset) { - this.myOffset = myOffset; - } + this.myOffset = myOffset; + } int getId() { int offset = myOffset + 0; - return buffer.getInt(offset); - } - void setId(int value) { + return buffer.getInt(offset); + } + void setId(int value) { buffer.putInt(myOffset + 0, value); - } + } int getPlayer() { int offset = myOffset + 4; - return buffer.getInt(offset); - } - void setPlayer(int value) { + return buffer.getInt(offset); + } + void setPlayer(int value) { buffer.putInt(myOffset + 4, value); - } + } int getType() { int offset = myOffset + 8; - return buffer.getInt(offset); - } - void setType(int value) { + return buffer.getInt(offset); + } + void setType(int value) { buffer.putInt(myOffset + 8, value); - } + } int getSource() { int offset = myOffset + 12; - return buffer.getInt(offset); - } - void setSource(int value) { + return buffer.getInt(offset); + } + void setSource(int value) { buffer.putInt(myOffset + 12, value); - } + } int getPositionX() { int offset = myOffset + 16; - return buffer.getInt(offset); - } - void setPositionX(int value) { + return buffer.getInt(offset); + } + void setPositionX(int value) { buffer.putInt(myOffset + 16, value); - } + } int getPositionY() { int offset = myOffset + 20; - return buffer.getInt(offset); - } - void setPositionY(int value) { + return buffer.getInt(offset); + } + void setPositionY(int value) { buffer.putInt(myOffset + 20, value); - } + } double getAngle() { int offset = myOffset + 24; - return buffer.getDouble(offset); - } - void setAngle(double value) { + return buffer.getDouble(offset); + } + void setAngle(double value) { buffer.putDouble(myOffset + 24, value); - } + } double getVelocityX() { int offset = myOffset + 32; - return buffer.getDouble(offset); - } - void setVelocityX(double value) { + return buffer.getDouble(offset); + } + void setVelocityX(double value) { buffer.putDouble(myOffset + 32, value); - } + } double getVelocityY() { int offset = myOffset + 40; - return buffer.getDouble(offset); - } - void setVelocityY(double value) { + return buffer.getDouble(offset); + } + void setVelocityY(double value) { buffer.putDouble(myOffset + 40, value); - } + } int getTarget() { int offset = myOffset + 48; - return buffer.getInt(offset); - } - void setTarget(int value) { + return buffer.getInt(offset); + } + void setTarget(int value) { buffer.putInt(myOffset + 48, value); - } + } int getTargetPositionX() { int offset = myOffset + 52; - return buffer.getInt(offset); - } - void setTargetPositionX(int value) { + return buffer.getInt(offset); + } + void setTargetPositionX(int value) { buffer.putInt(myOffset + 52, value); - } + } int getTargetPositionY() { int offset = myOffset + 56; - return buffer.getInt(offset); - } - void setTargetPositionY(int value) { + return buffer.getInt(offset); + } + void setTargetPositionY(int value) { buffer.putInt(myOffset + 56, value); - } + } int getRemoveTimer() { int offset = myOffset + 60; - return buffer.getInt(offset); - } - void setRemoveTimer(int value) { + return buffer.getInt(offset); + } + void setRemoveTimer(int value) { buffer.putInt(myOffset + 60, value); - } + } boolean getExists() { int offset = myOffset + 64; - return buffer.getByte(offset) != 0; - } - void setExists(boolean value) { + return buffer.getByte(offset) != 0; + } + void setExists(boolean value) { buffer.putByte(myOffset + 64, (byte) (value ? 1 : 0)); - } + } boolean isVisible(int i) { int offset = myOffset + 65 + 1 * 1 * i; - return buffer.getByte(offset) != 0; - } - void setIsVisible(int i, boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsVisible(int i, boolean value) { buffer.putByte(myOffset + 65 + 1 * 1 * i, (byte) (value ? 1 : 0)); - } - } + } + } class unitFinder { static final int SIZE = 8; - private int myOffset; + private int myOffset; public unitFinder(int myOffset) { - this.myOffset = myOffset; - } + this.myOffset = myOffset; + } int getUnitIndex() { int offset = myOffset + 0; - return buffer.getInt(offset); - } - void setUnitIndex(int value) { + return buffer.getInt(offset); + } + void setUnitIndex(int value) { buffer.putInt(myOffset + 0, value); - } + } int getSearchValue() { int offset = myOffset + 4; - return buffer.getInt(offset); - } - void setSearchValue(int value) { + return buffer.getInt(offset); + } + void setSearchValue(int value) { buffer.putInt(myOffset + 4, value); - } - } + } + } class UnitData { static final int SIZE = 336; - private int myOffset; + private int myOffset; public UnitData(int myOffset) { - this.myOffset = myOffset; - } + this.myOffset = myOffset; + } int getClearanceLevel() { int offset = myOffset + 0; - return buffer.getInt(offset); - } - void setClearanceLevel(int value) { + return buffer.getInt(offset); + } + void setClearanceLevel(int value) { buffer.putInt(myOffset + 0, value); - } + } int getId() { int offset = myOffset + 4; - return buffer.getInt(offset); - } - void setId(int value) { + return buffer.getInt(offset); + } + void setId(int value) { buffer.putInt(myOffset + 4, value); - } + } int getPlayer() { int offset = myOffset + 8; - return buffer.getInt(offset); - } - void setPlayer(int value) { + return buffer.getInt(offset); + } + void setPlayer(int value) { buffer.putInt(myOffset + 8, value); - } + } int getType() { int offset = myOffset + 12; - return buffer.getInt(offset); - } - void setType(int value) { + return buffer.getInt(offset); + } + void setType(int value) { buffer.putInt(myOffset + 12, value); - } + } int getPositionX() { int offset = myOffset + 16; - return buffer.getInt(offset); - } - void setPositionX(int value) { + return buffer.getInt(offset); + } + void setPositionX(int value) { buffer.putInt(myOffset + 16, value); - } + } int getPositionY() { int offset = myOffset + 20; - return buffer.getInt(offset); - } - void setPositionY(int value) { + return buffer.getInt(offset); + } + void setPositionY(int value) { buffer.putInt(myOffset + 20, value); - } + } double getAngle() { int offset = myOffset + 24; - return buffer.getDouble(offset); - } - void setAngle(double value) { + return buffer.getDouble(offset); + } + void setAngle(double value) { buffer.putDouble(myOffset + 24, value); - } + } double getVelocityX() { int offset = myOffset + 32; - return buffer.getDouble(offset); - } - void setVelocityX(double value) { + return buffer.getDouble(offset); + } + void setVelocityX(double value) { buffer.putDouble(myOffset + 32, value); - } + } double getVelocityY() { int offset = myOffset + 40; - return buffer.getDouble(offset); - } - void setVelocityY(double value) { + return buffer.getDouble(offset); + } + void setVelocityY(double value) { buffer.putDouble(myOffset + 40, value); - } + } int getHitPoints() { int offset = myOffset + 48; - return buffer.getInt(offset); - } - void setHitPoints(int value) { + return buffer.getInt(offset); + } + void setHitPoints(int value) { buffer.putInt(myOffset + 48, value); - } + } int getLastHitPoints() { int offset = myOffset + 52; - return buffer.getInt(offset); - } - void setLastHitPoints(int value) { + return buffer.getInt(offset); + } + void setLastHitPoints(int value) { buffer.putInt(myOffset + 52, value); - } + } int getShields() { int offset = myOffset + 56; - return buffer.getInt(offset); - } - void setShields(int value) { + return buffer.getInt(offset); + } + void setShields(int value) { buffer.putInt(myOffset + 56, value); - } + } int getEnergy() { int offset = myOffset + 60; - return buffer.getInt(offset); - } - void setEnergy(int value) { + return buffer.getInt(offset); + } + void setEnergy(int value) { buffer.putInt(myOffset + 60, value); - } + } int getResources() { int offset = myOffset + 64; - return buffer.getInt(offset); - } - void setResources(int value) { + return buffer.getInt(offset); + } + void setResources(int value) { buffer.putInt(myOffset + 64, value); - } + } int getResourceGroup() { int offset = myOffset + 68; - return buffer.getInt(offset); - } - void setResourceGroup(int value) { + return buffer.getInt(offset); + } + void setResourceGroup(int value) { buffer.putInt(myOffset + 68, value); - } + } int getKillCount() { int offset = myOffset + 72; - return buffer.getInt(offset); - } - void setKillCount(int value) { + return buffer.getInt(offset); + } + void setKillCount(int value) { buffer.putInt(myOffset + 72, value); - } + } int getAcidSporeCount() { int offset = myOffset + 76; - return buffer.getInt(offset); - } - void setAcidSporeCount(int value) { + return buffer.getInt(offset); + } + void setAcidSporeCount(int value) { buffer.putInt(myOffset + 76, value); - } + } int getScarabCount() { int offset = myOffset + 80; - return buffer.getInt(offset); - } - void setScarabCount(int value) { + return buffer.getInt(offset); + } + void setScarabCount(int value) { buffer.putInt(myOffset + 80, value); - } + } int getInterceptorCount() { int offset = myOffset + 84; - return buffer.getInt(offset); - } - void setInterceptorCount(int value) { + return buffer.getInt(offset); + } + void setInterceptorCount(int value) { buffer.putInt(myOffset + 84, value); - } + } int getSpiderMineCount() { int offset = myOffset + 88; - return buffer.getInt(offset); - } - void setSpiderMineCount(int value) { + return buffer.getInt(offset); + } + void setSpiderMineCount(int value) { buffer.putInt(myOffset + 88, value); - } + } int getGroundWeaponCooldown() { int offset = myOffset + 92; - return buffer.getInt(offset); - } - void setGroundWeaponCooldown(int value) { + return buffer.getInt(offset); + } + void setGroundWeaponCooldown(int value) { buffer.putInt(myOffset + 92, value); - } + } int getAirWeaponCooldown() { int offset = myOffset + 96; - return buffer.getInt(offset); - } - void setAirWeaponCooldown(int value) { + return buffer.getInt(offset); + } + void setAirWeaponCooldown(int value) { buffer.putInt(myOffset + 96, value); - } + } int getSpellCooldown() { int offset = myOffset + 100; - return buffer.getInt(offset); - } - void setSpellCooldown(int value) { + return buffer.getInt(offset); + } + void setSpellCooldown(int value) { buffer.putInt(myOffset + 100, value); - } + } int getDefenseMatrixPoints() { int offset = myOffset + 104; - return buffer.getInt(offset); - } - void setDefenseMatrixPoints(int value) { + return buffer.getInt(offset); + } + void setDefenseMatrixPoints(int value) { buffer.putInt(myOffset + 104, value); - } + } int getDefenseMatrixTimer() { int offset = myOffset + 108; - return buffer.getInt(offset); - } - void setDefenseMatrixTimer(int value) { + return buffer.getInt(offset); + } + void setDefenseMatrixTimer(int value) { buffer.putInt(myOffset + 108, value); - } + } int getEnsnareTimer() { int offset = myOffset + 112; - return buffer.getInt(offset); - } - void setEnsnareTimer(int value) { + return buffer.getInt(offset); + } + void setEnsnareTimer(int value) { buffer.putInt(myOffset + 112, value); - } + } int getIrradiateTimer() { int offset = myOffset + 116; - return buffer.getInt(offset); - } - void setIrradiateTimer(int value) { + return buffer.getInt(offset); + } + void setIrradiateTimer(int value) { buffer.putInt(myOffset + 116, value); - } + } int getLockdownTimer() { int offset = myOffset + 120; - return buffer.getInt(offset); - } - void setLockdownTimer(int value) { + return buffer.getInt(offset); + } + void setLockdownTimer(int value) { buffer.putInt(myOffset + 120, value); - } + } int getMaelstromTimer() { int offset = myOffset + 124; - return buffer.getInt(offset); - } - void setMaelstromTimer(int value) { + return buffer.getInt(offset); + } + void setMaelstromTimer(int value) { buffer.putInt(myOffset + 124, value); - } + } int getOrderTimer() { int offset = myOffset + 128; - return buffer.getInt(offset); - } - void setOrderTimer(int value) { + return buffer.getInt(offset); + } + void setOrderTimer(int value) { buffer.putInt(myOffset + 128, value); - } + } int getPlagueTimer() { int offset = myOffset + 132; - return buffer.getInt(offset); - } - void setPlagueTimer(int value) { + return buffer.getInt(offset); + } + void setPlagueTimer(int value) { buffer.putInt(myOffset + 132, value); - } + } int getRemoveTimer() { int offset = myOffset + 136; - return buffer.getInt(offset); - } - void setRemoveTimer(int value) { + return buffer.getInt(offset); + } + void setRemoveTimer(int value) { buffer.putInt(myOffset + 136, value); - } + } int getStasisTimer() { int offset = myOffset + 140; - return buffer.getInt(offset); - } - void setStasisTimer(int value) { + return buffer.getInt(offset); + } + void setStasisTimer(int value) { buffer.putInt(myOffset + 140, value); - } + } int getStimTimer() { int offset = myOffset + 144; - return buffer.getInt(offset); - } - void setStimTimer(int value) { + return buffer.getInt(offset); + } + void setStimTimer(int value) { buffer.putInt(myOffset + 144, value); - } + } int getBuildType() { int offset = myOffset + 148; - return buffer.getInt(offset); - } - void setBuildType(int value) { + return buffer.getInt(offset); + } + void setBuildType(int value) { buffer.putInt(myOffset + 148, value); - } + } int getTrainingQueueCount() { int offset = myOffset + 152; - return buffer.getInt(offset); - } - void setTrainingQueueCount(int value) { + return buffer.getInt(offset); + } + void setTrainingQueueCount(int value) { buffer.putInt(myOffset + 152, value); - } + } int getTrainingQueue(int i) { int offset = myOffset + 156 + 4 * 1 * i; - return buffer.getInt(offset); - } - void setTrainingQueue(int i, int value) { + return buffer.getInt(offset); + } + void setTrainingQueue(int i, int value) { buffer.putInt(myOffset + 156 + 4 * 1 * i, value); - } + } int getTech() { int offset = myOffset + 176; - return buffer.getInt(offset); - } - void setTech(int value) { + return buffer.getInt(offset); + } + void setTech(int value) { buffer.putInt(myOffset + 176, value); - } + } int getUpgrade() { int offset = myOffset + 180; - return buffer.getInt(offset); - } - void setUpgrade(int value) { + return buffer.getInt(offset); + } + void setUpgrade(int value) { buffer.putInt(myOffset + 180, value); - } + } int getRemainingBuildTime() { int offset = myOffset + 184; - return buffer.getInt(offset); - } - void setRemainingBuildTime(int value) { + return buffer.getInt(offset); + } + void setRemainingBuildTime(int value) { buffer.putInt(myOffset + 184, value); - } + } int getRemainingTrainTime() { int offset = myOffset + 188; - return buffer.getInt(offset); - } - void setRemainingTrainTime(int value) { + return buffer.getInt(offset); + } + void setRemainingTrainTime(int value) { buffer.putInt(myOffset + 188, value); - } + } int getRemainingResearchTime() { int offset = myOffset + 192; - return buffer.getInt(offset); - } - void setRemainingResearchTime(int value) { + return buffer.getInt(offset); + } + void setRemainingResearchTime(int value) { buffer.putInt(myOffset + 192, value); - } + } int getRemainingUpgradeTime() { int offset = myOffset + 196; - return buffer.getInt(offset); - } - void setRemainingUpgradeTime(int value) { + return buffer.getInt(offset); + } + void setRemainingUpgradeTime(int value) { buffer.putInt(myOffset + 196, value); - } + } int getBuildUnit() { int offset = myOffset + 200; - return buffer.getInt(offset); - } - void setBuildUnit(int value) { + return buffer.getInt(offset); + } + void setBuildUnit(int value) { buffer.putInt(myOffset + 200, value); - } + } int getTarget() { int offset = myOffset + 204; - return buffer.getInt(offset); - } - void setTarget(int value) { + return buffer.getInt(offset); + } + void setTarget(int value) { buffer.putInt(myOffset + 204, value); - } + } int getTargetPositionX() { int offset = myOffset + 208; - return buffer.getInt(offset); - } - void setTargetPositionX(int value) { + return buffer.getInt(offset); + } + void setTargetPositionX(int value) { buffer.putInt(myOffset + 208, value); - } + } int getTargetPositionY() { int offset = myOffset + 212; - return buffer.getInt(offset); - } - void setTargetPositionY(int value) { + return buffer.getInt(offset); + } + void setTargetPositionY(int value) { buffer.putInt(myOffset + 212, value); - } + } int getOrder() { int offset = myOffset + 216; - return buffer.getInt(offset); - } - void setOrder(int value) { + return buffer.getInt(offset); + } + void setOrder(int value) { buffer.putInt(myOffset + 216, value); - } + } int getOrderTarget() { int offset = myOffset + 220; - return buffer.getInt(offset); - } - void setOrderTarget(int value) { + return buffer.getInt(offset); + } + void setOrderTarget(int value) { buffer.putInt(myOffset + 220, value); - } + } int getOrderTargetPositionX() { int offset = myOffset + 224; - return buffer.getInt(offset); - } - void setOrderTargetPositionX(int value) { + return buffer.getInt(offset); + } + void setOrderTargetPositionX(int value) { buffer.putInt(myOffset + 224, value); - } + } int getOrderTargetPositionY() { int offset = myOffset + 228; - return buffer.getInt(offset); - } - void setOrderTargetPositionY(int value) { + return buffer.getInt(offset); + } + void setOrderTargetPositionY(int value) { buffer.putInt(myOffset + 228, value); - } + } int getSecondaryOrder() { int offset = myOffset + 232; - return buffer.getInt(offset); - } - void setSecondaryOrder(int value) { + return buffer.getInt(offset); + } + void setSecondaryOrder(int value) { buffer.putInt(myOffset + 232, value); - } + } int getRallyPositionX() { int offset = myOffset + 236; - return buffer.getInt(offset); - } - void setRallyPositionX(int value) { + return buffer.getInt(offset); + } + void setRallyPositionX(int value) { buffer.putInt(myOffset + 236, value); - } + } int getRallyPositionY() { int offset = myOffset + 240; - return buffer.getInt(offset); - } - void setRallyPositionY(int value) { + return buffer.getInt(offset); + } + void setRallyPositionY(int value) { buffer.putInt(myOffset + 240, value); - } + } int getRallyUnit() { int offset = myOffset + 244; - return buffer.getInt(offset); - } - void setRallyUnit(int value) { + return buffer.getInt(offset); + } + void setRallyUnit(int value) { buffer.putInt(myOffset + 244, value); - } + } int getAddon() { int offset = myOffset + 248; - return buffer.getInt(offset); - } - void setAddon(int value) { + return buffer.getInt(offset); + } + void setAddon(int value) { buffer.putInt(myOffset + 248, value); - } + } int getNydusExit() { int offset = myOffset + 252; - return buffer.getInt(offset); - } - void setNydusExit(int value) { + return buffer.getInt(offset); + } + void setNydusExit(int value) { buffer.putInt(myOffset + 252, value); - } + } int getPowerUp() { int offset = myOffset + 256; - return buffer.getInt(offset); - } - void setPowerUp(int value) { + return buffer.getInt(offset); + } + void setPowerUp(int value) { buffer.putInt(myOffset + 256, value); - } + } int getTransport() { int offset = myOffset + 260; - return buffer.getInt(offset); - } - void setTransport(int value) { + return buffer.getInt(offset); + } + void setTransport(int value) { buffer.putInt(myOffset + 260, value); - } + } int getCarrier() { int offset = myOffset + 264; - return buffer.getInt(offset); - } - void setCarrier(int value) { + return buffer.getInt(offset); + } + void setCarrier(int value) { buffer.putInt(myOffset + 264, value); - } + } int getHatchery() { int offset = myOffset + 268; - return buffer.getInt(offset); - } - void setHatchery(int value) { + return buffer.getInt(offset); + } + void setHatchery(int value) { buffer.putInt(myOffset + 268, value); - } + } boolean getExists() { int offset = myOffset + 272; - return buffer.getByte(offset) != 0; - } - void setExists(boolean value) { + return buffer.getByte(offset) != 0; + } + void setExists(boolean value) { buffer.putByte(myOffset + 272, (byte) (value ? 1 : 0)); - } + } boolean getHasNuke() { int offset = myOffset + 273; - return buffer.getByte(offset) != 0; - } - void setHasNuke(boolean value) { + return buffer.getByte(offset) != 0; + } + void setHasNuke(boolean value) { buffer.putByte(myOffset + 273, (byte) (value ? 1 : 0)); - } + } boolean isAccelerating() { int offset = myOffset + 274; - return buffer.getByte(offset) != 0; - } - void setIsAccelerating(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsAccelerating(boolean value) { buffer.putByte(myOffset + 274, (byte) (value ? 1 : 0)); - } + } boolean isAttacking() { int offset = myOffset + 275; - return buffer.getByte(offset) != 0; - } - void setIsAttacking(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsAttacking(boolean value) { buffer.putByte(myOffset + 275, (byte) (value ? 1 : 0)); - } + } boolean isAttackFrame() { int offset = myOffset + 276; - return buffer.getByte(offset) != 0; - } - void setIsAttackFrame(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsAttackFrame(boolean value) { buffer.putByte(myOffset + 276, (byte) (value ? 1 : 0)); - } + } boolean isBeingGathered() { int offset = myOffset + 277; - return buffer.getByte(offset) != 0; - } - void setIsBeingGathered(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsBeingGathered(boolean value) { buffer.putByte(myOffset + 277, (byte) (value ? 1 : 0)); - } + } boolean isBlind() { int offset = myOffset + 278; - return buffer.getByte(offset) != 0; - } - void setIsBlind(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsBlind(boolean value) { buffer.putByte(myOffset + 278, (byte) (value ? 1 : 0)); - } + } boolean isBraking() { int offset = myOffset + 279; - return buffer.getByte(offset) != 0; - } - void setIsBraking(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsBraking(boolean value) { buffer.putByte(myOffset + 279, (byte) (value ? 1 : 0)); - } + } boolean isBurrowed() { int offset = myOffset + 280; - return buffer.getByte(offset) != 0; - } - void setIsBurrowed(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsBurrowed(boolean value) { buffer.putByte(myOffset + 280, (byte) (value ? 1 : 0)); - } + } int getCarryResourceType() { int offset = myOffset + 284; - return buffer.getInt(offset); - } - void setCarryResourceType(int value) { + return buffer.getInt(offset); + } + void setCarryResourceType(int value) { buffer.putInt(myOffset + 284, value); - } + } boolean isCloaked() { int offset = myOffset + 288; - return buffer.getByte(offset) != 0; - } - void setIsCloaked(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsCloaked(boolean value) { buffer.putByte(myOffset + 288, (byte) (value ? 1 : 0)); - } + } boolean isCompleted() { int offset = myOffset + 289; - return buffer.getByte(offset) != 0; - } - void setIsCompleted(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsCompleted(boolean value) { buffer.putByte(myOffset + 289, (byte) (value ? 1 : 0)); - } + } boolean isConstructing() { int offset = myOffset + 290; - return buffer.getByte(offset) != 0; - } - void setIsConstructing(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsConstructing(boolean value) { buffer.putByte(myOffset + 290, (byte) (value ? 1 : 0)); - } + } boolean isDetected() { int offset = myOffset + 291; - return buffer.getByte(offset) != 0; - } - void setIsDetected(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsDetected(boolean value) { buffer.putByte(myOffset + 291, (byte) (value ? 1 : 0)); - } + } boolean isGathering() { int offset = myOffset + 292; - return buffer.getByte(offset) != 0; - } - void setIsGathering(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsGathering(boolean value) { buffer.putByte(myOffset + 292, (byte) (value ? 1 : 0)); - } + } boolean isHallucination() { int offset = myOffset + 293; - return buffer.getByte(offset) != 0; - } - void setIsHallucination(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsHallucination(boolean value) { buffer.putByte(myOffset + 293, (byte) (value ? 1 : 0)); - } + } boolean isIdle() { int offset = myOffset + 294; - return buffer.getByte(offset) != 0; - } - void setIsIdle(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsIdle(boolean value) { buffer.putByte(myOffset + 294, (byte) (value ? 1 : 0)); - } + } boolean isInterruptible() { int offset = myOffset + 295; - return buffer.getByte(offset) != 0; - } - void setIsInterruptible(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsInterruptible(boolean value) { buffer.putByte(myOffset + 295, (byte) (value ? 1 : 0)); - } + } boolean isInvincible() { int offset = myOffset + 296; - return buffer.getByte(offset) != 0; - } - void setIsInvincible(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsInvincible(boolean value) { buffer.putByte(myOffset + 296, (byte) (value ? 1 : 0)); - } + } boolean isLifted() { int offset = myOffset + 297; - return buffer.getByte(offset) != 0; - } - void setIsLifted(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsLifted(boolean value) { buffer.putByte(myOffset + 297, (byte) (value ? 1 : 0)); - } + } boolean isMorphing() { int offset = myOffset + 298; - return buffer.getByte(offset) != 0; - } - void setIsMorphing(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsMorphing(boolean value) { buffer.putByte(myOffset + 298, (byte) (value ? 1 : 0)); - } + } boolean isMoving() { int offset = myOffset + 299; - return buffer.getByte(offset) != 0; - } - void setIsMoving(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsMoving(boolean value) { buffer.putByte(myOffset + 299, (byte) (value ? 1 : 0)); - } + } boolean isParasited() { int offset = myOffset + 300; - return buffer.getByte(offset) != 0; - } - void setIsParasited(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsParasited(boolean value) { buffer.putByte(myOffset + 300, (byte) (value ? 1 : 0)); - } + } boolean isSelected() { int offset = myOffset + 301; - return buffer.getByte(offset) != 0; - } - void setIsSelected(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsSelected(boolean value) { buffer.putByte(myOffset + 301, (byte) (value ? 1 : 0)); - } + } boolean isStartingAttack() { int offset = myOffset + 302; - return buffer.getByte(offset) != 0; - } - void setIsStartingAttack(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsStartingAttack(boolean value) { buffer.putByte(myOffset + 302, (byte) (value ? 1 : 0)); - } + } boolean isStuck() { int offset = myOffset + 303; - return buffer.getByte(offset) != 0; - } - void setIsStuck(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsStuck(boolean value) { buffer.putByte(myOffset + 303, (byte) (value ? 1 : 0)); - } + } boolean isTraining() { int offset = myOffset + 304; - return buffer.getByte(offset) != 0; - } - void setIsTraining(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsTraining(boolean value) { buffer.putByte(myOffset + 304, (byte) (value ? 1 : 0)); - } + } boolean isUnderStorm() { int offset = myOffset + 305; - return buffer.getByte(offset) != 0; - } - void setIsUnderStorm(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsUnderStorm(boolean value) { buffer.putByte(myOffset + 305, (byte) (value ? 1 : 0)); - } + } boolean isUnderDarkSwarm() { int offset = myOffset + 306; - return buffer.getByte(offset) != 0; - } - void setIsUnderDarkSwarm(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsUnderDarkSwarm(boolean value) { buffer.putByte(myOffset + 306, (byte) (value ? 1 : 0)); - } + } boolean isUnderDWeb() { int offset = myOffset + 307; - return buffer.getByte(offset) != 0; - } - void setIsUnderDWeb(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsUnderDWeb(boolean value) { buffer.putByte(myOffset + 307, (byte) (value ? 1 : 0)); - } + } boolean isPowered() { int offset = myOffset + 308; - return buffer.getByte(offset) != 0; - } - void setIsPowered(boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsPowered(boolean value) { buffer.putByte(myOffset + 308, (byte) (value ? 1 : 0)); - } + } boolean isVisible(int i) { int offset = myOffset + 309 + 1 * 1 * i; - return buffer.getByte(offset) != 0; - } - void setIsVisible(int i, boolean value) { + return buffer.getByte(offset) != 0; + } + void setIsVisible(int i, boolean value) { buffer.putByte(myOffset + 309 + 1 * 1 * i, (byte) (value ? 1 : 0)); - } + } int getButtonset() { int offset = myOffset + 320; - return buffer.getInt(offset); - } - void setButtonset(int value) { + return buffer.getInt(offset); + } + void setButtonset(int value) { buffer.putInt(myOffset + 320, value); - } + } int getLastAttackerPlayer() { int offset = myOffset + 324; - return buffer.getInt(offset); - } - void setLastAttackerPlayer(int value) { + return buffer.getInt(offset); + } + void setLastAttackerPlayer(int value) { buffer.putInt(myOffset + 324, value); - } + } boolean getRecentlyAttacked() { int offset = myOffset + 328; - return buffer.getByte(offset) != 0; - } - void setRecentlyAttacked(boolean value) { + return buffer.getByte(offset) != 0; + } + void setRecentlyAttacked(boolean value) { buffer.putByte(myOffset + 328, (byte) (value ? 1 : 0)); - } + } int getReplayID() { int offset = myOffset + 332; - return buffer.getInt(offset); - } - void setReplayID(int value) { + return buffer.getInt(offset); + } + void setReplayID(int value) { buffer.putInt(myOffset + 332, value); - } - } -} - + } + } +} + From 0cae24e8de67504f4b8615fc33b37a0bde1c6be7 Mon Sep 17 00:00:00 2001 From: dgant Date: Sun, 19 Jul 2020 19:27:54 -0400 Subject: [PATCH 02/56] Extracted portions of Client to GameDataUtils. Removed the bot loop entirely for reinsertion via BotWrapper, so this revision is in a non-working state. --- src/main/java/bwapi/BWClient.java | 5 +- .../java/bwapi/BWClientConfiguration.java | 32 +- src/main/java/bwapi/BotWrapper.java | 62 + src/main/java/bwapi/Client.java | 73 +- src/main/java/bwapi/ClientData.java | 1724 +++++++++-------- src/main/java/bwapi/EventHandler.java | 6 +- src/main/java/bwapi/FrameBuffer.java | 85 + src/main/java/bwapi/Game.java | 14 +- src/main/java/bwapi/GameDataUtils.java | 83 + src/test/java/DumpToClient.java | 12 +- src/test/java/bwapi/ClientDataBenchmark.java | 18 +- 11 files changed, 1159 insertions(+), 955 deletions(-) create mode 100644 src/main/java/bwapi/BotWrapper.java create mode 100644 src/main/java/bwapi/FrameBuffer.java create mode 100644 src/main/java/bwapi/GameDataUtils.java diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index cdacf523..540db4b6 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -44,6 +44,7 @@ public void startGame(boolean autoContinue) { * @param configuration Settings for playing games with this client. */ public void startGame(BWClientConfiguration configuration) { + configuration.validate(); Client client = new Client(configuration); client.reconnect(); handler = new EventHandler(eventListener, client); @@ -53,10 +54,10 @@ public void startGame(BWClientConfiguration configuration) { if (!client.isConnected()) { return; } - client.update(handler); + client.update(); } while (getGame().isInGame()) { - client.update(handler); + client.update(); if (!client.isConnected()) { System.out.println("Reconnecting..."); client.reconnect(); diff --git a/src/main/java/bwapi/BWClientConfiguration.java b/src/main/java/bwapi/BWClientConfiguration.java index bee742d1..5318cfea 100644 --- a/src/main/java/bwapi/BWClientConfiguration.java +++ b/src/main/java/bwapi/BWClientConfiguration.java @@ -5,6 +5,11 @@ */ public class BWClientConfiguration { + /** + * Set to `true` for more explicit error messages (which might spam the terminal). + */ + public boolean debugConnection; + /** * When true, restarts the client loop when a game ends, allowing the client to play multiple games without restarting. */ @@ -24,18 +29,19 @@ public class BWClientConfiguration { public boolean async = false; /** - * If JBWAPI detects that this much time (in nanoseconds) has passed since a bot's event handlers began, returns control back to BWAPI. - * Real-time human play typically uses the "fastest" game speed, which has 42.86ms (42,860ns) between frames. + * How frequently (in nanoseconds) to poll for the bot's event handlers completing. Acts as a floor on the bot's frame duration. */ - public int asyncFrameDurationNanosMax = 40000; + public int asyncFrameDurationNanosMin = 500; /** - * How frequently (in nanoseconds) to poll for the bot's event handlers completing. Acts as a floor on the bot's frame duration. + * If JBWAPI detects that this much time (in nanoseconds) has passed since a bot's event handlers began, returns control back to BWAPI. + * Real-time human play typically uses the "fastest" game speed, which has 42.86ms (42,860ns) between frames. */ - public int asyncFrameDurationNanosMin = 500; + public int asyncFrameDurationNanosMax = 40000; /** - * The maximum number of frames to buffer while waiting on a bot + * The maximum number of frames to buffer while waiting on a bot. + * Each frame buffered adds about 33 megabytes to JBWAPI's memory footprint. */ public int asyncFrameBufferSize = 10; @@ -49,7 +55,17 @@ public class BWClientConfiguration { public boolean asyncWaitOnFrameZero = true; /** - * Set to `true` for more explicit error messages (which might spam the terminal). + * Checks that the configuration is in a valid state. Throws an IllegalArgumentException if it isn't. */ - public boolean debugConnection; + public void validate() { + if (async && asyncFrameDurationNanosMin <= 0) { + throw new IllegalArgumentException("asyncFrameDurationNanosMin needs to be a positive number (it's how long JBWAPI waits to poll for a completed frame."); + } + if (async && asyncFrameDurationNanosMax < 0) { + throw new IllegalArgumentException("asyncFrameDurationNanosMax needs to be a non-negative number (it's how long JBWAPI waits for a bot response before returning control to BWAPI)."); + } + if (async && asyncFrameBufferSize < 1) { + throw new IllegalArgumentException("asyncFrameBufferSize needs to be a positive number (There needs to be at least one frame buffer)."); + } + } } \ No newline at end of file diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java new file mode 100644 index 00000000..b206826e --- /dev/null +++ b/src/main/java/bwapi/BotWrapper.java @@ -0,0 +1,62 @@ +/* +MIT License + +Copyright (c) 2018 Hannes Bredberg +Modified work Copyright (c) 2018 Jasper + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package bwapi; + +import java.nio.ByteBuffer; + +/** + * Manages invocation of bot event handlers + */ +public class BotWrapper { + private EventHandler eventHandler; + private ByteBuffer sharedMemory; + private BWClientConfiguration configuration; + + private FrameBuffer frameBuffer; + + public BotWrapper(ByteBuffer sharedMemory, EventHandler eventHandler, BWClientConfiguration configuration) { + this.sharedMemory = sharedMemory; + this.eventHandler = eventHandler; + this.configuration = configuration; + + if (configuration.async) { + frameBuffer = new FrameBuffer(configuration.asyncFrameBufferSize); + } + } + + public void run() { + if (configuration.async) { + // TODO: Synchronize + frameBuffer.enqueueFrame(); + } else { + /* + for (int i = 0; i < gameData.getEventCount(); i++) { + eventHandler.operation(gameData.getEvents(i)); + } + */ + } + } +} diff --git a/src/main/java/bwapi/Client.java b/src/main/java/bwapi/Client.java index 53c89693..fc1a5283 100644 --- a/src/main/java/bwapi/Client.java +++ b/src/main/java/bwapi/Client.java @@ -25,9 +25,7 @@ of this software and associated documentation files (the "Software"), to deal package bwapi; -import bwapi.ClientData.Command; import bwapi.ClientData.GameData; -import bwapi.ClientData.Shape; import com.sun.jna.Native; import com.sun.jna.platform.win32.Kernel32; import com.sun.jna.win32.W32APIOptions; @@ -48,17 +46,14 @@ public interface EventHandler { } private static final int READ_WRITE = 0x1 | 0x2 | 0x4; - private static final int SUPPORTED_BWAPI_VERSION = 10003; - static final int MAX_COUNT = 19999; - static final int MAX_STRING_SIZE = 1024; private ClientData clientData; private ClientData.GameData gameData; private boolean connected = false; private RandomAccessFile pipeObjectHandle = null; - private ByteBuffer mapFileHandle = null; private ByteBuffer gameTableFileHandle = null; + private ByteBuffer mapFileHandle = null; private BWClientConfiguration configuration = new BWClientConfiguration(); @@ -70,7 +65,8 @@ public interface EventHandler { * For test purposes only */ Client(ByteBuffer buffer) { - clientData = new ClientData(buffer); + clientData = new ClientData(); + clientData.setBuffer(buffer); gameData = clientData.new GameData(0); } @@ -86,7 +82,7 @@ boolean isConnected() { return connected; } - void reconnect(){ + void reconnect() { while (!connect()) { sleep(1000); } @@ -111,8 +107,8 @@ void disconnect() { pipeObjectHandle = null; } - mapFileHandle = null; gameTableFileHandle = null; + mapFileHandle = null; gameData = null; connected = false; } @@ -126,6 +122,7 @@ boolean connect() { int serverProcID = -1; int gameTableIndex = -1; + // Expose the BWAPI list of games from shared memory via a ByteBuffer try { gameTableFileHandle = Kernel32.INSTANCE.MapViewOfFile( MappingKernel.INSTANCE.OpenFileMapping(READ_WRITE, false, "Local\\bwapi_shared_memory_game_list"), READ_WRITE, 0, 0, GameTable.SIZE) @@ -185,6 +182,7 @@ boolean connect() { } System.out.println("Connected"); + // Expose the raw game data from shared memory via a ByteBuffer try { mapFileHandle = Kernel32.INSTANCE.MapViewOfFile(MappingKernel.INSTANCE .OpenFileMapping(READ_WRITE, false, sharedMemoryName), READ_WRITE, @@ -200,7 +198,8 @@ boolean connect() { return false; } try { - clientData = new ClientData(mapFileHandle); + clientData = new ClientData(); + clientData.setBuffer(mapFileHandle); gameData = clientData.new GameData(0); } catch (Exception e) { @@ -239,7 +238,7 @@ boolean connect() { return true; } - void update(final EventHandler handler) { + void update() { byte code = 1; try { pipeObjectHandle.writeByte(code); @@ -265,60 +264,10 @@ void update(final EventHandler handler) { return; } } - for (int i = 0; i < gameData.getEventCount(); i++) { - handler.operation(gameData.getEvents(i)); - } - } - - String eventString(final int s) { - return gameData.getEventStrings(s); - } - - int addString(final String string) { - int stringCount = gameData.getStringCount(); - if (stringCount >= MAX_COUNT) { - throw new IllegalStateException("Too many strings!"); - } - - //truncate string if its size equals or exceeds 1024 - final String stringTruncated = string.length() >= MAX_STRING_SIZE - ? string.substring(0, MAX_STRING_SIZE - 1) - : string; - - gameData.setStringCount(stringCount + 1); - gameData.setStrings(stringCount, stringTruncated); - return stringCount; - } - - Shape addShape() { - int shapeCount = gameData.getShapeCount(); - if (shapeCount >= MAX_COUNT) { - throw new IllegalStateException("Too many shapes!"); - } - gameData.setShapeCount(shapeCount + 1); - return gameData.getShapes(shapeCount); - } - - Command addCommand() { - final int commandCount = gameData.getCommandCount(); - if (commandCount >= MAX_COUNT) { - throw new IllegalStateException("Too many commands!"); - } - gameData.setCommandCount(commandCount + 1); - return gameData.getCommands(commandCount); - } - - ClientData.UnitCommand addUnitCommand() { - int unitCommandCount = gameData.getUnitCommandCount(); - if (unitCommandCount >= MAX_COUNT) { - throw new IllegalStateException("Too many unit commands!"); - } - gameData.setUnitCommandCount(unitCommandCount + 1); - return gameData.getUnitCommands(unitCommandCount); } private void sleep(final int millis) { - try{ + try { Thread.sleep(millis); } catch (Exception ignored) { diff --git a/src/main/java/bwapi/ClientData.java b/src/main/java/bwapi/ClientData.java index dd77bfa5..e5b51bb1 100644 --- a/src/main/java/bwapi/ClientData.java +++ b/src/main/java/bwapi/ClientData.java @@ -1,1992 +1,1996 @@ package bwapi; import java.nio.ByteBuffer; final class ClientData { - final WrappedBuffer buffer; - ClientData(final ByteBuffer buffer) { + WrappedBuffer buffer; + private int bufferOffset = 0; + public void setBuffer(ByteBuffer buffer) { this.buffer = new WrappedBuffer(buffer); } - class UnitCommand { - static final int SIZE = 24; + public void setBufferOffset(int offset) { + this.bufferOffset = offset; + } + class UnitCommand { + static final int SIZE = 24; private int myOffset; - public UnitCommand(int myOffset) { + public UnitCommand(int myOffset) { this.myOffset = myOffset; } - int getTid() { - int offset = myOffset + 0; + int getTid() { + int offset = bufferOffset + myOffset + 0; return buffer.getInt(offset); } void setTid(int value) { - buffer.putInt(myOffset + 0, value); + buffer.putInt(bufferOffset + myOffset + 0, value); } - int getUnitIndex() { - int offset = myOffset + 4; + int getUnitIndex() { + int offset = bufferOffset + myOffset + 4; return buffer.getInt(offset); } void setUnitIndex(int value) { - buffer.putInt(myOffset + 4, value); + buffer.putInt(bufferOffset + myOffset + 4, value); } - int getTargetIndex() { - int offset = myOffset + 8; + int getTargetIndex() { + int offset = bufferOffset + myOffset + 8; return buffer.getInt(offset); } void setTargetIndex(int value) { - buffer.putInt(myOffset + 8, value); + buffer.putInt(bufferOffset + myOffset + 8, value); } - int getX() { - int offset = myOffset + 12; + int getX() { + int offset = bufferOffset + myOffset + 12; return buffer.getInt(offset); } void setX(int value) { - buffer.putInt(myOffset + 12, value); + buffer.putInt(bufferOffset + myOffset + 12, value); } - int getY() { - int offset = myOffset + 16; + int getY() { + int offset = bufferOffset + myOffset + 16; return buffer.getInt(offset); } void setY(int value) { - buffer.putInt(myOffset + 16, value); + buffer.putInt(bufferOffset + myOffset + 16, value); } - int getExtra() { - int offset = myOffset + 20; + int getExtra() { + int offset = bufferOffset + myOffset + 20; return buffer.getInt(offset); } void setExtra(int value) { - buffer.putInt(myOffset + 20, value); + buffer.putInt(bufferOffset + myOffset + 20, value); } } - class GameData { - static final int SIZE = 33017048; + class GameData { + static final int SIZE = 33017048; private int myOffset; - public GameData(int myOffset) { + public GameData(int myOffset) { this.myOffset = myOffset; } - int getClient_version() { - int offset = myOffset + 0; + int getClient_version() { + int offset = bufferOffset + myOffset + 0; return buffer.getInt(offset); } void setClient_version(int value) { - buffer.putInt(myOffset + 0, value); + buffer.putInt(bufferOffset + myOffset + 0, value); } - int getRevision() { - int offset = myOffset + 4; + int getRevision() { + int offset = bufferOffset + myOffset + 4; return buffer.getInt(offset); } void setRevision(int value) { - buffer.putInt(myOffset + 4, value); + buffer.putInt(bufferOffset + myOffset + 4, value); } - boolean isDebug() { - int offset = myOffset + 8; + boolean isDebug() { + int offset = bufferOffset + myOffset + 8; return buffer.getByte(offset) != 0; } void setIsDebug(boolean value) { - buffer.putByte(myOffset + 8, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 8, (byte) (value ? 1 : 0)); } - int getInstanceID() { - int offset = myOffset + 12; + int getInstanceID() { + int offset = bufferOffset + myOffset + 12; return buffer.getInt(offset); } void setInstanceID(int value) { - buffer.putInt(myOffset + 12, value); + buffer.putInt(bufferOffset + myOffset + 12, value); } - int getBotAPM_noselects() { - int offset = myOffset + 16; + int getBotAPM_noselects() { + int offset = bufferOffset + myOffset + 16; return buffer.getInt(offset); } void setBotAPM_noselects(int value) { - buffer.putInt(myOffset + 16, value); + buffer.putInt(bufferOffset + myOffset + 16, value); } - int getBotAPM_selects() { - int offset = myOffset + 20; + int getBotAPM_selects() { + int offset = bufferOffset + myOffset + 20; return buffer.getInt(offset); } void setBotAPM_selects(int value) { - buffer.putInt(myOffset + 20, value); + buffer.putInt(bufferOffset + myOffset + 20, value); } - int getForceCount() { - int offset = myOffset + 24; + int getForceCount() { + int offset = bufferOffset + myOffset + 24; return buffer.getInt(offset); } void setForceCount(int value) { - buffer.putInt(myOffset + 24, value); + buffer.putInt(bufferOffset + myOffset + 24, value); } - ForceData getForces(int i) { - int offset = myOffset + 28 + 32 * 1 * i; + ForceData getForces(int i) { + int offset = bufferOffset + myOffset + 28 + 32 * 1 * i; return new ForceData(offset); } - int getPlayerCount() { - int offset = myOffset + 188; + int getPlayerCount() { + int offset = bufferOffset + myOffset + 188; return buffer.getInt(offset); } void setPlayerCount(int value) { - buffer.putInt(myOffset + 188, value); + buffer.putInt(bufferOffset + myOffset + 188, value); } - PlayerData getPlayers(int i) { - int offset = myOffset + 192 + 5788 * 1 * i; + PlayerData getPlayers(int i) { + int offset = bufferOffset + myOffset + 192 + 5788 * 1 * i; return new PlayerData(offset); } - int getInitialUnitCount() { - int offset = myOffset + 69648; + int getInitialUnitCount() { + int offset = bufferOffset + myOffset + 69648; return buffer.getInt(offset); } void setInitialUnitCount(int value) { - buffer.putInt(myOffset + 69648, value); + buffer.putInt(bufferOffset + myOffset + 69648, value); } - UnitData getUnits(int i) { - int offset = myOffset + 69656 + 336 * 1 * i; + UnitData getUnits(int i) { + int offset = bufferOffset + myOffset + 69656 + 336 * 1 * i; return new UnitData(offset); } - int getUnitArray(int i) { - int offset = myOffset + 3429656 + 4 * 1 * i; + int getUnitArray(int i) { + int offset = bufferOffset + myOffset + 3429656 + 4 * 1 * i; return buffer.getInt(offset); } void setUnitArray(int i, int value) { - buffer.putInt(myOffset + 3429656 + 4 * 1 * i, value); + buffer.putInt(bufferOffset + myOffset + 3429656 + 4 * 1 * i, value); } - BulletData getBullets(int i) { - int offset = myOffset + 3436456 + 80 * 1 * i; + BulletData getBullets(int i) { + int offset = bufferOffset + myOffset + 3436456 + 80 * 1 * i; return new BulletData(offset); } - int getNukeDotCount() { - int offset = myOffset + 3444456; + int getNukeDotCount() { + int offset = bufferOffset + myOffset + 3444456; return buffer.getInt(offset); } void setNukeDotCount(int value) { - buffer.putInt(myOffset + 3444456, value); + buffer.putInt(bufferOffset + myOffset + 3444456, value); } - Position getNukeDots(int i) { - int offset = myOffset + 3444460 + 8 * 1 * i; + Position getNukeDots(int i) { + int offset = bufferOffset + myOffset + 3444460 + 8 * 1 * i; return new Position(offset); } - int getGameType() { - int offset = myOffset + 3446060; + int getGameType() { + int offset = bufferOffset + myOffset + 3446060; return buffer.getInt(offset); } void setGameType(int value) { - buffer.putInt(myOffset + 3446060, value); + buffer.putInt(bufferOffset + myOffset + 3446060, value); } - int getLatency() { - int offset = myOffset + 3446064; + int getLatency() { + int offset = bufferOffset + myOffset + 3446064; return buffer.getInt(offset); } void setLatency(int value) { - buffer.putInt(myOffset + 3446064, value); + buffer.putInt(bufferOffset + myOffset + 3446064, value); } - int getLatencyFrames() { - int offset = myOffset + 3446068; + int getLatencyFrames() { + int offset = bufferOffset + myOffset + 3446068; return buffer.getInt(offset); } void setLatencyFrames(int value) { - buffer.putInt(myOffset + 3446068, value); + buffer.putInt(bufferOffset + myOffset + 3446068, value); } - int getLatencyTime() { - int offset = myOffset + 3446072; + int getLatencyTime() { + int offset = bufferOffset + myOffset + 3446072; return buffer.getInt(offset); } void setLatencyTime(int value) { - buffer.putInt(myOffset + 3446072, value); + buffer.putInt(bufferOffset + myOffset + 3446072, value); } - int getRemainingLatencyFrames() { - int offset = myOffset + 3446076; + int getRemainingLatencyFrames() { + int offset = bufferOffset + myOffset + 3446076; return buffer.getInt(offset); } void setRemainingLatencyFrames(int value) { - buffer.putInt(myOffset + 3446076, value); + buffer.putInt(bufferOffset + myOffset + 3446076, value); } - int getRemainingLatencyTime() { - int offset = myOffset + 3446080; + int getRemainingLatencyTime() { + int offset = bufferOffset + myOffset + 3446080; return buffer.getInt(offset); } void setRemainingLatencyTime(int value) { - buffer.putInt(myOffset + 3446080, value); + buffer.putInt(bufferOffset + myOffset + 3446080, value); } - boolean getHasLatCom() { - int offset = myOffset + 3446084; + boolean getHasLatCom() { + int offset = bufferOffset + myOffset + 3446084; return buffer.getByte(offset) != 0; } void setHasLatCom(boolean value) { - buffer.putByte(myOffset + 3446084, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 3446084, (byte) (value ? 1 : 0)); } - boolean getHasGUI() { - int offset = myOffset + 3446085; + boolean getHasGUI() { + int offset = bufferOffset + myOffset + 3446085; return buffer.getByte(offset) != 0; } void setHasGUI(boolean value) { - buffer.putByte(myOffset + 3446085, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 3446085, (byte) (value ? 1 : 0)); } - int getReplayFrameCount() { - int offset = myOffset + 3446088; + int getReplayFrameCount() { + int offset = bufferOffset + myOffset + 3446088; return buffer.getInt(offset); } void setReplayFrameCount(int value) { - buffer.putInt(myOffset + 3446088, value); + buffer.putInt(bufferOffset + myOffset + 3446088, value); } - int getRandomSeed() { - int offset = myOffset + 3446092; + int getRandomSeed() { + int offset = bufferOffset + myOffset + 3446092; return buffer.getInt(offset); } void setRandomSeed(int value) { - buffer.putInt(myOffset + 3446092, value); + buffer.putInt(bufferOffset + myOffset + 3446092, value); } - int getFrameCount() { - int offset = myOffset + 3446096; + int getFrameCount() { + int offset = bufferOffset + myOffset + 3446096; return buffer.getInt(offset); } void setFrameCount(int value) { - buffer.putInt(myOffset + 3446096, value); + buffer.putInt(bufferOffset + myOffset + 3446096, value); } - int getElapsedTime() { - int offset = myOffset + 3446100; + int getElapsedTime() { + int offset = bufferOffset + myOffset + 3446100; return buffer.getInt(offset); } void setElapsedTime(int value) { - buffer.putInt(myOffset + 3446100, value); + buffer.putInt(bufferOffset + myOffset + 3446100, value); } - int getCountdownTimer() { - int offset = myOffset + 3446104; + int getCountdownTimer() { + int offset = bufferOffset + myOffset + 3446104; return buffer.getInt(offset); } void setCountdownTimer(int value) { - buffer.putInt(myOffset + 3446104, value); + buffer.putInt(bufferOffset + myOffset + 3446104, value); } - int getFps() { - int offset = myOffset + 3446108; + int getFps() { + int offset = bufferOffset + myOffset + 3446108; return buffer.getInt(offset); } void setFps(int value) { - buffer.putInt(myOffset + 3446108, value); + buffer.putInt(bufferOffset + myOffset + 3446108, value); } - double getAverageFPS() { - int offset = myOffset + 3446112; + double getAverageFPS() { + int offset = bufferOffset + myOffset + 3446112; return buffer.getDouble(offset); } void setAverageFPS(double value) { - buffer.putDouble(myOffset + 3446112, value); + buffer.putDouble(bufferOffset + myOffset + 3446112, value); } - int getMouseX() { - int offset = myOffset + 3446120; + int getMouseX() { + int offset = bufferOffset + myOffset + 3446120; return buffer.getInt(offset); } void setMouseX(int value) { - buffer.putInt(myOffset + 3446120, value); + buffer.putInt(bufferOffset + myOffset + 3446120, value); } - int getMouseY() { - int offset = myOffset + 3446124; + int getMouseY() { + int offset = bufferOffset + myOffset + 3446124; return buffer.getInt(offset); } void setMouseY(int value) { - buffer.putInt(myOffset + 3446124, value); + buffer.putInt(bufferOffset + myOffset + 3446124, value); } - boolean getMouseState(int i) { - int offset = myOffset + 3446128 + 1 * 1 * i; + boolean getMouseState(int i) { + int offset = bufferOffset + myOffset + 3446128 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setMouseState(int i, boolean value) { - buffer.putByte(myOffset + 3446128 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 3446128 + 1 * 1 * i, (byte) (value ? 1 : 0)); } - boolean getKeyState(int i) { - int offset = myOffset + 3446131 + 1 * 1 * i; + boolean getKeyState(int i) { + int offset = bufferOffset + myOffset + 3446131 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setKeyState(int i, boolean value) { - buffer.putByte(myOffset + 3446131 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 3446131 + 1 * 1 * i, (byte) (value ? 1 : 0)); } - int getScreenX() { - int offset = myOffset + 3446388; + int getScreenX() { + int offset = bufferOffset + myOffset + 3446388; return buffer.getInt(offset); } void setScreenX(int value) { - buffer.putInt(myOffset + 3446388, value); + buffer.putInt(bufferOffset + myOffset + 3446388, value); } - int getScreenY() { - int offset = myOffset + 3446392; + int getScreenY() { + int offset = bufferOffset + myOffset + 3446392; return buffer.getInt(offset); } void setScreenY(int value) { - buffer.putInt(myOffset + 3446392, value); + buffer.putInt(bufferOffset + myOffset + 3446392, value); } - boolean getFlags(int i) { - int offset = myOffset + 3446396 + 1 * 1 * i; + boolean getFlags(int i) { + int offset = bufferOffset + myOffset + 3446396 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setFlags(int i, boolean value) { - buffer.putByte(myOffset + 3446396 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 3446396 + 1 * 1 * i, (byte) (value ? 1 : 0)); } - int getMapWidth() { - int offset = myOffset + 3446400; + int getMapWidth() { + int offset = bufferOffset + myOffset + 3446400; return buffer.getInt(offset); } void setMapWidth(int value) { - buffer.putInt(myOffset + 3446400, value); + buffer.putInt(bufferOffset + myOffset + 3446400, value); } - int getMapHeight() { - int offset = myOffset + 3446404; + int getMapHeight() { + int offset = bufferOffset + myOffset + 3446404; return buffer.getInt(offset); } void setMapHeight(int value) { - buffer.putInt(myOffset + 3446404, value); + buffer.putInt(bufferOffset + myOffset + 3446404, value); } - String getMapFileName() { - int offset = myOffset + 3446408; + String getMapFileName() { + int offset = bufferOffset + myOffset + 3446408; return buffer.getString(offset, 261); } void setMapFileName(String value) { - buffer.putString(myOffset + 3446408, 261, value); + buffer.putString(bufferOffset + myOffset + 3446408, 261, value); } - String getMapPathName() { - int offset = myOffset + 3446669; + String getMapPathName() { + int offset = bufferOffset + myOffset + 3446669; return buffer.getString(offset, 261); } void setMapPathName(String value) { - buffer.putString(myOffset + 3446669, 261, value); + buffer.putString(bufferOffset + myOffset + 3446669, 261, value); } - String getMapName() { - int offset = myOffset + 3446930; + String getMapName() { + int offset = bufferOffset + myOffset + 3446930; return buffer.getString(offset, 33); } void setMapName(String value) { - buffer.putString(myOffset + 3446930, 33, value); + buffer.putString(bufferOffset + myOffset + 3446930, 33, value); } - String getMapHash() { - int offset = myOffset + 3446963; + String getMapHash() { + int offset = bufferOffset + myOffset + 3446963; return buffer.getString(offset, 41); } void setMapHash(String value) { - buffer.putString(myOffset + 3446963, 41, value); + buffer.putString(bufferOffset + myOffset + 3446963, 41, value); } - int getGroundHeight(int i, int j) { - int offset = myOffset + 3447004 + 4 * 1 * j + 4 * 256 * i; + int getGroundHeight(int i, int j) { + int offset = bufferOffset + myOffset + 3447004 + 4 * 1 * j + 4 * 256 * i; return buffer.getInt(offset); } void setGetGroundHeight(int i, int j, int value) { - buffer.putInt(myOffset + 3447004 + 4 * 1 * j + 4 * 256 * i, value); + buffer.putInt(bufferOffset + myOffset + 3447004 + 4 * 1 * j + 4 * 256 * i, value); } - boolean isWalkable(int i, int j) { - int offset = myOffset + 3709148 + 1 * 1 * j + 1 * 1024 * i; + boolean isWalkable(int i, int j) { + int offset = bufferOffset + myOffset + 3709148 + 1 * 1 * j + 1 * 1024 * i; return buffer.getByte(offset) != 0; } void setIsWalkable(int i, int j, boolean value) { - buffer.putByte(myOffset + 3709148 + 1 * 1 * j + 1 * 1024 * i, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 3709148 + 1 * 1 * j + 1 * 1024 * i, (byte) (value ? 1 : 0)); } - boolean isBuildable(int i, int j) { - int offset = myOffset + 4757724 + 1 * 1 * j + 1 * 256 * i; + boolean isBuildable(int i, int j) { + int offset = bufferOffset + myOffset + 4757724 + 1 * 1 * j + 1 * 256 * i; return buffer.getByte(offset) != 0; } void setIsBuildable(int i, int j, boolean value) { - buffer.putByte(myOffset + 4757724 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 4757724 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); } - boolean isVisible(int i, int j) { - int offset = myOffset + 4823260 + 1 * 1 * j + 1 * 256 * i; + boolean isVisible(int i, int j) { + int offset = bufferOffset + myOffset + 4823260 + 1 * 1 * j + 1 * 256 * i; return buffer.getByte(offset) != 0; } void setIsVisible(int i, int j, boolean value) { - buffer.putByte(myOffset + 4823260 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 4823260 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); } - boolean isExplored(int i, int j) { - int offset = myOffset + 4888796 + 1 * 1 * j + 1 * 256 * i; + boolean isExplored(int i, int j) { + int offset = bufferOffset + myOffset + 4888796 + 1 * 1 * j + 1 * 256 * i; return buffer.getByte(offset) != 0; } void setIsExplored(int i, int j, boolean value) { - buffer.putByte(myOffset + 4888796 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 4888796 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); } - boolean getHasCreep(int i, int j) { - int offset = myOffset + 4954332 + 1 * 1 * j + 1 * 256 * i; + boolean getHasCreep(int i, int j) { + int offset = bufferOffset + myOffset + 4954332 + 1 * 1 * j + 1 * 256 * i; return buffer.getByte(offset) != 0; } void setHasCreep(int i, int j, boolean value) { - buffer.putByte(myOffset + 4954332 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 4954332 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); } - boolean isOccupied(int i, int j) { - int offset = myOffset + 5019868 + 1 * 1 * j + 1 * 256 * i; + boolean isOccupied(int i, int j) { + int offset = bufferOffset + myOffset + 5019868 + 1 * 1 * j + 1 * 256 * i; return buffer.getByte(offset) != 0; } void setIsOccupied(int i, int j, boolean value) { - buffer.putByte(myOffset + 5019868 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 5019868 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); } - short getMapTileRegionId(int i, int j) { - int offset = myOffset + 5085404 + 2 * 1 * j + 2 * 256 * i; + short getMapTileRegionId(int i, int j) { + int offset = bufferOffset + myOffset + 5085404 + 2 * 1 * j + 2 * 256 * i; return buffer.getShort(offset); } void setMapTileRegionId(int i, int j, short value) { - buffer.putShort(myOffset + 5085404 + 2 * 1 * j + 2 * 256 * i, value); + buffer.putShort(bufferOffset + myOffset + 5085404 + 2 * 1 * j + 2 * 256 * i, value); } - short getMapSplitTilesMiniTileMask(int i) { - int offset = myOffset + 5216476 + 2 * 1 * i; + short getMapSplitTilesMiniTileMask(int i) { + int offset = bufferOffset + myOffset + 5216476 + 2 * 1 * i; return buffer.getShort(offset); } void setMapSplitTilesMiniTileMask(int i, short value) { - buffer.putShort(myOffset + 5216476 + 2 * 1 * i, value); + buffer.putShort(bufferOffset + myOffset + 5216476 + 2 * 1 * i, value); } - short getMapSplitTilesRegion1(int i) { - int offset = myOffset + 5226476 + 2 * 1 * i; + short getMapSplitTilesRegion1(int i) { + int offset = bufferOffset + myOffset + 5226476 + 2 * 1 * i; return buffer.getShort(offset); } void setMapSplitTilesRegion1(int i, short value) { - buffer.putShort(myOffset + 5226476 + 2 * 1 * i, value); + buffer.putShort(bufferOffset + myOffset + 5226476 + 2 * 1 * i, value); } - short getMapSplitTilesRegion2(int i) { - int offset = myOffset + 5236476 + 2 * 1 * i; + short getMapSplitTilesRegion2(int i) { + int offset = bufferOffset + myOffset + 5236476 + 2 * 1 * i; return buffer.getShort(offset); } void setMapSplitTilesRegion2(int i, short value) { - buffer.putShort(myOffset + 5236476 + 2 * 1 * i, value); + buffer.putShort(bufferOffset + myOffset + 5236476 + 2 * 1 * i, value); } - int getRegionCount() { - int offset = myOffset + 5246476; + int getRegionCount() { + int offset = bufferOffset + myOffset + 5246476; return buffer.getInt(offset); } void setRegionCount(int value) { - buffer.putInt(myOffset + 5246476, value); + buffer.putInt(bufferOffset + myOffset + 5246476, value); } - RegionData getRegions(int i) { - int offset = myOffset + 5246480 + 1068 * 1 * i; + RegionData getRegions(int i) { + int offset = bufferOffset + myOffset + 5246480 + 1068 * 1 * i; return new RegionData(offset); } - int getStartLocationCount() { - int offset = myOffset + 10586480; + int getStartLocationCount() { + int offset = bufferOffset + myOffset + 10586480; return buffer.getInt(offset); } void setStartLocationCount(int value) { - buffer.putInt(myOffset + 10586480, value); + buffer.putInt(bufferOffset + myOffset + 10586480, value); } - Position getStartLocations(int i) { - int offset = myOffset + 10586484 + 8 * 1 * i; + Position getStartLocations(int i) { + int offset = bufferOffset + myOffset + 10586484 + 8 * 1 * i; return new Position(offset); } - boolean isInGame() { - int offset = myOffset + 10586548; + boolean isInGame() { + int offset = bufferOffset + myOffset + 10586548; return buffer.getByte(offset) != 0; } void setIsInGame(boolean value) { - buffer.putByte(myOffset + 10586548, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 10586548, (byte) (value ? 1 : 0)); } - boolean isMultiplayer() { - int offset = myOffset + 10586549; + boolean isMultiplayer() { + int offset = bufferOffset + myOffset + 10586549; return buffer.getByte(offset) != 0; } void setIsMultiplayer(boolean value) { - buffer.putByte(myOffset + 10586549, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 10586549, (byte) (value ? 1 : 0)); } - boolean isBattleNet() { - int offset = myOffset + 10586550; + boolean isBattleNet() { + int offset = bufferOffset + myOffset + 10586550; return buffer.getByte(offset) != 0; } void setIsBattleNet(boolean value) { - buffer.putByte(myOffset + 10586550, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 10586550, (byte) (value ? 1 : 0)); } - boolean isPaused() { - int offset = myOffset + 10586551; + boolean isPaused() { + int offset = bufferOffset + myOffset + 10586551; return buffer.getByte(offset) != 0; } void setIsPaused(boolean value) { - buffer.putByte(myOffset + 10586551, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 10586551, (byte) (value ? 1 : 0)); } - boolean isReplay() { - int offset = myOffset + 10586552; + boolean isReplay() { + int offset = bufferOffset + myOffset + 10586552; return buffer.getByte(offset) != 0; } void setIsReplay(boolean value) { - buffer.putByte(myOffset + 10586552, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 10586552, (byte) (value ? 1 : 0)); } - int getSelectedUnitCount() { - int offset = myOffset + 10586556; + int getSelectedUnitCount() { + int offset = bufferOffset + myOffset + 10586556; return buffer.getInt(offset); } void setSelectedUnitCount(int value) { - buffer.putInt(myOffset + 10586556, value); + buffer.putInt(bufferOffset + myOffset + 10586556, value); } - int getSelectedUnits(int i) { - int offset = myOffset + 10586560 + 4 * 1 * i; + int getSelectedUnits(int i) { + int offset = bufferOffset + myOffset + 10586560 + 4 * 1 * i; return buffer.getInt(offset); } void setSelectedUnits(int i, int value) { - buffer.putInt(myOffset + 10586560 + 4 * 1 * i, value); + buffer.putInt(bufferOffset + myOffset + 10586560 + 4 * 1 * i, value); } - int getSelf() { - int offset = myOffset + 10586608; + int getSelf() { + int offset = bufferOffset + myOffset + 10586608; return buffer.getInt(offset); } void setSelf(int value) { - buffer.putInt(myOffset + 10586608, value); + buffer.putInt(bufferOffset + myOffset + 10586608, value); } - int getEnemy() { - int offset = myOffset + 10586612; + int getEnemy() { + int offset = bufferOffset + myOffset + 10586612; return buffer.getInt(offset); } void setEnemy(int value) { - buffer.putInt(myOffset + 10586612, value); + buffer.putInt(bufferOffset + myOffset + 10586612, value); } - int getNeutral() { - int offset = myOffset + 10586616; + int getNeutral() { + int offset = bufferOffset + myOffset + 10586616; return buffer.getInt(offset); } void setNeutral(int value) { - buffer.putInt(myOffset + 10586616, value); + buffer.putInt(bufferOffset + myOffset + 10586616, value); } - int getEventCount() { - int offset = myOffset + 10586620; + int getEventCount() { + int offset = bufferOffset + myOffset + 10586620; return buffer.getInt(offset); } void setEventCount(int value) { - buffer.putInt(myOffset + 10586620, value); + buffer.putInt(bufferOffset + myOffset + 10586620, value); } - Event getEvents(int i) { - int offset = myOffset + 10586624 + 12 * 1 * i; + Event getEvents(int i) { + int offset = bufferOffset + myOffset + 10586624 + 12 * 1 * i; return new Event(offset); } - int getEventStringCount() { - int offset = myOffset + 10706624; + int getEventStringCount() { + int offset = bufferOffset + myOffset + 10706624; return buffer.getInt(offset); } void setEventStringCount(int value) { - buffer.putInt(myOffset + 10706624, value); + buffer.putInt(bufferOffset + myOffset + 10706624, value); } - String getEventStrings(int i) { - int offset = myOffset + 10706628 + 1 * 256 * i; + String getEventStrings(int i) { + int offset = bufferOffset + myOffset + 10706628 + 1 * 256 * i; return buffer.getString(offset, 256); } void setEventStrings(int i, String value) { - buffer.putString(myOffset + 10706628 + 1 * 256 * i, 256, value); + buffer.putString(bufferOffset + myOffset + 10706628 + 1 * 256 * i, 256, value); } - int getStringCount() { - int offset = myOffset + 10962628; + int getStringCount() { + int offset = bufferOffset + myOffset + 10962628; return buffer.getInt(offset); } void setStringCount(int value) { - buffer.putInt(myOffset + 10962628, value); + buffer.putInt(bufferOffset + myOffset + 10962628, value); } - String getStrings(int i) { - int offset = myOffset + 10962632 + 1 * 1024 * i; + String getStrings(int i) { + int offset = bufferOffset + myOffset + 10962632 + 1 * 1024 * i; return buffer.getString(offset, 1024); } void setStrings(int i, String value) { - buffer.putString(myOffset + 10962632 + 1 * 1024 * i, 1024, value); + buffer.putString(bufferOffset + myOffset + 10962632 + 1 * 1024 * i, 1024, value); } - int getShapeCount() { - int offset = myOffset + 31442632; + int getShapeCount() { + int offset = bufferOffset + myOffset + 31442632; return buffer.getInt(offset); } void setShapeCount(int value) { - buffer.putInt(myOffset + 31442632, value); + buffer.putInt(bufferOffset + myOffset + 31442632, value); } - Shape getShapes(int i) { - int offset = myOffset + 31442636 + 40 * 1 * i; + Shape getShapes(int i) { + int offset = bufferOffset + myOffset + 31442636 + 40 * 1 * i; return new Shape(offset); } - int getCommandCount() { - int offset = myOffset + 32242636; + int getCommandCount() { + int offset = bufferOffset + myOffset + 32242636; return buffer.getInt(offset); } void setCommandCount(int value) { - buffer.putInt(myOffset + 32242636, value); + buffer.putInt(bufferOffset + myOffset + 32242636, value); } - Command getCommands(int i) { - int offset = myOffset + 32242640 + 12 * 1 * i; + Command getCommands(int i) { + int offset = bufferOffset + myOffset + 32242640 + 12 * 1 * i; return new Command(offset); } - int getUnitCommandCount() { - int offset = myOffset + 32482640; + int getUnitCommandCount() { + int offset = bufferOffset + myOffset + 32482640; return buffer.getInt(offset); } void setUnitCommandCount(int value) { - buffer.putInt(myOffset + 32482640, value); + buffer.putInt(bufferOffset + myOffset + 32482640, value); } - UnitCommand getUnitCommands(int i) { - int offset = myOffset + 32482644 + 24 * 1 * i; + UnitCommand getUnitCommands(int i) { + int offset = bufferOffset + myOffset + 32482644 + 24 * 1 * i; return new UnitCommand(offset); } - int getUnitSearchSize() { - int offset = myOffset + 32962644; + int getUnitSearchSize() { + int offset = bufferOffset + myOffset + 32962644; return buffer.getInt(offset); } void setUnitSearchSize(int value) { - buffer.putInt(myOffset + 32962644, value); + buffer.putInt(bufferOffset + myOffset + 32962644, value); } - unitFinder getXUnitSearch(int i) { - int offset = myOffset + 32962648 + 8 * 1 * i; + unitFinder getXUnitSearch(int i) { + int offset = bufferOffset + myOffset + 32962648 + 8 * 1 * i; return new unitFinder(offset); } - unitFinder getYUnitSearch(int i) { - int offset = myOffset + 32989848 + 8 * 1 * i; + unitFinder getYUnitSearch(int i) { + int offset = bufferOffset + myOffset + 32989848 + 8 * 1 * i; return new unitFinder(offset); } } - class Shape { - static final int SIZE = 40; + class Shape { + static final int SIZE = 40; private int myOffset; - public Shape(int myOffset) { + public Shape(int myOffset) { this.myOffset = myOffset; } - ShapeType getType() { - int offset = myOffset + 0; + ShapeType getType() { + int offset = bufferOffset + myOffset + 0; return ShapeType.idToEnum[buffer.getInt(offset)]; } - void setType(ShapeType value) { - buffer.putInt(myOffset + 0, value.id); + void setType(ShapeType value) { + buffer.putInt(bufferOffset + myOffset + 0, value.id); } - CoordinateType getCtype() { - int offset = myOffset + 4; + CoordinateType getCtype() { + int offset = bufferOffset + myOffset + 4; return CoordinateType.idToEnum[buffer.getInt(offset)]; } - void setCtype(CoordinateType value) { - buffer.putInt(myOffset + 4, value.id); + void setCtype(CoordinateType value) { + buffer.putInt(bufferOffset + myOffset + 4, value.id); } - int getX1() { - int offset = myOffset + 8; + int getX1() { + int offset = bufferOffset + myOffset + 8; return buffer.getInt(offset); } void setX1(int value) { - buffer.putInt(myOffset + 8, value); + buffer.putInt(bufferOffset + myOffset + 8, value); } - int getY1() { - int offset = myOffset + 12; + int getY1() { + int offset = bufferOffset + myOffset + 12; return buffer.getInt(offset); } void setY1(int value) { - buffer.putInt(myOffset + 12, value); + buffer.putInt(bufferOffset + myOffset + 12, value); } - int getX2() { - int offset = myOffset + 16; + int getX2() { + int offset = bufferOffset + myOffset + 16; return buffer.getInt(offset); } void setX2(int value) { - buffer.putInt(myOffset + 16, value); + buffer.putInt(bufferOffset + myOffset + 16, value); } - int getY2() { - int offset = myOffset + 20; + int getY2() { + int offset = bufferOffset + myOffset + 20; return buffer.getInt(offset); } void setY2(int value) { - buffer.putInt(myOffset + 20, value); + buffer.putInt(bufferOffset + myOffset + 20, value); } - int getExtra1() { - int offset = myOffset + 24; + int getExtra1() { + int offset = bufferOffset + myOffset + 24; return buffer.getInt(offset); } void setExtra1(int value) { - buffer.putInt(myOffset + 24, value); + buffer.putInt(bufferOffset + myOffset + 24, value); } - int getExtra2() { - int offset = myOffset + 28; + int getExtra2() { + int offset = bufferOffset + myOffset + 28; return buffer.getInt(offset); } void setExtra2(int value) { - buffer.putInt(myOffset + 28, value); + buffer.putInt(bufferOffset + myOffset + 28, value); } - int getColor() { - int offset = myOffset + 32; + int getColor() { + int offset = bufferOffset + myOffset + 32; return buffer.getInt(offset); } void setColor(int value) { - buffer.putInt(myOffset + 32, value); + buffer.putInt(bufferOffset + myOffset + 32, value); } - boolean isSolid() { - int offset = myOffset + 36; + boolean isSolid() { + int offset = bufferOffset + myOffset + 36; return buffer.getByte(offset) != 0; } void setIsSolid(boolean value) { - buffer.putByte(myOffset + 36, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 36, (byte) (value ? 1 : 0)); } } - class Command { - static final int SIZE = 12; + class Command { + static final int SIZE = 12; private int myOffset; - public Command(int myOffset) { + public Command(int myOffset) { this.myOffset = myOffset; } - CommandType getType() { - int offset = myOffset + 0; + CommandType getType() { + int offset = bufferOffset + myOffset + 0; return CommandType.idToEnum[buffer.getInt(offset)]; } - void setType(CommandType value) { - buffer.putInt(myOffset + 0, value.id); + void setType(CommandType value) { + buffer.putInt(bufferOffset + myOffset + 0, value.id); } - int getValue1() { - int offset = myOffset + 4; + int getValue1() { + int offset = bufferOffset + myOffset + 4; return buffer.getInt(offset); } void setValue1(int value) { - buffer.putInt(myOffset + 4, value); + buffer.putInt(bufferOffset + myOffset + 4, value); } - int getValue2() { - int offset = myOffset + 8; + int getValue2() { + int offset = bufferOffset + myOffset + 8; return buffer.getInt(offset); } void setValue2(int value) { - buffer.putInt(myOffset + 8, value); + buffer.putInt(bufferOffset + myOffset + 8, value); } } - class Position { - static final int SIZE = 8; + class Position { + static final int SIZE = 8; private int myOffset; - public Position(int myOffset) { + public Position(int myOffset) { this.myOffset = myOffset; } - int getX() { - int offset = myOffset + 0; + int getX() { + int offset = bufferOffset + myOffset + 0; return buffer.getInt(offset); } void setX(int value) { - buffer.putInt(myOffset + 0, value); + buffer.putInt(bufferOffset + myOffset + 0, value); } - int getY() { - int offset = myOffset + 4; + int getY() { + int offset = bufferOffset + myOffset + 4; return buffer.getInt(offset); } void setY(int value) { - buffer.putInt(myOffset + 4, value); + buffer.putInt(bufferOffset + myOffset + 4, value); } } - class Event { - static final int SIZE = 12; + class Event { + static final int SIZE = 12; private int myOffset; - public Event(int myOffset) { + public Event(int myOffset) { this.myOffset = myOffset; } - EventType getType() { - int offset = myOffset + 0; + EventType getType() { + int offset = bufferOffset + myOffset + 0; return EventType.idToEnum[buffer.getInt(offset)]; } - void setType(EventType value) { - buffer.putInt(myOffset + 0, value.id); + void setType(EventType value) { + buffer.putInt(bufferOffset + myOffset + 0, value.id); } - int getV1() { - int offset = myOffset + 4; + int getV1() { + int offset = bufferOffset + myOffset + 4; return buffer.getInt(offset); } void setV1(int value) { - buffer.putInt(myOffset + 4, value); + buffer.putInt(bufferOffset + myOffset + 4, value); } - int getV2() { - int offset = myOffset + 8; + int getV2() { + int offset = bufferOffset + myOffset + 8; return buffer.getInt(offset); } void setV2(int value) { - buffer.putInt(myOffset + 8, value); + buffer.putInt(bufferOffset + myOffset + 8, value); } } - class RegionData { - static final int SIZE = 1068; + class RegionData { + static final int SIZE = 1068; private int myOffset; - public RegionData(int myOffset) { + public RegionData(int myOffset) { this.myOffset = myOffset; } - int getId() { - int offset = myOffset + 0; + int getId() { + int offset = bufferOffset + myOffset + 0; return buffer.getInt(offset); } void setId(int value) { - buffer.putInt(myOffset + 0, value); + buffer.putInt(bufferOffset + myOffset + 0, value); } - int islandID() { - int offset = myOffset + 4; + int islandID() { + int offset = bufferOffset + myOffset + 4; return buffer.getInt(offset); } void setIslandID(int value) { - buffer.putInt(myOffset + 4, value); + buffer.putInt(bufferOffset + myOffset + 4, value); } - int getCenter_x() { - int offset = myOffset + 8; + int getCenter_x() { + int offset = bufferOffset + myOffset + 8; return buffer.getInt(offset); } void setCenter_x(int value) { - buffer.putInt(myOffset + 8, value); + buffer.putInt(bufferOffset + myOffset + 8, value); } - int getCenter_y() { - int offset = myOffset + 12; + int getCenter_y() { + int offset = bufferOffset + myOffset + 12; return buffer.getInt(offset); } void setCenter_y(int value) { - buffer.putInt(myOffset + 12, value); + buffer.putInt(bufferOffset + myOffset + 12, value); } - int getPriority() { - int offset = myOffset + 16; + int getPriority() { + int offset = bufferOffset + myOffset + 16; return buffer.getInt(offset); } void setPriority(int value) { - buffer.putInt(myOffset + 16, value); + buffer.putInt(bufferOffset + myOffset + 16, value); } - int getLeftMost() { - int offset = myOffset + 20; + int getLeftMost() { + int offset = bufferOffset + myOffset + 20; return buffer.getInt(offset); } void setLeftMost(int value) { - buffer.putInt(myOffset + 20, value); + buffer.putInt(bufferOffset + myOffset + 20, value); } - int getRightMost() { - int offset = myOffset + 24; + int getRightMost() { + int offset = bufferOffset + myOffset + 24; return buffer.getInt(offset); } void setRightMost(int value) { - buffer.putInt(myOffset + 24, value); + buffer.putInt(bufferOffset + myOffset + 24, value); } - int getTopMost() { - int offset = myOffset + 28; + int getTopMost() { + int offset = bufferOffset + myOffset + 28; return buffer.getInt(offset); } void setTopMost(int value) { - buffer.putInt(myOffset + 28, value); + buffer.putInt(bufferOffset + myOffset + 28, value); } - int getBottomMost() { - int offset = myOffset + 32; + int getBottomMost() { + int offset = bufferOffset + myOffset + 32; return buffer.getInt(offset); } void setBottomMost(int value) { - buffer.putInt(myOffset + 32, value); + buffer.putInt(bufferOffset + myOffset + 32, value); } - int getNeighborCount() { - int offset = myOffset + 36; + int getNeighborCount() { + int offset = bufferOffset + myOffset + 36; return buffer.getInt(offset); } void setNeighborCount(int value) { - buffer.putInt(myOffset + 36, value); + buffer.putInt(bufferOffset + myOffset + 36, value); } - int getNeighbors(int i) { - int offset = myOffset + 40 + 4 * 1 * i; + int getNeighbors(int i) { + int offset = bufferOffset + myOffset + 40 + 4 * 1 * i; return buffer.getInt(offset); } void setNeighbors(int i, int value) { - buffer.putInt(myOffset + 40 + 4 * 1 * i, value); + buffer.putInt(bufferOffset + myOffset + 40 + 4 * 1 * i, value); } - boolean isAccessible() { - int offset = myOffset + 1064; + boolean isAccessible() { + int offset = bufferOffset + myOffset + 1064; return buffer.getByte(offset) != 0; } void setIsAccessible(boolean value) { - buffer.putByte(myOffset + 1064, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 1064, (byte) (value ? 1 : 0)); } - boolean isHigherGround() { - int offset = myOffset + 1065; + boolean isHigherGround() { + int offset = bufferOffset + myOffset + 1065; return buffer.getByte(offset) != 0; } void setIsHigherGround(boolean value) { - buffer.putByte(myOffset + 1065, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 1065, (byte) (value ? 1 : 0)); } } - class ForceData { - static final int SIZE = 32; + class ForceData { + static final int SIZE = 32; private int myOffset; - public ForceData(int myOffset) { + public ForceData(int myOffset) { this.myOffset = myOffset; } - String getName() { - int offset = myOffset + 0; + String getName() { + int offset = bufferOffset + myOffset + 0; return buffer.getString(offset, 32); } void setName(String value) { - buffer.putString(myOffset + 0, 32, value); + buffer.putString(bufferOffset + myOffset + 0, 32, value); } } - class PlayerData { - static final int SIZE = 5788; + class PlayerData { + static final int SIZE = 5788; private int myOffset; - public PlayerData(int myOffset) { + public PlayerData(int myOffset) { this.myOffset = myOffset; } - String getName() { - int offset = myOffset + 0; + String getName() { + int offset = bufferOffset + myOffset + 0; return buffer.getString(offset, 25); } void setName(String value) { - buffer.putString(myOffset + 0, 25, value); + buffer.putString(bufferOffset + myOffset + 0, 25, value); } - int getRace() { - int offset = myOffset + 28; + int getRace() { + int offset = bufferOffset + myOffset + 28; return buffer.getInt(offset); } void setRace(int value) { - buffer.putInt(myOffset + 28, value); + buffer.putInt(bufferOffset + myOffset + 28, value); } - int getType() { - int offset = myOffset + 32; + int getType() { + int offset = bufferOffset + myOffset + 32; return buffer.getInt(offset); } void setType(int value) { - buffer.putInt(myOffset + 32, value); + buffer.putInt(bufferOffset + myOffset + 32, value); } - int getForce() { - int offset = myOffset + 36; + int getForce() { + int offset = bufferOffset + myOffset + 36; return buffer.getInt(offset); } void setForce(int value) { - buffer.putInt(myOffset + 36, value); + buffer.putInt(bufferOffset + myOffset + 36, value); } - boolean isAlly(int i) { - int offset = myOffset + 40 + 1 * 1 * i; + boolean isAlly(int i) { + int offset = bufferOffset + myOffset + 40 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setIsAlly(int i, boolean value) { - buffer.putByte(myOffset + 40 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 40 + 1 * 1 * i, (byte) (value ? 1 : 0)); } - boolean isEnemy(int i) { - int offset = myOffset + 52 + 1 * 1 * i; + boolean isEnemy(int i) { + int offset = bufferOffset + myOffset + 52 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setIsEnemy(int i, boolean value) { - buffer.putByte(myOffset + 52 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 52 + 1 * 1 * i, (byte) (value ? 1 : 0)); } - boolean isNeutral() { - int offset = myOffset + 64; + boolean isNeutral() { + int offset = bufferOffset + myOffset + 64; return buffer.getByte(offset) != 0; } void setIsNeutral(boolean value) { - buffer.putByte(myOffset + 64, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 64, (byte) (value ? 1 : 0)); } - int getStartLocationX() { - int offset = myOffset + 68; + int getStartLocationX() { + int offset = bufferOffset + myOffset + 68; return buffer.getInt(offset); } void setStartLocationX(int value) { - buffer.putInt(myOffset + 68, value); + buffer.putInt(bufferOffset + myOffset + 68, value); } - int getStartLocationY() { - int offset = myOffset + 72; + int getStartLocationY() { + int offset = bufferOffset + myOffset + 72; return buffer.getInt(offset); } void setStartLocationY(int value) { - buffer.putInt(myOffset + 72, value); + buffer.putInt(bufferOffset + myOffset + 72, value); } - boolean isVictorious() { - int offset = myOffset + 76; + boolean isVictorious() { + int offset = bufferOffset + myOffset + 76; return buffer.getByte(offset) != 0; } void setIsVictorious(boolean value) { - buffer.putByte(myOffset + 76, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 76, (byte) (value ? 1 : 0)); } - boolean isDefeated() { - int offset = myOffset + 77; + boolean isDefeated() { + int offset = bufferOffset + myOffset + 77; return buffer.getByte(offset) != 0; } void setIsDefeated(boolean value) { - buffer.putByte(myOffset + 77, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 77, (byte) (value ? 1 : 0)); } - boolean getLeftGame() { - int offset = myOffset + 78; + boolean getLeftGame() { + int offset = bufferOffset + myOffset + 78; return buffer.getByte(offset) != 0; } void setLeftGame(boolean value) { - buffer.putByte(myOffset + 78, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 78, (byte) (value ? 1 : 0)); } - boolean isParticipating() { - int offset = myOffset + 79; + boolean isParticipating() { + int offset = bufferOffset + myOffset + 79; return buffer.getByte(offset) != 0; } void setIsParticipating(boolean value) { - buffer.putByte(myOffset + 79, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 79, (byte) (value ? 1 : 0)); } - int getMinerals() { - int offset = myOffset + 80; + int getMinerals() { + int offset = bufferOffset + myOffset + 80; return buffer.getInt(offset); } void setMinerals(int value) { - buffer.putInt(myOffset + 80, value); + buffer.putInt(bufferOffset + myOffset + 80, value); } - int getGas() { - int offset = myOffset + 84; + int getGas() { + int offset = bufferOffset + myOffset + 84; return buffer.getInt(offset); } void setGas(int value) { - buffer.putInt(myOffset + 84, value); + buffer.putInt(bufferOffset + myOffset + 84, value); } - int getGatheredMinerals() { - int offset = myOffset + 88; + int getGatheredMinerals() { + int offset = bufferOffset + myOffset + 88; return buffer.getInt(offset); } void setGatheredMinerals(int value) { - buffer.putInt(myOffset + 88, value); + buffer.putInt(bufferOffset + myOffset + 88, value); } - int getGatheredGas() { - int offset = myOffset + 92; + int getGatheredGas() { + int offset = bufferOffset + myOffset + 92; return buffer.getInt(offset); } void setGatheredGas(int value) { - buffer.putInt(myOffset + 92, value); + buffer.putInt(bufferOffset + myOffset + 92, value); } - int getRepairedMinerals() { - int offset = myOffset + 96; + int getRepairedMinerals() { + int offset = bufferOffset + myOffset + 96; return buffer.getInt(offset); } void setRepairedMinerals(int value) { - buffer.putInt(myOffset + 96, value); + buffer.putInt(bufferOffset + myOffset + 96, value); } - int getRepairedGas() { - int offset = myOffset + 100; + int getRepairedGas() { + int offset = bufferOffset + myOffset + 100; return buffer.getInt(offset); } void setRepairedGas(int value) { - buffer.putInt(myOffset + 100, value); + buffer.putInt(bufferOffset + myOffset + 100, value); } - int getRefundedMinerals() { - int offset = myOffset + 104; + int getRefundedMinerals() { + int offset = bufferOffset + myOffset + 104; return buffer.getInt(offset); } void setRefundedMinerals(int value) { - buffer.putInt(myOffset + 104, value); + buffer.putInt(bufferOffset + myOffset + 104, value); } - int getRefundedGas() { - int offset = myOffset + 108; + int getRefundedGas() { + int offset = bufferOffset + myOffset + 108; return buffer.getInt(offset); } void setRefundedGas(int value) { - buffer.putInt(myOffset + 108, value); + buffer.putInt(bufferOffset + myOffset + 108, value); } - int getSupplyTotal(int i) { - int offset = myOffset + 112 + 4 * 1 * i; + int getSupplyTotal(int i) { + int offset = bufferOffset + myOffset + 112 + 4 * 1 * i; return buffer.getInt(offset); } void setSupplyTotal(int i, int value) { - buffer.putInt(myOffset + 112 + 4 * 1 * i, value); + buffer.putInt(bufferOffset + myOffset + 112 + 4 * 1 * i, value); } - int getSupplyUsed(int i) { - int offset = myOffset + 124 + 4 * 1 * i; + int getSupplyUsed(int i) { + int offset = bufferOffset + myOffset + 124 + 4 * 1 * i; return buffer.getInt(offset); } void setSupplyUsed(int i, int value) { - buffer.putInt(myOffset + 124 + 4 * 1 * i, value); + buffer.putInt(bufferOffset + myOffset + 124 + 4 * 1 * i, value); } - int getAllUnitCount(int i) { - int offset = myOffset + 136 + 4 * 1 * i; + int getAllUnitCount(int i) { + int offset = bufferOffset + myOffset + 136 + 4 * 1 * i; return buffer.getInt(offset); } void setAllUnitCount(int i, int value) { - buffer.putInt(myOffset + 136 + 4 * 1 * i, value); + buffer.putInt(bufferOffset + myOffset + 136 + 4 * 1 * i, value); } - int getVisibleUnitCount(int i) { - int offset = myOffset + 1072 + 4 * 1 * i; + int getVisibleUnitCount(int i) { + int offset = bufferOffset + myOffset + 1072 + 4 * 1 * i; return buffer.getInt(offset); } void setVisibleUnitCount(int i, int value) { - buffer.putInt(myOffset + 1072 + 4 * 1 * i, value); + buffer.putInt(bufferOffset + myOffset + 1072 + 4 * 1 * i, value); } - int getCompletedUnitCount(int i) { - int offset = myOffset + 2008 + 4 * 1 * i; + int getCompletedUnitCount(int i) { + int offset = bufferOffset + myOffset + 2008 + 4 * 1 * i; return buffer.getInt(offset); } void setCompletedUnitCount(int i, int value) { - buffer.putInt(myOffset + 2008 + 4 * 1 * i, value); + buffer.putInt(bufferOffset + myOffset + 2008 + 4 * 1 * i, value); } - int getDeadUnitCount(int i) { - int offset = myOffset + 2944 + 4 * 1 * i; + int getDeadUnitCount(int i) { + int offset = bufferOffset + myOffset + 2944 + 4 * 1 * i; return buffer.getInt(offset); } void setDeadUnitCount(int i, int value) { - buffer.putInt(myOffset + 2944 + 4 * 1 * i, value); + buffer.putInt(bufferOffset + myOffset + 2944 + 4 * 1 * i, value); } - int getKilledUnitCount(int i) { - int offset = myOffset + 3880 + 4 * 1 * i; + int getKilledUnitCount(int i) { + int offset = bufferOffset + myOffset + 3880 + 4 * 1 * i; return buffer.getInt(offset); } void setKilledUnitCount(int i, int value) { - buffer.putInt(myOffset + 3880 + 4 * 1 * i, value); + buffer.putInt(bufferOffset + myOffset + 3880 + 4 * 1 * i, value); } - int getUpgradeLevel(int i) { - int offset = myOffset + 4816 + 4 * 1 * i; + int getUpgradeLevel(int i) { + int offset = bufferOffset + myOffset + 4816 + 4 * 1 * i; return buffer.getInt(offset); } void setUpgradeLevel(int i, int value) { - buffer.putInt(myOffset + 4816 + 4 * 1 * i, value); + buffer.putInt(bufferOffset + myOffset + 4816 + 4 * 1 * i, value); } - boolean getHasResearched(int i) { - int offset = myOffset + 5068 + 1 * 1 * i; + boolean getHasResearched(int i) { + int offset = bufferOffset + myOffset + 5068 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setHasResearched(int i, boolean value) { - buffer.putByte(myOffset + 5068 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 5068 + 1 * 1 * i, (byte) (value ? 1 : 0)); } - boolean isResearching(int i) { - int offset = myOffset + 5115 + 1 * 1 * i; + boolean isResearching(int i) { + int offset = bufferOffset + myOffset + 5115 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setIsResearching(int i, boolean value) { - buffer.putByte(myOffset + 5115 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 5115 + 1 * 1 * i, (byte) (value ? 1 : 0)); } - boolean isUpgrading(int i) { - int offset = myOffset + 5162 + 1 * 1 * i; + boolean isUpgrading(int i) { + int offset = bufferOffset + myOffset + 5162 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setIsUpgrading(int i, boolean value) { - buffer.putByte(myOffset + 5162 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 5162 + 1 * 1 * i, (byte) (value ? 1 : 0)); } - int getColor() { - int offset = myOffset + 5228; + int getColor() { + int offset = bufferOffset + myOffset + 5228; return buffer.getInt(offset); } void setColor(int value) { - buffer.putInt(myOffset + 5228, value); + buffer.putInt(bufferOffset + myOffset + 5228, value); } - int getTotalUnitScore() { - int offset = myOffset + 5232; + int getTotalUnitScore() { + int offset = bufferOffset + myOffset + 5232; return buffer.getInt(offset); } void setTotalUnitScore(int value) { - buffer.putInt(myOffset + 5232, value); + buffer.putInt(bufferOffset + myOffset + 5232, value); } - int getTotalKillScore() { - int offset = myOffset + 5236; + int getTotalKillScore() { + int offset = bufferOffset + myOffset + 5236; return buffer.getInt(offset); } void setTotalKillScore(int value) { - buffer.putInt(myOffset + 5236, value); + buffer.putInt(bufferOffset + myOffset + 5236, value); } - int getTotalBuildingScore() { - int offset = myOffset + 5240; + int getTotalBuildingScore() { + int offset = bufferOffset + myOffset + 5240; return buffer.getInt(offset); } void setTotalBuildingScore(int value) { - buffer.putInt(myOffset + 5240, value); + buffer.putInt(bufferOffset + myOffset + 5240, value); } - int getTotalRazingScore() { - int offset = myOffset + 5244; + int getTotalRazingScore() { + int offset = bufferOffset + myOffset + 5244; return buffer.getInt(offset); } void setTotalRazingScore(int value) { - buffer.putInt(myOffset + 5244, value); + buffer.putInt(bufferOffset + myOffset + 5244, value); } - int getCustomScore() { - int offset = myOffset + 5248; + int getCustomScore() { + int offset = bufferOffset + myOffset + 5248; return buffer.getInt(offset); } void setCustomScore(int value) { - buffer.putInt(myOffset + 5248, value); + buffer.putInt(bufferOffset + myOffset + 5248, value); } - int getMaxUpgradeLevel(int i) { - int offset = myOffset + 5252 + 4 * 1 * i; + int getMaxUpgradeLevel(int i) { + int offset = bufferOffset + myOffset + 5252 + 4 * 1 * i; return buffer.getInt(offset); } void setMaxUpgradeLevel(int i, int value) { - buffer.putInt(myOffset + 5252 + 4 * 1 * i, value); + buffer.putInt(bufferOffset + myOffset + 5252 + 4 * 1 * i, value); } - boolean isResearchAvailable(int i) { - int offset = myOffset + 5504 + 1 * 1 * i; + boolean isResearchAvailable(int i) { + int offset = bufferOffset + myOffset + 5504 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setIsResearchAvailable(int i, boolean value) { - buffer.putByte(myOffset + 5504 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 5504 + 1 * 1 * i, (byte) (value ? 1 : 0)); } - boolean isUnitAvailable(int i) { - int offset = myOffset + 5551 + 1 * 1 * i; + boolean isUnitAvailable(int i) { + int offset = bufferOffset + myOffset + 5551 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setIsUnitAvailable(int i, boolean value) { - buffer.putByte(myOffset + 5551 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 5551 + 1 * 1 * i, (byte) (value ? 1 : 0)); } } - class BulletData { - static final int SIZE = 80; + class BulletData { + static final int SIZE = 80; private int myOffset; - public BulletData(int myOffset) { + public BulletData(int myOffset) { this.myOffset = myOffset; } - int getId() { - int offset = myOffset + 0; + int getId() { + int offset = bufferOffset + myOffset + 0; return buffer.getInt(offset); } void setId(int value) { - buffer.putInt(myOffset + 0, value); + buffer.putInt(bufferOffset + myOffset + 0, value); } - int getPlayer() { - int offset = myOffset + 4; + int getPlayer() { + int offset = bufferOffset + myOffset + 4; return buffer.getInt(offset); } void setPlayer(int value) { - buffer.putInt(myOffset + 4, value); + buffer.putInt(bufferOffset + myOffset + 4, value); } - int getType() { - int offset = myOffset + 8; + int getType() { + int offset = bufferOffset + myOffset + 8; return buffer.getInt(offset); } void setType(int value) { - buffer.putInt(myOffset + 8, value); + buffer.putInt(bufferOffset + myOffset + 8, value); } - int getSource() { - int offset = myOffset + 12; + int getSource() { + int offset = bufferOffset + myOffset + 12; return buffer.getInt(offset); } void setSource(int value) { - buffer.putInt(myOffset + 12, value); + buffer.putInt(bufferOffset + myOffset + 12, value); } - int getPositionX() { - int offset = myOffset + 16; + int getPositionX() { + int offset = bufferOffset + myOffset + 16; return buffer.getInt(offset); } void setPositionX(int value) { - buffer.putInt(myOffset + 16, value); + buffer.putInt(bufferOffset + myOffset + 16, value); } - int getPositionY() { - int offset = myOffset + 20; + int getPositionY() { + int offset = bufferOffset + myOffset + 20; return buffer.getInt(offset); } void setPositionY(int value) { - buffer.putInt(myOffset + 20, value); + buffer.putInt(bufferOffset + myOffset + 20, value); } - double getAngle() { - int offset = myOffset + 24; + double getAngle() { + int offset = bufferOffset + myOffset + 24; return buffer.getDouble(offset); } void setAngle(double value) { - buffer.putDouble(myOffset + 24, value); + buffer.putDouble(bufferOffset + myOffset + 24, value); } - double getVelocityX() { - int offset = myOffset + 32; + double getVelocityX() { + int offset = bufferOffset + myOffset + 32; return buffer.getDouble(offset); } void setVelocityX(double value) { - buffer.putDouble(myOffset + 32, value); + buffer.putDouble(bufferOffset + myOffset + 32, value); } - double getVelocityY() { - int offset = myOffset + 40; + double getVelocityY() { + int offset = bufferOffset + myOffset + 40; return buffer.getDouble(offset); } void setVelocityY(double value) { - buffer.putDouble(myOffset + 40, value); + buffer.putDouble(bufferOffset + myOffset + 40, value); } - int getTarget() { - int offset = myOffset + 48; + int getTarget() { + int offset = bufferOffset + myOffset + 48; return buffer.getInt(offset); } void setTarget(int value) { - buffer.putInt(myOffset + 48, value); + buffer.putInt(bufferOffset + myOffset + 48, value); } - int getTargetPositionX() { - int offset = myOffset + 52; + int getTargetPositionX() { + int offset = bufferOffset + myOffset + 52; return buffer.getInt(offset); } void setTargetPositionX(int value) { - buffer.putInt(myOffset + 52, value); + buffer.putInt(bufferOffset + myOffset + 52, value); } - int getTargetPositionY() { - int offset = myOffset + 56; + int getTargetPositionY() { + int offset = bufferOffset + myOffset + 56; return buffer.getInt(offset); } void setTargetPositionY(int value) { - buffer.putInt(myOffset + 56, value); + buffer.putInt(bufferOffset + myOffset + 56, value); } - int getRemoveTimer() { - int offset = myOffset + 60; + int getRemoveTimer() { + int offset = bufferOffset + myOffset + 60; return buffer.getInt(offset); } void setRemoveTimer(int value) { - buffer.putInt(myOffset + 60, value); + buffer.putInt(bufferOffset + myOffset + 60, value); } - boolean getExists() { - int offset = myOffset + 64; + boolean getExists() { + int offset = bufferOffset + myOffset + 64; return buffer.getByte(offset) != 0; } void setExists(boolean value) { - buffer.putByte(myOffset + 64, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 64, (byte) (value ? 1 : 0)); } - boolean isVisible(int i) { - int offset = myOffset + 65 + 1 * 1 * i; + boolean isVisible(int i) { + int offset = bufferOffset + myOffset + 65 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setIsVisible(int i, boolean value) { - buffer.putByte(myOffset + 65 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 65 + 1 * 1 * i, (byte) (value ? 1 : 0)); } } - class unitFinder { - static final int SIZE = 8; + class unitFinder { + static final int SIZE = 8; private int myOffset; - public unitFinder(int myOffset) { + public unitFinder(int myOffset) { this.myOffset = myOffset; } - int getUnitIndex() { - int offset = myOffset + 0; + int getUnitIndex() { + int offset = bufferOffset + myOffset + 0; return buffer.getInt(offset); } void setUnitIndex(int value) { - buffer.putInt(myOffset + 0, value); + buffer.putInt(bufferOffset + myOffset + 0, value); } - int getSearchValue() { - int offset = myOffset + 4; + int getSearchValue() { + int offset = bufferOffset + myOffset + 4; return buffer.getInt(offset); } void setSearchValue(int value) { - buffer.putInt(myOffset + 4, value); + buffer.putInt(bufferOffset + myOffset + 4, value); } } - class UnitData { - static final int SIZE = 336; + class UnitData { + static final int SIZE = 336; private int myOffset; - public UnitData(int myOffset) { + public UnitData(int myOffset) { this.myOffset = myOffset; } - int getClearanceLevel() { - int offset = myOffset + 0; + int getClearanceLevel() { + int offset = bufferOffset + myOffset + 0; return buffer.getInt(offset); } void setClearanceLevel(int value) { - buffer.putInt(myOffset + 0, value); + buffer.putInt(bufferOffset + myOffset + 0, value); } - int getId() { - int offset = myOffset + 4; + int getId() { + int offset = bufferOffset + myOffset + 4; return buffer.getInt(offset); } void setId(int value) { - buffer.putInt(myOffset + 4, value); + buffer.putInt(bufferOffset + myOffset + 4, value); } - int getPlayer() { - int offset = myOffset + 8; + int getPlayer() { + int offset = bufferOffset + myOffset + 8; return buffer.getInt(offset); } void setPlayer(int value) { - buffer.putInt(myOffset + 8, value); + buffer.putInt(bufferOffset + myOffset + 8, value); } - int getType() { - int offset = myOffset + 12; + int getType() { + int offset = bufferOffset + myOffset + 12; return buffer.getInt(offset); } void setType(int value) { - buffer.putInt(myOffset + 12, value); + buffer.putInt(bufferOffset + myOffset + 12, value); } - int getPositionX() { - int offset = myOffset + 16; + int getPositionX() { + int offset = bufferOffset + myOffset + 16; return buffer.getInt(offset); } void setPositionX(int value) { - buffer.putInt(myOffset + 16, value); + buffer.putInt(bufferOffset + myOffset + 16, value); } - int getPositionY() { - int offset = myOffset + 20; + int getPositionY() { + int offset = bufferOffset + myOffset + 20; return buffer.getInt(offset); } void setPositionY(int value) { - buffer.putInt(myOffset + 20, value); + buffer.putInt(bufferOffset + myOffset + 20, value); } - double getAngle() { - int offset = myOffset + 24; + double getAngle() { + int offset = bufferOffset + myOffset + 24; return buffer.getDouble(offset); } void setAngle(double value) { - buffer.putDouble(myOffset + 24, value); + buffer.putDouble(bufferOffset + myOffset + 24, value); } - double getVelocityX() { - int offset = myOffset + 32; + double getVelocityX() { + int offset = bufferOffset + myOffset + 32; return buffer.getDouble(offset); } void setVelocityX(double value) { - buffer.putDouble(myOffset + 32, value); + buffer.putDouble(bufferOffset + myOffset + 32, value); } - double getVelocityY() { - int offset = myOffset + 40; + double getVelocityY() { + int offset = bufferOffset + myOffset + 40; return buffer.getDouble(offset); } void setVelocityY(double value) { - buffer.putDouble(myOffset + 40, value); + buffer.putDouble(bufferOffset + myOffset + 40, value); } - int getHitPoints() { - int offset = myOffset + 48; + int getHitPoints() { + int offset = bufferOffset + myOffset + 48; return buffer.getInt(offset); } void setHitPoints(int value) { - buffer.putInt(myOffset + 48, value); + buffer.putInt(bufferOffset + myOffset + 48, value); } - int getLastHitPoints() { - int offset = myOffset + 52; + int getLastHitPoints() { + int offset = bufferOffset + myOffset + 52; return buffer.getInt(offset); } void setLastHitPoints(int value) { - buffer.putInt(myOffset + 52, value); + buffer.putInt(bufferOffset + myOffset + 52, value); } - int getShields() { - int offset = myOffset + 56; + int getShields() { + int offset = bufferOffset + myOffset + 56; return buffer.getInt(offset); } void setShields(int value) { - buffer.putInt(myOffset + 56, value); + buffer.putInt(bufferOffset + myOffset + 56, value); } - int getEnergy() { - int offset = myOffset + 60; + int getEnergy() { + int offset = bufferOffset + myOffset + 60; return buffer.getInt(offset); } void setEnergy(int value) { - buffer.putInt(myOffset + 60, value); + buffer.putInt(bufferOffset + myOffset + 60, value); } - int getResources() { - int offset = myOffset + 64; + int getResources() { + int offset = bufferOffset + myOffset + 64; return buffer.getInt(offset); } void setResources(int value) { - buffer.putInt(myOffset + 64, value); + buffer.putInt(bufferOffset + myOffset + 64, value); } - int getResourceGroup() { - int offset = myOffset + 68; + int getResourceGroup() { + int offset = bufferOffset + myOffset + 68; return buffer.getInt(offset); } void setResourceGroup(int value) { - buffer.putInt(myOffset + 68, value); + buffer.putInt(bufferOffset + myOffset + 68, value); } - int getKillCount() { - int offset = myOffset + 72; + int getKillCount() { + int offset = bufferOffset + myOffset + 72; return buffer.getInt(offset); } void setKillCount(int value) { - buffer.putInt(myOffset + 72, value); + buffer.putInt(bufferOffset + myOffset + 72, value); } - int getAcidSporeCount() { - int offset = myOffset + 76; + int getAcidSporeCount() { + int offset = bufferOffset + myOffset + 76; return buffer.getInt(offset); } void setAcidSporeCount(int value) { - buffer.putInt(myOffset + 76, value); + buffer.putInt(bufferOffset + myOffset + 76, value); } - int getScarabCount() { - int offset = myOffset + 80; + int getScarabCount() { + int offset = bufferOffset + myOffset + 80; return buffer.getInt(offset); } void setScarabCount(int value) { - buffer.putInt(myOffset + 80, value); + buffer.putInt(bufferOffset + myOffset + 80, value); } - int getInterceptorCount() { - int offset = myOffset + 84; + int getInterceptorCount() { + int offset = bufferOffset + myOffset + 84; return buffer.getInt(offset); } void setInterceptorCount(int value) { - buffer.putInt(myOffset + 84, value); + buffer.putInt(bufferOffset + myOffset + 84, value); } - int getSpiderMineCount() { - int offset = myOffset + 88; + int getSpiderMineCount() { + int offset = bufferOffset + myOffset + 88; return buffer.getInt(offset); } void setSpiderMineCount(int value) { - buffer.putInt(myOffset + 88, value); + buffer.putInt(bufferOffset + myOffset + 88, value); } - int getGroundWeaponCooldown() { - int offset = myOffset + 92; + int getGroundWeaponCooldown() { + int offset = bufferOffset + myOffset + 92; return buffer.getInt(offset); } void setGroundWeaponCooldown(int value) { - buffer.putInt(myOffset + 92, value); + buffer.putInt(bufferOffset + myOffset + 92, value); } - int getAirWeaponCooldown() { - int offset = myOffset + 96; + int getAirWeaponCooldown() { + int offset = bufferOffset + myOffset + 96; return buffer.getInt(offset); } void setAirWeaponCooldown(int value) { - buffer.putInt(myOffset + 96, value); + buffer.putInt(bufferOffset + myOffset + 96, value); } - int getSpellCooldown() { - int offset = myOffset + 100; + int getSpellCooldown() { + int offset = bufferOffset + myOffset + 100; return buffer.getInt(offset); } void setSpellCooldown(int value) { - buffer.putInt(myOffset + 100, value); + buffer.putInt(bufferOffset + myOffset + 100, value); } - int getDefenseMatrixPoints() { - int offset = myOffset + 104; + int getDefenseMatrixPoints() { + int offset = bufferOffset + myOffset + 104; return buffer.getInt(offset); } void setDefenseMatrixPoints(int value) { - buffer.putInt(myOffset + 104, value); + buffer.putInt(bufferOffset + myOffset + 104, value); } - int getDefenseMatrixTimer() { - int offset = myOffset + 108; + int getDefenseMatrixTimer() { + int offset = bufferOffset + myOffset + 108; return buffer.getInt(offset); } void setDefenseMatrixTimer(int value) { - buffer.putInt(myOffset + 108, value); + buffer.putInt(bufferOffset + myOffset + 108, value); } - int getEnsnareTimer() { - int offset = myOffset + 112; + int getEnsnareTimer() { + int offset = bufferOffset + myOffset + 112; return buffer.getInt(offset); } void setEnsnareTimer(int value) { - buffer.putInt(myOffset + 112, value); + buffer.putInt(bufferOffset + myOffset + 112, value); } - int getIrradiateTimer() { - int offset = myOffset + 116; + int getIrradiateTimer() { + int offset = bufferOffset + myOffset + 116; return buffer.getInt(offset); } void setIrradiateTimer(int value) { - buffer.putInt(myOffset + 116, value); + buffer.putInt(bufferOffset + myOffset + 116, value); } - int getLockdownTimer() { - int offset = myOffset + 120; + int getLockdownTimer() { + int offset = bufferOffset + myOffset + 120; return buffer.getInt(offset); } void setLockdownTimer(int value) { - buffer.putInt(myOffset + 120, value); + buffer.putInt(bufferOffset + myOffset + 120, value); } - int getMaelstromTimer() { - int offset = myOffset + 124; + int getMaelstromTimer() { + int offset = bufferOffset + myOffset + 124; return buffer.getInt(offset); } void setMaelstromTimer(int value) { - buffer.putInt(myOffset + 124, value); + buffer.putInt(bufferOffset + myOffset + 124, value); } - int getOrderTimer() { - int offset = myOffset + 128; + int getOrderTimer() { + int offset = bufferOffset + myOffset + 128; return buffer.getInt(offset); } void setOrderTimer(int value) { - buffer.putInt(myOffset + 128, value); + buffer.putInt(bufferOffset + myOffset + 128, value); } - int getPlagueTimer() { - int offset = myOffset + 132; + int getPlagueTimer() { + int offset = bufferOffset + myOffset + 132; return buffer.getInt(offset); } void setPlagueTimer(int value) { - buffer.putInt(myOffset + 132, value); + buffer.putInt(bufferOffset + myOffset + 132, value); } - int getRemoveTimer() { - int offset = myOffset + 136; + int getRemoveTimer() { + int offset = bufferOffset + myOffset + 136; return buffer.getInt(offset); } void setRemoveTimer(int value) { - buffer.putInt(myOffset + 136, value); + buffer.putInt(bufferOffset + myOffset + 136, value); } - int getStasisTimer() { - int offset = myOffset + 140; + int getStasisTimer() { + int offset = bufferOffset + myOffset + 140; return buffer.getInt(offset); } void setStasisTimer(int value) { - buffer.putInt(myOffset + 140, value); + buffer.putInt(bufferOffset + myOffset + 140, value); } - int getStimTimer() { - int offset = myOffset + 144; + int getStimTimer() { + int offset = bufferOffset + myOffset + 144; return buffer.getInt(offset); } void setStimTimer(int value) { - buffer.putInt(myOffset + 144, value); + buffer.putInt(bufferOffset + myOffset + 144, value); } - int getBuildType() { - int offset = myOffset + 148; + int getBuildType() { + int offset = bufferOffset + myOffset + 148; return buffer.getInt(offset); } void setBuildType(int value) { - buffer.putInt(myOffset + 148, value); + buffer.putInt(bufferOffset + myOffset + 148, value); } - int getTrainingQueueCount() { - int offset = myOffset + 152; + int getTrainingQueueCount() { + int offset = bufferOffset + myOffset + 152; return buffer.getInt(offset); } void setTrainingQueueCount(int value) { - buffer.putInt(myOffset + 152, value); + buffer.putInt(bufferOffset + myOffset + 152, value); } - int getTrainingQueue(int i) { - int offset = myOffset + 156 + 4 * 1 * i; + int getTrainingQueue(int i) { + int offset = bufferOffset + myOffset + 156 + 4 * 1 * i; return buffer.getInt(offset); } void setTrainingQueue(int i, int value) { - buffer.putInt(myOffset + 156 + 4 * 1 * i, value); + buffer.putInt(bufferOffset + myOffset + 156 + 4 * 1 * i, value); } - int getTech() { - int offset = myOffset + 176; + int getTech() { + int offset = bufferOffset + myOffset + 176; return buffer.getInt(offset); } void setTech(int value) { - buffer.putInt(myOffset + 176, value); + buffer.putInt(bufferOffset + myOffset + 176, value); } - int getUpgrade() { - int offset = myOffset + 180; + int getUpgrade() { + int offset = bufferOffset + myOffset + 180; return buffer.getInt(offset); } void setUpgrade(int value) { - buffer.putInt(myOffset + 180, value); + buffer.putInt(bufferOffset + myOffset + 180, value); } - int getRemainingBuildTime() { - int offset = myOffset + 184; + int getRemainingBuildTime() { + int offset = bufferOffset + myOffset + 184; return buffer.getInt(offset); } void setRemainingBuildTime(int value) { - buffer.putInt(myOffset + 184, value); + buffer.putInt(bufferOffset + myOffset + 184, value); } - int getRemainingTrainTime() { - int offset = myOffset + 188; + int getRemainingTrainTime() { + int offset = bufferOffset + myOffset + 188; return buffer.getInt(offset); } void setRemainingTrainTime(int value) { - buffer.putInt(myOffset + 188, value); + buffer.putInt(bufferOffset + myOffset + 188, value); } - int getRemainingResearchTime() { - int offset = myOffset + 192; + int getRemainingResearchTime() { + int offset = bufferOffset + myOffset + 192; return buffer.getInt(offset); } void setRemainingResearchTime(int value) { - buffer.putInt(myOffset + 192, value); + buffer.putInt(bufferOffset + myOffset + 192, value); } - int getRemainingUpgradeTime() { - int offset = myOffset + 196; + int getRemainingUpgradeTime() { + int offset = bufferOffset + myOffset + 196; return buffer.getInt(offset); } void setRemainingUpgradeTime(int value) { - buffer.putInt(myOffset + 196, value); + buffer.putInt(bufferOffset + myOffset + 196, value); } - int getBuildUnit() { - int offset = myOffset + 200; + int getBuildUnit() { + int offset = bufferOffset + myOffset + 200; return buffer.getInt(offset); } void setBuildUnit(int value) { - buffer.putInt(myOffset + 200, value); + buffer.putInt(bufferOffset + myOffset + 200, value); } - int getTarget() { - int offset = myOffset + 204; + int getTarget() { + int offset = bufferOffset + myOffset + 204; return buffer.getInt(offset); } void setTarget(int value) { - buffer.putInt(myOffset + 204, value); + buffer.putInt(bufferOffset + myOffset + 204, value); } - int getTargetPositionX() { - int offset = myOffset + 208; + int getTargetPositionX() { + int offset = bufferOffset + myOffset + 208; return buffer.getInt(offset); } void setTargetPositionX(int value) { - buffer.putInt(myOffset + 208, value); + buffer.putInt(bufferOffset + myOffset + 208, value); } - int getTargetPositionY() { - int offset = myOffset + 212; + int getTargetPositionY() { + int offset = bufferOffset + myOffset + 212; return buffer.getInt(offset); } void setTargetPositionY(int value) { - buffer.putInt(myOffset + 212, value); + buffer.putInt(bufferOffset + myOffset + 212, value); } - int getOrder() { - int offset = myOffset + 216; + int getOrder() { + int offset = bufferOffset + myOffset + 216; return buffer.getInt(offset); } void setOrder(int value) { - buffer.putInt(myOffset + 216, value); + buffer.putInt(bufferOffset + myOffset + 216, value); } - int getOrderTarget() { - int offset = myOffset + 220; + int getOrderTarget() { + int offset = bufferOffset + myOffset + 220; return buffer.getInt(offset); } void setOrderTarget(int value) { - buffer.putInt(myOffset + 220, value); + buffer.putInt(bufferOffset + myOffset + 220, value); } - int getOrderTargetPositionX() { - int offset = myOffset + 224; + int getOrderTargetPositionX() { + int offset = bufferOffset + myOffset + 224; return buffer.getInt(offset); } void setOrderTargetPositionX(int value) { - buffer.putInt(myOffset + 224, value); + buffer.putInt(bufferOffset + myOffset + 224, value); } - int getOrderTargetPositionY() { - int offset = myOffset + 228; + int getOrderTargetPositionY() { + int offset = bufferOffset + myOffset + 228; return buffer.getInt(offset); } void setOrderTargetPositionY(int value) { - buffer.putInt(myOffset + 228, value); + buffer.putInt(bufferOffset + myOffset + 228, value); } - int getSecondaryOrder() { - int offset = myOffset + 232; + int getSecondaryOrder() { + int offset = bufferOffset + myOffset + 232; return buffer.getInt(offset); } void setSecondaryOrder(int value) { - buffer.putInt(myOffset + 232, value); + buffer.putInt(bufferOffset + myOffset + 232, value); } - int getRallyPositionX() { - int offset = myOffset + 236; + int getRallyPositionX() { + int offset = bufferOffset + myOffset + 236; return buffer.getInt(offset); } void setRallyPositionX(int value) { - buffer.putInt(myOffset + 236, value); + buffer.putInt(bufferOffset + myOffset + 236, value); } - int getRallyPositionY() { - int offset = myOffset + 240; + int getRallyPositionY() { + int offset = bufferOffset + myOffset + 240; return buffer.getInt(offset); } void setRallyPositionY(int value) { - buffer.putInt(myOffset + 240, value); + buffer.putInt(bufferOffset + myOffset + 240, value); } - int getRallyUnit() { - int offset = myOffset + 244; + int getRallyUnit() { + int offset = bufferOffset + myOffset + 244; return buffer.getInt(offset); } void setRallyUnit(int value) { - buffer.putInt(myOffset + 244, value); + buffer.putInt(bufferOffset + myOffset + 244, value); } - int getAddon() { - int offset = myOffset + 248; + int getAddon() { + int offset = bufferOffset + myOffset + 248; return buffer.getInt(offset); } void setAddon(int value) { - buffer.putInt(myOffset + 248, value); + buffer.putInt(bufferOffset + myOffset + 248, value); } - int getNydusExit() { - int offset = myOffset + 252; + int getNydusExit() { + int offset = bufferOffset + myOffset + 252; return buffer.getInt(offset); } void setNydusExit(int value) { - buffer.putInt(myOffset + 252, value); + buffer.putInt(bufferOffset + myOffset + 252, value); } - int getPowerUp() { - int offset = myOffset + 256; + int getPowerUp() { + int offset = bufferOffset + myOffset + 256; return buffer.getInt(offset); } void setPowerUp(int value) { - buffer.putInt(myOffset + 256, value); + buffer.putInt(bufferOffset + myOffset + 256, value); } - int getTransport() { - int offset = myOffset + 260; + int getTransport() { + int offset = bufferOffset + myOffset + 260; return buffer.getInt(offset); } void setTransport(int value) { - buffer.putInt(myOffset + 260, value); + buffer.putInt(bufferOffset + myOffset + 260, value); } - int getCarrier() { - int offset = myOffset + 264; + int getCarrier() { + int offset = bufferOffset + myOffset + 264; return buffer.getInt(offset); } void setCarrier(int value) { - buffer.putInt(myOffset + 264, value); + buffer.putInt(bufferOffset + myOffset + 264, value); } - int getHatchery() { - int offset = myOffset + 268; + int getHatchery() { + int offset = bufferOffset + myOffset + 268; return buffer.getInt(offset); } void setHatchery(int value) { - buffer.putInt(myOffset + 268, value); + buffer.putInt(bufferOffset + myOffset + 268, value); } - boolean getExists() { - int offset = myOffset + 272; + boolean getExists() { + int offset = bufferOffset + myOffset + 272; return buffer.getByte(offset) != 0; } void setExists(boolean value) { - buffer.putByte(myOffset + 272, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 272, (byte) (value ? 1 : 0)); } - boolean getHasNuke() { - int offset = myOffset + 273; + boolean getHasNuke() { + int offset = bufferOffset + myOffset + 273; return buffer.getByte(offset) != 0; } void setHasNuke(boolean value) { - buffer.putByte(myOffset + 273, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 273, (byte) (value ? 1 : 0)); } - boolean isAccelerating() { - int offset = myOffset + 274; + boolean isAccelerating() { + int offset = bufferOffset + myOffset + 274; return buffer.getByte(offset) != 0; } void setIsAccelerating(boolean value) { - buffer.putByte(myOffset + 274, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 274, (byte) (value ? 1 : 0)); } - boolean isAttacking() { - int offset = myOffset + 275; + boolean isAttacking() { + int offset = bufferOffset + myOffset + 275; return buffer.getByte(offset) != 0; } void setIsAttacking(boolean value) { - buffer.putByte(myOffset + 275, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 275, (byte) (value ? 1 : 0)); } - boolean isAttackFrame() { - int offset = myOffset + 276; + boolean isAttackFrame() { + int offset = bufferOffset + myOffset + 276; return buffer.getByte(offset) != 0; } void setIsAttackFrame(boolean value) { - buffer.putByte(myOffset + 276, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 276, (byte) (value ? 1 : 0)); } - boolean isBeingGathered() { - int offset = myOffset + 277; + boolean isBeingGathered() { + int offset = bufferOffset + myOffset + 277; return buffer.getByte(offset) != 0; } void setIsBeingGathered(boolean value) { - buffer.putByte(myOffset + 277, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 277, (byte) (value ? 1 : 0)); } - boolean isBlind() { - int offset = myOffset + 278; + boolean isBlind() { + int offset = bufferOffset + myOffset + 278; return buffer.getByte(offset) != 0; } void setIsBlind(boolean value) { - buffer.putByte(myOffset + 278, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 278, (byte) (value ? 1 : 0)); } - boolean isBraking() { - int offset = myOffset + 279; + boolean isBraking() { + int offset = bufferOffset + myOffset + 279; return buffer.getByte(offset) != 0; } void setIsBraking(boolean value) { - buffer.putByte(myOffset + 279, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 279, (byte) (value ? 1 : 0)); } - boolean isBurrowed() { - int offset = myOffset + 280; + boolean isBurrowed() { + int offset = bufferOffset + myOffset + 280; return buffer.getByte(offset) != 0; } void setIsBurrowed(boolean value) { - buffer.putByte(myOffset + 280, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 280, (byte) (value ? 1 : 0)); } - int getCarryResourceType() { - int offset = myOffset + 284; + int getCarryResourceType() { + int offset = bufferOffset + myOffset + 284; return buffer.getInt(offset); } void setCarryResourceType(int value) { - buffer.putInt(myOffset + 284, value); + buffer.putInt(bufferOffset + myOffset + 284, value); } - boolean isCloaked() { - int offset = myOffset + 288; + boolean isCloaked() { + int offset = bufferOffset + myOffset + 288; return buffer.getByte(offset) != 0; } void setIsCloaked(boolean value) { - buffer.putByte(myOffset + 288, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 288, (byte) (value ? 1 : 0)); } - boolean isCompleted() { - int offset = myOffset + 289; + boolean isCompleted() { + int offset = bufferOffset + myOffset + 289; return buffer.getByte(offset) != 0; } void setIsCompleted(boolean value) { - buffer.putByte(myOffset + 289, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 289, (byte) (value ? 1 : 0)); } - boolean isConstructing() { - int offset = myOffset + 290; + boolean isConstructing() { + int offset = bufferOffset + myOffset + 290; return buffer.getByte(offset) != 0; } void setIsConstructing(boolean value) { - buffer.putByte(myOffset + 290, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 290, (byte) (value ? 1 : 0)); } - boolean isDetected() { - int offset = myOffset + 291; + boolean isDetected() { + int offset = bufferOffset + myOffset + 291; return buffer.getByte(offset) != 0; } void setIsDetected(boolean value) { - buffer.putByte(myOffset + 291, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 291, (byte) (value ? 1 : 0)); } - boolean isGathering() { - int offset = myOffset + 292; + boolean isGathering() { + int offset = bufferOffset + myOffset + 292; return buffer.getByte(offset) != 0; } void setIsGathering(boolean value) { - buffer.putByte(myOffset + 292, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 292, (byte) (value ? 1 : 0)); } - boolean isHallucination() { - int offset = myOffset + 293; + boolean isHallucination() { + int offset = bufferOffset + myOffset + 293; return buffer.getByte(offset) != 0; } void setIsHallucination(boolean value) { - buffer.putByte(myOffset + 293, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 293, (byte) (value ? 1 : 0)); } - boolean isIdle() { - int offset = myOffset + 294; + boolean isIdle() { + int offset = bufferOffset + myOffset + 294; return buffer.getByte(offset) != 0; } void setIsIdle(boolean value) { - buffer.putByte(myOffset + 294, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 294, (byte) (value ? 1 : 0)); } - boolean isInterruptible() { - int offset = myOffset + 295; + boolean isInterruptible() { + int offset = bufferOffset + myOffset + 295; return buffer.getByte(offset) != 0; } void setIsInterruptible(boolean value) { - buffer.putByte(myOffset + 295, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 295, (byte) (value ? 1 : 0)); } - boolean isInvincible() { - int offset = myOffset + 296; + boolean isInvincible() { + int offset = bufferOffset + myOffset + 296; return buffer.getByte(offset) != 0; } void setIsInvincible(boolean value) { - buffer.putByte(myOffset + 296, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 296, (byte) (value ? 1 : 0)); } - boolean isLifted() { - int offset = myOffset + 297; + boolean isLifted() { + int offset = bufferOffset + myOffset + 297; return buffer.getByte(offset) != 0; } void setIsLifted(boolean value) { - buffer.putByte(myOffset + 297, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 297, (byte) (value ? 1 : 0)); } - boolean isMorphing() { - int offset = myOffset + 298; + boolean isMorphing() { + int offset = bufferOffset + myOffset + 298; return buffer.getByte(offset) != 0; } void setIsMorphing(boolean value) { - buffer.putByte(myOffset + 298, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 298, (byte) (value ? 1 : 0)); } - boolean isMoving() { - int offset = myOffset + 299; + boolean isMoving() { + int offset = bufferOffset + myOffset + 299; return buffer.getByte(offset) != 0; } void setIsMoving(boolean value) { - buffer.putByte(myOffset + 299, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 299, (byte) (value ? 1 : 0)); } - boolean isParasited() { - int offset = myOffset + 300; + boolean isParasited() { + int offset = bufferOffset + myOffset + 300; return buffer.getByte(offset) != 0; } void setIsParasited(boolean value) { - buffer.putByte(myOffset + 300, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 300, (byte) (value ? 1 : 0)); } - boolean isSelected() { - int offset = myOffset + 301; + boolean isSelected() { + int offset = bufferOffset + myOffset + 301; return buffer.getByte(offset) != 0; } void setIsSelected(boolean value) { - buffer.putByte(myOffset + 301, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 301, (byte) (value ? 1 : 0)); } - boolean isStartingAttack() { - int offset = myOffset + 302; + boolean isStartingAttack() { + int offset = bufferOffset + myOffset + 302; return buffer.getByte(offset) != 0; } void setIsStartingAttack(boolean value) { - buffer.putByte(myOffset + 302, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 302, (byte) (value ? 1 : 0)); } - boolean isStuck() { - int offset = myOffset + 303; + boolean isStuck() { + int offset = bufferOffset + myOffset + 303; return buffer.getByte(offset) != 0; } void setIsStuck(boolean value) { - buffer.putByte(myOffset + 303, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 303, (byte) (value ? 1 : 0)); } - boolean isTraining() { - int offset = myOffset + 304; + boolean isTraining() { + int offset = bufferOffset + myOffset + 304; return buffer.getByte(offset) != 0; } void setIsTraining(boolean value) { - buffer.putByte(myOffset + 304, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 304, (byte) (value ? 1 : 0)); } - boolean isUnderStorm() { - int offset = myOffset + 305; + boolean isUnderStorm() { + int offset = bufferOffset + myOffset + 305; return buffer.getByte(offset) != 0; } void setIsUnderStorm(boolean value) { - buffer.putByte(myOffset + 305, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 305, (byte) (value ? 1 : 0)); } - boolean isUnderDarkSwarm() { - int offset = myOffset + 306; + boolean isUnderDarkSwarm() { + int offset = bufferOffset + myOffset + 306; return buffer.getByte(offset) != 0; } void setIsUnderDarkSwarm(boolean value) { - buffer.putByte(myOffset + 306, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 306, (byte) (value ? 1 : 0)); } - boolean isUnderDWeb() { - int offset = myOffset + 307; + boolean isUnderDWeb() { + int offset = bufferOffset + myOffset + 307; return buffer.getByte(offset) != 0; } void setIsUnderDWeb(boolean value) { - buffer.putByte(myOffset + 307, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 307, (byte) (value ? 1 : 0)); } - boolean isPowered() { - int offset = myOffset + 308; + boolean isPowered() { + int offset = bufferOffset + myOffset + 308; return buffer.getByte(offset) != 0; } void setIsPowered(boolean value) { - buffer.putByte(myOffset + 308, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 308, (byte) (value ? 1 : 0)); } - boolean isVisible(int i) { - int offset = myOffset + 309 + 1 * 1 * i; + boolean isVisible(int i) { + int offset = bufferOffset + myOffset + 309 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setIsVisible(int i, boolean value) { - buffer.putByte(myOffset + 309 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 309 + 1 * 1 * i, (byte) (value ? 1 : 0)); } - int getButtonset() { - int offset = myOffset + 320; + int getButtonset() { + int offset = bufferOffset + myOffset + 320; return buffer.getInt(offset); } void setButtonset(int value) { - buffer.putInt(myOffset + 320, value); + buffer.putInt(bufferOffset + myOffset + 320, value); } - int getLastAttackerPlayer() { - int offset = myOffset + 324; + int getLastAttackerPlayer() { + int offset = bufferOffset + myOffset + 324; return buffer.getInt(offset); } void setLastAttackerPlayer(int value) { - buffer.putInt(myOffset + 324, value); + buffer.putInt(bufferOffset + myOffset + 324, value); } - boolean getRecentlyAttacked() { - int offset = myOffset + 328; + boolean getRecentlyAttacked() { + int offset = bufferOffset + myOffset + 328; return buffer.getByte(offset) != 0; } void setRecentlyAttacked(boolean value) { - buffer.putByte(myOffset + 328, (byte) (value ? 1 : 0)); + buffer.putByte(bufferOffset + myOffset + 328, (byte) (value ? 1 : 0)); } - int getReplayID() { - int offset = myOffset + 332; + int getReplayID() { + int offset = bufferOffset + myOffset + 332; return buffer.getInt(offset); } void setReplayID(int value) { - buffer.putInt(myOffset + 332, value); + buffer.putInt(bufferOffset + myOffset + 332, value); } } } diff --git a/src/main/java/bwapi/EventHandler.java b/src/main/java/bwapi/EventHandler.java index a0bf860b..495f445e 100644 --- a/src/main/java/bwapi/EventHandler.java +++ b/src/main/java/bwapi/EventHandler.java @@ -30,10 +30,10 @@ public void operation(final ClientData.Event event) { break; //case 3: //MenuFrame case SendText: - eventListener.onSendText(client.eventString(event.getV1())); + eventListener.onSendText(GameDataUtils.eventString(client.gameData(), event.getV1())); break; case ReceiveText: - eventListener.onReceiveText(game.getPlayer(event.getV1()), client.eventString(event.getV2())); + eventListener.onReceiveText(game.getPlayer(event.getV1()), GameDataUtils.eventString(client.gameData(), event.getV2())); break; case PlayerLeft: eventListener.onPlayerLeft(game.getPlayer(event.getV1())); @@ -42,7 +42,7 @@ public void operation(final ClientData.Event event) { eventListener.onNukeDetect(new Position(event.getV1(), event.getV2())); break; case SaveGame: - eventListener.onSaveGame(client.eventString(event.getV1())); + eventListener.onSaveGame(GameDataUtils.eventString(client.gameData(), event.getV1())); break; case UnitDiscover: game.unitCreate(event.getV1()); diff --git a/src/main/java/bwapi/FrameBuffer.java b/src/main/java/bwapi/FrameBuffer.java new file mode 100644 index 00000000..3a5112a4 --- /dev/null +++ b/src/main/java/bwapi/FrameBuffer.java @@ -0,0 +1,85 @@ +/* +MIT License + +Copyright (c) 2018 Hannes Bredberg +Modified work Copyright (c) 2018 Jasper + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package bwapi; + +import java.nio.ByteBuffer; + +/** + * Circular buffer of game states. + * Doesn't attempt to manage overruns or synchronization. + */ +public class FrameBuffer { + + private ByteBuffer data; + private int size; + private int stepGame = 0; + private int stepBot = 0; + + FrameBuffer(int size) { + this.size = size; + data = ByteBuffer.allocateDirect(size * ClientData.GameData.SIZE); + } + + /** + * @return The number of frames currently buffered ahead of the bot's current frame + */ + public int framesBuffered() { + return stepGame - stepBot; + } + + /** + * @return Whether the frame buffer is full and can not buffer any additional frames. + * When the frame buffer is full, JBWAPI must wait for the bot to complete a frame before returning control to StarCraft. + */ + public boolean full() { + return framesBuffered() >= size - 1; + } + + /** + * Copy data from shared memory into the head of the frame buffer. + * Does not attempt to check bounds or handle synchronization. + */ + public void enqueueFrame() { + // TODO: Enqueue + ++stepGame; + } + + /** + * Points the bot to the next frame in the buffer. + */ + public void dequeueFrame() { + // TODO: Dequeue + ++stepBot; + } + + private int indexGame() { + return stepGame % size; + } + + private int indexBot() { + return stepBot % size; + } +} diff --git a/src/main/java/bwapi/Game.java b/src/main/java/bwapi/Game.java index 62d06f05..29c5e96c 100644 --- a/src/main/java/bwapi/Game.java +++ b/src/main/java/bwapi/Game.java @@ -305,7 +305,7 @@ void onFrame(final int frame) { } void addUnitCommand(final int type, final int unit, final int target, final int x, final int y, final int extra) { - ClientData.UnitCommand unitCommand = client.addUnitCommand(); + ClientData.UnitCommand unitCommand = GameDataUtils.addUnitCommand(client.gameData()); unitCommand.setTid(type); unitCommand.setUnitIndex(unit); unitCommand.setTargetIndex(target); @@ -315,14 +315,14 @@ void addUnitCommand(final int type, final int unit, final int target, final int } void addCommand(final CommandType type, final int value1, final int value2) { - Command command = client.addCommand(); + Command command = GameDataUtils.addCommand(client.gameData()); command.setType(type); command.setValue1(value1); command.setValue2(value2); } void 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) { - Shape shape = client.addShape(); + Shape shape = GameDataUtils.addShape(client.gameData()); shape.setType(type); shape.setCtype(coordType); shape.setX1(x1); @@ -1513,7 +1513,7 @@ static String formatString(final String string, final Text... colors) { */ public void printf(final String string, final Text... colors) { final String formatted = formatString(string, colors); - addCommand(Printf, client.addString(formatted), 0); + addCommand(Printf, GameDataUtils.addString(client.gameData(), formatted), 0); } /** @@ -1538,7 +1538,7 @@ public void sendText(final String string, final Text... colors) { */ public void sendTextEx(final boolean toAllies, final String string, final Text... colors) { final String formatted = formatString(string, colors); - addCommand(SendText, client.addString(formatted), toAllies ? 1 : 0); + addCommand(SendText, GameDataUtils.addString(client.gameData(), formatted), toAllies ? 1 : 0); } /** @@ -1747,7 +1747,7 @@ public List observers() { public void drawText(final CoordinateType ctype, final int x, final int y, final String string, final Text... colors) { final String formatted = formatString(string, colors); - final int stringId = client.addString(formatted); + final int stringId = GameDataUtils.addString(client.gameData(), formatted); addShape(ShapeType.Text, ctype, x, y, 0, 0, stringId, textSize.id, 0, false); } @@ -2363,7 +2363,7 @@ public boolean setMap(final String mapFileName) { return false; } - addCommand(CommandType.SetMap, client.addString(mapFileName), 0); + addCommand(CommandType.SetMap, GameDataUtils.addString(client.gameData(), mapFileName), 0); return true; } diff --git a/src/main/java/bwapi/GameDataUtils.java b/src/main/java/bwapi/GameDataUtils.java new file mode 100644 index 00000000..b6215bc1 --- /dev/null +++ b/src/main/java/bwapi/GameDataUtils.java @@ -0,0 +1,83 @@ +/* +MIT License + +Copyright (c) 2018 Hannes Bredberg +Modified work Copyright (c) 2018 Jasper + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package bwapi; + +/** + * Static functions for modifying GameData. + * These functions live outside GameData because GameData is auto-generated. + */ +public class GameDataUtils { + + static final int MAX_COUNT = 19999; + static final int MAX_STRING_SIZE = 1024; + + static String eventString(ClientData.GameData gameData, final int s) { + return gameData.getEventStrings(s); + } + + static int addString(ClientData.GameData gameData, final String string) { + int stringCount = gameData.getStringCount(); + if (stringCount >= MAX_COUNT) { + throw new IllegalStateException("Too many strings!"); + } + + // Truncate string if its size equals or exceeds 1024 + final String stringTruncated = string.length() >= MAX_STRING_SIZE + ? string.substring(0, MAX_STRING_SIZE - 1) + : string; + + gameData.setStringCount(stringCount + 1); + gameData.setStrings(stringCount, stringTruncated); + return stringCount; + } + + static ClientData.Shape addShape(ClientData.GameData gameData) { + int shapeCount = gameData.getShapeCount(); + if (shapeCount >= MAX_COUNT) { + throw new IllegalStateException("Too many shapes!"); + } + gameData.setShapeCount(shapeCount + 1); + return gameData.getShapes(shapeCount); + } + + static ClientData.Command addCommand(ClientData.GameData gameData) { + final int commandCount = gameData.getCommandCount(); + if (commandCount >= MAX_COUNT) { + throw new IllegalStateException("Too many commands!"); + } + gameData.setCommandCount(commandCount + 1); + return gameData.getCommands(commandCount); + } + + static ClientData.UnitCommand addUnitCommand(ClientData.GameData gameData) { + int unitCommandCount = gameData.getUnitCommandCount(); + if (unitCommandCount >= MAX_COUNT) { + throw new IllegalStateException("Too many unit commands!"); + } + gameData.setUnitCommandCount(unitCommandCount + 1); + return gameData.getUnitCommands(unitCommandCount); + } +} diff --git a/src/test/java/DumpToClient.java b/src/test/java/DumpToClient.java index 9e049500..5577c706 100644 --- a/src/test/java/DumpToClient.java +++ b/src/test/java/DumpToClient.java @@ -92,10 +92,14 @@ public static void main(String[] args) throws IOException { out.println("package bwapi;"); out.println("import java.nio.ByteBuffer;"); out.println("final class ClientData {"); - out.println(" final WrappedBuffer buffer;"); - out.println(" ClientData(final ByteBuffer buffer) {"); + out.println(" WrappedBuffer buffer;"); + out.println(" private int bufferOffset = 0;"); + out.println(" public void setBuffer(ByteBuffer buffer) {"); out.println(" this.buffer = new WrappedBuffer(buffer);"); out.println(" }"); + out.println(" public void setBufferOffset(int offset) {"); + out.println(" this.bufferOffset = offset;"); + out.println(" }"); structs.values().forEach(s -> { out.printf(" class %s {\n", s.name); out.printf(" static final int SIZE = %d;\n", s.size); @@ -173,9 +177,9 @@ public static void main(String[] args) throws IOException { } offset *= v.arraySizes.get(i); } - offsetString = "myOffset + " + v.offset + " + " + String.join(" + ", index); + offsetString = "bufferOffset + myOffset + " + v.offset + " + " + String.join(" + ", index); } else { - offsetString = "myOffset + " + v.offset; + offsetString = "bufferOffset + myOffset + " + v.offset; } String paramString = String.join(", ", params); out.printf("%s) {\n", paramString); diff --git a/src/test/java/bwapi/ClientDataBenchmark.java b/src/test/java/bwapi/ClientDataBenchmark.java index b0377beb..7227619a 100644 --- a/src/test/java/bwapi/ClientDataBenchmark.java +++ b/src/test/java/bwapi/ClientDataBenchmark.java @@ -39,14 +39,14 @@ public void setup() { game = new Game(client); String[] strings = buildStrings(); for (String s : strings) { - client.addString(s); + GameDataUtils.addString(client.gameData(), s); } } } private static String[] buildStrings() { SplittableRandom rnd = new SplittableRandom(987654321L); - String[] strings = new String[Client.MAX_COUNT]; + String[] strings = new String[GameDataUtils.MAX_COUNT]; for (int i = 0; i < strings.length; i++) { strings[i] = rnd.ints(1022, 0, 9) .mapToObj(Integer::toString) @@ -64,27 +64,27 @@ public void reference(Blackhole blackhole) { } @Benchmark - @OperationsPerInvocation(Client.MAX_COUNT) + @OperationsPerInvocation(GameDataUtils.MAX_COUNT) public int addUnitCommand(EmptyState s) { - for (int i = 0; i < Client.MAX_COUNT; i++) { + for (int i = 0; i < GameDataUtils.MAX_COUNT; i++) { s.game.addUnitCommand(0, 1, 2, 3, 4, 5); } return s.client.gameData().getCommandCount(); } @Benchmark - @OperationsPerInvocation(Client.MAX_COUNT) + @OperationsPerInvocation(GameDataUtils.MAX_COUNT) public int addString(EmptyState s) { - for (int i = 0; i < Client.MAX_COUNT; i++) { - s.client.addString(s.strings[i]); + for (int i = 0; i < GameDataUtils.MAX_COUNT; i++) { + GameDataUtils.addString(s.client.gameData(), s.strings[i]); } return s.client.gameData().getStringCount(); } @Benchmark - @OperationsPerInvocation(Client.MAX_COUNT) + @OperationsPerInvocation(GameDataUtils.MAX_COUNT) public void getString(FilledWithStrings s, Blackhole blackhole) { - for (int i = 0; i < Client.MAX_COUNT; i++) { + for (int i = 0; i < GameDataUtils.MAX_COUNT; i++) { blackhole.consume(s.data.getStrings(i)); } } From 486bf4f87ca7b2db914708372675c7f6c6639396 Mon Sep 17 00:00:00 2001 From: dgant Date: Sun, 19 Jul 2020 20:24:29 -0400 Subject: [PATCH 03/56] Restored existing (sync) behavior. --- src/main/java/bwapi/BWClient.java | 9 +++++++++ .../java/bwapi/BWClientConfiguration.java | 2 +- src/main/java/bwapi/BotWrapper.java | 19 +++++++++++-------- src/main/java/bwapi/Client.java | 4 ++++ src/main/java/bwapi/Game.java | 2 +- src/main/java/bwapi/Unit.java | 2 +- 6 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index 540db4b6..d3cec599 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -50,19 +50,28 @@ public void startGame(BWClientConfiguration configuration) { handler = new EventHandler(eventListener, client); do { + BotWrapper botWrapper; while (!getGame().isInGame()) { if (!client.isConnected()) { return; } client.update(); } + botWrapper = new BotWrapper(configuration, client.mapFile(), client.gameData(), handler); + botWrapper.step(); while (getGame().isInGame()) { client.update(); + botWrapper.step(); if (!client.isConnected()) { System.out.println("Reconnecting..."); client.reconnect(); } } + + // TODO: Before exiting give async bot time to complete onEnd(). + } while (configuration.autoContinue); // lgtm [java/constant-loop-condition] + + } } \ No newline at end of file diff --git a/src/main/java/bwapi/BWClientConfiguration.java b/src/main/java/bwapi/BWClientConfiguration.java index 5318cfea..13131eb0 100644 --- a/src/main/java/bwapi/BWClientConfiguration.java +++ b/src/main/java/bwapi/BWClientConfiguration.java @@ -22,7 +22,7 @@ public class BWClientConfiguration { * and some tournaments enforce frame-wise time limits (at time of writing, 55ms for COG and AIIDE; 85ms for SSCAIT). * * 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 - * returns control to StarCraft, allowing the game to proceed while the bot continues to run in the background. This increases the likelihood of meeting + * returns control to StarCraft, allowing the game to proceed while the bot continues to step in the background. This increases the likelihood of meeting * 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 * issuing commands later than intended, and a marginally larger memory footprint. */ diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java index b206826e..ee9ce179 100644 --- a/src/main/java/bwapi/BotWrapper.java +++ b/src/main/java/bwapi/BotWrapper.java @@ -31,32 +31,35 @@ of this software and associated documentation files (the "Software"), to deal * Manages invocation of bot event handlers */ public class BotWrapper { - private EventHandler eventHandler; - private ByteBuffer sharedMemory; private BWClientConfiguration configuration; - + private ByteBuffer sharedMemory; + private ClientData.GameData gameData; + private EventHandler eventHandler; private FrameBuffer frameBuffer; - public BotWrapper(ByteBuffer sharedMemory, EventHandler eventHandler, BWClientConfiguration configuration) { + public BotWrapper( + BWClientConfiguration configuration, + ByteBuffer sharedMemory, + ClientData.GameData gameData, + EventHandler eventHandler) { + this.configuration = configuration; this.sharedMemory = sharedMemory; + this.gameData = gameData; this.eventHandler = eventHandler; - this.configuration = configuration; if (configuration.async) { frameBuffer = new FrameBuffer(configuration.asyncFrameBufferSize); } } - public void run() { + public void step() { if (configuration.async) { // TODO: Synchronize frameBuffer.enqueueFrame(); } else { - /* for (int i = 0; i < gameData.getEventCount(); i++) { eventHandler.operation(gameData.getEvents(i)); } - */ } } } diff --git a/src/main/java/bwapi/Client.java b/src/main/java/bwapi/Client.java index fc1a5283..cc41b351 100644 --- a/src/main/java/bwapi/Client.java +++ b/src/main/java/bwapi/Client.java @@ -78,6 +78,10 @@ GameData gameData() { return gameData; } + ByteBuffer mapFile() { + return mapFileHandle; + } + boolean isConnected() { return connected; } diff --git a/src/main/java/bwapi/Game.java b/src/main/java/bwapi/Game.java index 29c5e96c..1983af3e 100644 --- a/src/main/java/bwapi/Game.java +++ b/src/main/java/bwapi/Game.java @@ -2244,7 +2244,7 @@ public int getAPM(final boolean includeSelects) { /** * Sets the number of graphical frames for every logical frame. This - * allows the game to run more logical frames per graphical frame, increasing the speed at + * allows the game to step more logical frames per graphical frame, increasing the speed at * which the game runs. * * @param frameSkip Number of graphical frames per logical frame. If this value is 0 or less, then it will default to 1. diff --git a/src/main/java/bwapi/Unit.java b/src/main/java/bwapi/Unit.java index 0ac4cb1b..6d491ae9 100644 --- a/src/main/java/bwapi/Unit.java +++ b/src/main/java/bwapi/Unit.java @@ -1019,7 +1019,7 @@ public Order getOrder() { /** * Retrieves the secondary Order that the unit is assigned. Secondary - * orders are run in the background as a sub-order. An example would be {@link Order#TrainFighter}, + * orders are step in the background as a sub-order. An example would be {@link Order#TrainFighter}, * because a @Carrier can move and train fighters at the same time. * * @return The secondary {@link Order} that the unit is executing. From 43c9ac3e8826692ad5132fed6b3135bf9b30a65d Mon Sep 17 00:00:00 2001 From: dgant Date: Tue, 21 Jul 2020 19:01:12 -0400 Subject: [PATCH 04/56] Finished first pass async implementation, untested --- src/main/java/bwapi/BWClient.java | 9 +-- .../java/bwapi/BWClientConfiguration.java | 14 +---- src/main/java/bwapi/BotWrapper.java | 53 ++++++++++++----- src/main/java/bwapi/FrameBuffer.java | 58 +++++++++++++++---- 4 files changed, 92 insertions(+), 42 deletions(-) diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index d3cec599..81fb488a 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -57,18 +57,19 @@ public void startGame(BWClientConfiguration configuration) { } client.update(); } - botWrapper = new BotWrapper(configuration, client.mapFile(), client.gameData(), handler); - botWrapper.step(); + + botWrapper = new BotWrapper(configuration, client.mapFile(), client.clientData(), handler); + while (getGame().isInGame()) { - client.update(); botWrapper.step(); + client.update(); if (!client.isConnected()) { System.out.println("Reconnecting..."); client.reconnect(); } } - // TODO: Before exiting give async bot time to complete onEnd(). + // TODO: Before exiting give async bot time to complete onEnd(), maybe via thread.join(). } while (configuration.autoContinue); // lgtm [java/constant-loop-condition] diff --git a/src/main/java/bwapi/BWClientConfiguration.java b/src/main/java/bwapi/BWClientConfiguration.java index 13131eb0..c9c97063 100644 --- a/src/main/java/bwapi/BWClientConfiguration.java +++ b/src/main/java/bwapi/BWClientConfiguration.java @@ -28,16 +28,11 @@ public class BWClientConfiguration { */ public boolean async = false; - /** - * How frequently (in nanoseconds) to poll for the bot's event handlers completing. Acts as a floor on the bot's frame duration. - */ - public int asyncFrameDurationNanosMin = 500; - /** * If JBWAPI detects that this much time (in nanoseconds) has passed since a bot's event handlers began, returns control back to BWAPI. * Real-time human play typically uses the "fastest" game speed, which has 42.86ms (42,860ns) between frames. */ - public int asyncFrameDurationNanosMax = 40000; + public int asyncFrameDurationNanos = 40000; /** * The maximum number of frames to buffer while waiting on a bot. @@ -58,11 +53,8 @@ public class BWClientConfiguration { * Checks that the configuration is in a valid state. Throws an IllegalArgumentException if it isn't. */ public void validate() { - if (async && asyncFrameDurationNanosMin <= 0) { - throw new IllegalArgumentException("asyncFrameDurationNanosMin needs to be a positive number (it's how long JBWAPI waits to poll for a completed frame."); - } - if (async && asyncFrameDurationNanosMax < 0) { - throw new IllegalArgumentException("asyncFrameDurationNanosMax needs to be a non-negative number (it's how long JBWAPI waits for a bot response before returning control to BWAPI)."); + if (async && asyncFrameDurationNanos < 0) { + throw new IllegalArgumentException("asyncFrameDurationNanos needs to be a non-negative number (it's how long JBWAPI waits for a bot response before returning control to BWAPI)."); } if (async && asyncFrameBufferSize < 1) { throw new IllegalArgumentException("asyncFrameBufferSize needs to be a positive number (There needs to be at least one frame buffer)."); diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java index ee9ce179..34365cb9 100644 --- a/src/main/java/bwapi/BotWrapper.java +++ b/src/main/java/bwapi/BotWrapper.java @@ -30,36 +30,59 @@ of this software and associated documentation files (the "Software"), to deal /** * Manages invocation of bot event handlers */ -public class BotWrapper { - private BWClientConfiguration configuration; - private ByteBuffer sharedMemory; - private ClientData.GameData gameData; - private EventHandler eventHandler; - private FrameBuffer frameBuffer; +class BotWrapper { + private final BWClientConfiguration configuration; + private final ByteBuffer sharedMemory; + private final ClientData clientData; + private final EventHandler eventHandler; + private final FrameBuffer frameBuffer; + private final Thread botThread; - public BotWrapper( + BotWrapper( BWClientConfiguration configuration, ByteBuffer sharedMemory, - ClientData.GameData gameData, + ClientData clientData, EventHandler eventHandler) { this.configuration = configuration; this.sharedMemory = sharedMemory; - this.gameData = gameData; + this.clientData = clientData; this.eventHandler = eventHandler; if (configuration.async) { frameBuffer = new FrameBuffer(configuration.asyncFrameBufferSize); + botThread = new Thread(() -> { + while(true) { + while(frameBuffer.empty()) try { + frameBuffer.wait(); + } catch (InterruptedException ignored) {} + + frameBuffer.dequeueFrame(clientData); + handleEvents(); + if (!clientData.new GameData(0).isInGame()) { + return; + } + } + }); + } else { + frameBuffer = null; + botThread = null; } } - public void step() { + void step() { if (configuration.async) { - // TODO: Synchronize - frameBuffer.enqueueFrame(); + frameBuffer.enqueueFrame(sharedMemory); + frameBuffer.notifyAll(); } else { - for (int i = 0; i < gameData.getEventCount(); i++) { - eventHandler.operation(gameData.getEvents(i)); - } + handleEvents(); } } + + private void handleEvents() { + for (int i = 0; i < clientData.new GameData(0).getEventCount(); i++) { + eventHandler.operation(clientData.new GameData(0).getEvents(i)); + } + } + + } diff --git a/src/main/java/bwapi/FrameBuffer.java b/src/main/java/bwapi/FrameBuffer.java index 3a5112a4..295749d9 100644 --- a/src/main/java/bwapi/FrameBuffer.java +++ b/src/main/java/bwapi/FrameBuffer.java @@ -26,10 +26,12 @@ of this software and associated documentation files (the "Software"), to deal package bwapi; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; /** * Circular buffer of game states. - * Doesn't attempt to manage overruns or synchronization. */ public class FrameBuffer { @@ -38,41 +40,73 @@ public class FrameBuffer { private int stepGame = 0; private int stepBot = 0; + // Synchronization locks + private Object stepCount; + private ArrayList frameLocks; + FrameBuffer(int size) { this.size = size; data = ByteBuffer.allocateDirect(size * ClientData.GameData.SIZE); + while (frameLocks.size() < size) { + frameLocks.add(new ReentrantLock()); + } } /** * @return The number of frames currently buffered ahead of the bot's current frame */ - public int framesBuffered() { - return stepGame - stepBot; + int framesBuffered() { + synchronized (stepCount) { + return stepGame - stepBot; + } + } + + /** + * @return Whether the frame buffer is empty and has no frames available for the bot to consume. + */ + boolean empty() { + synchronized (stepCount) { + return framesBuffered() <= 0; + } } /** * @return Whether the frame buffer is full and can not buffer any additional frames. * When the frame buffer is full, JBWAPI must wait for the bot to complete a frame before returning control to StarCraft. */ - public boolean full() { - return framesBuffered() >= size - 1; + boolean full() { + synchronized (stepCount) { + return framesBuffered() >= size - 1; + } } /** * Copy data from shared memory into the head of the frame buffer. - * Does not attempt to check bounds or handle synchronization. */ - public void enqueueFrame() { - // TODO: Enqueue - ++stepGame; + void enqueueFrame(ByteBuffer source) { + while(full()) try { + wait(); + } catch (InterruptedException ignored) {} + synchronized (stepCount) { + data.put(source.array(), indexGame() * ClientData.GameData.SIZE, ClientData.GameData.SIZE); + ++stepGame; + } + notifyAll(); } /** * Points the bot to the next frame in the buffer. */ - public void dequeueFrame() { - // TODO: Dequeue - ++stepBot; + void dequeueFrame(ClientData clientData) { + while(empty()) try { + wait(); + } catch (InterruptedException ignored) {} + synchronized (stepCount) { + clientData.setBuffer(data); + clientData.setBufferOffset(indexBot() * ClientData.GameData.SIZE); + ++stepBot; + } + notifyAll(); } private int indexGame() { From 165db79b3e8f84624c6734ede855963e879c04bf Mon Sep 17 00:00:00 2001 From: dgant Date: Sun, 26 Jul 2020 13:05:25 -0400 Subject: [PATCH 05/56] Assigning the asynchronous bot perspective to Game --- src/main/java/bwapi/BWClient.java | 34 +- .../java/bwapi/BWClientConfiguration.java | 8 +- src/main/java/bwapi/BotWrapper.java | 57 +- src/main/java/bwapi/Client.java | 19 +- src/main/java/bwapi/ClientData.java | 1099 +++++++++-------- src/main/java/bwapi/EventHandler.java | 26 +- src/main/java/bwapi/FrameBuffer.java | 86 +- src/main/java/bwapi/Game.java | 163 +-- src/main/java/bwapi/GameDataUtils.java | 4 - src/test/java/DumpToClient.java | 17 +- src/test/java/bwapi/ClientDataBenchmark.java | 14 +- src/test/java/bwapi/GameBuilder.java | 2 +- src/test/java/bwapi/GameStateDumper.java | 4 +- src/test/java/bwapi/GameTest.java | 4 +- 14 files changed, 768 insertions(+), 769 deletions(-) diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index 81fb488a..58d75331 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -7,7 +7,7 @@ */ public class BWClient { private final BWEventListener eventListener; - private EventHandler handler; + private BotWrapper botWrapper; public BWClient(final BWEventListener eventListener) { Objects.requireNonNull(eventListener); @@ -16,9 +16,10 @@ public BWClient(final BWEventListener eventListener) { /** * Get the {@link Game} instance of the currently running game. + * When running in asynchronous mode, this is the game from the bot's perspective, eg. potentially a previous frame. */ public Game getGame() { - return handler == null ? null : handler.getGame(); + return botWrapper == null ? null : botWrapper.getGame(); } public void startGame() { @@ -47,21 +48,44 @@ public void startGame(BWClientConfiguration configuration) { configuration.validate(); Client client = new Client(configuration); client.reconnect(); - handler = new EventHandler(eventListener, client); do { - BotWrapper botWrapper; + long lastUpdateTimestampMillis = 0; + System.out.println("Client: Starting game"); while (!getGame().isInGame()) { if (!client.isConnected()) { return; } + System.out.println("Client: connected."); + lastUpdateTimestampMillis = System.currentTimeMillis(); client.update(); } - botWrapper = new BotWrapper(configuration, client.mapFile(), client.clientData(), handler); + System.out.println("Client: Creating bot wrapper"); + BotWrapper botWrapper = new BotWrapper(configuration, eventListener, client.mapFile()); while (getGame().isInGame()) { + System.out.println("Client: Stepping bot wrapper"); botWrapper.step(); + System.out.println("Client: Waiting for idle bot or frame duration"); + + // Proceed immediately once framebuffer is empty + // Otherwise, wait for bot to catch up + // TODO: Replace with a wait instead of a sleep + // TODO: Respect configuration.asyncWaitOnFrameZero + while ( ! botWrapper.botIdle()) { + long frameDuration = System.currentTimeMillis() - lastUpdateTimestampMillis; + if (frameDuration > configuration.asyncFrameDurationMillis && (client.clientData().gameData().getFrameCount() > 0 || ! configuration.asyncWaitOnFrameZero)) { + System.out.println("Client: Exceeded frame duration while waiting for bot: " + frameDuration + "ms on frame " + client.clientData().gameData().getFrameCount()); + break; + } + try { + Thread.sleep(1); + } catch (InterruptedException ignored) {} + } + + System.out.println("Client: Ending frame. Frames buffered: "); + lastUpdateTimestampMillis = System.currentTimeMillis(); client.update(); if (!client.isConnected()) { System.out.println("Reconnecting..."); diff --git a/src/main/java/bwapi/BWClientConfiguration.java b/src/main/java/bwapi/BWClientConfiguration.java index c9c97063..5cfa8b38 100644 --- a/src/main/java/bwapi/BWClientConfiguration.java +++ b/src/main/java/bwapi/BWClientConfiguration.java @@ -29,10 +29,10 @@ public class BWClientConfiguration { public boolean async = false; /** - * If JBWAPI detects that this much time (in nanoseconds) has passed since a bot's event handlers began, returns control back to BWAPI. + * If JBWAPI detects that this much time (in milliseconds) has passed since a bot's event handlers began, returns control back to BWAPI. * Real-time human play typically uses the "fastest" game speed, which has 42.86ms (42,860ns) between frames. */ - public int asyncFrameDurationNanos = 40000; + public int asyncFrameDurationMillis = 40000; /** * The maximum number of frames to buffer while waiting on a bot. @@ -53,8 +53,8 @@ public class BWClientConfiguration { * Checks that the configuration is in a valid state. Throws an IllegalArgumentException if it isn't. */ public void validate() { - if (async && asyncFrameDurationNanos < 0) { - throw new IllegalArgumentException("asyncFrameDurationNanos needs to be a non-negative number (it's how long JBWAPI waits for a bot response before returning control to BWAPI)."); + if (async && asyncFrameDurationMillis < 0) { + throw new IllegalArgumentException("asyncFrameDurationMillis needs to be a non-negative number (it's how long JBWAPI waits for a bot response before returning control to BWAPI)."); } if (async && asyncFrameBufferSize < 1) { throw new IllegalArgumentException("asyncFrameBufferSize needs to be a positive number (There needs to be at least one frame buffer)."); diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java index 34365cb9..7e43c863 100644 --- a/src/main/java/bwapi/BotWrapper.java +++ b/src/main/java/bwapi/BotWrapper.java @@ -26,63 +26,74 @@ of this software and associated documentation files (the "Software"), to deal package bwapi; import java.nio.ByteBuffer; +import java.util.EventListener; /** * Manages invocation of bot event handlers */ class BotWrapper { private final BWClientConfiguration configuration; - private final ByteBuffer sharedMemory; - private final ClientData clientData; - private final EventHandler eventHandler; + private final BWEventListener eventListener; + private final Game game; private final FrameBuffer frameBuffer; private final Thread botThread; - BotWrapper( - BWClientConfiguration configuration, - ByteBuffer sharedMemory, - ClientData clientData, - EventHandler eventHandler) { + BotWrapper(BWClientConfiguration configuration, BWEventListener eventListener, ByteBuffer dataSource) { this.configuration = configuration; - this.sharedMemory = sharedMemory; - this.clientData = clientData; - this.eventHandler = eventHandler; + this.eventListener = eventListener; + + ClientData currentClientData = new ClientData(); + currentClientData.setBuffer(dataSource); + game = new Game(currentClientData); if (configuration.async) { - frameBuffer = new FrameBuffer(configuration.asyncFrameBufferSize); + frameBuffer = new FrameBuffer(configuration.asyncFrameBufferSize, dataSource); botThread = new Thread(() -> { while(true) { - while(frameBuffer.empty()) try { - frameBuffer.wait(); - } catch (InterruptedException ignored) {} + while(frameBuffer.empty()) try { Thread.sleep(0, 100); } catch (InterruptedException ignored) {} + + System.out.println("Bot thread: Dequeuing frame. There are " + frameBuffer.framesBuffered() + " frames buffered."); + game.clientData().setBuffer(frameBuffer.dequeueFrame()); - frameBuffer.dequeueFrame(clientData); + System.out.println("Bot thread: Handling events."); handleEvents(); - if (!clientData.new GameData(0).isInGame()) { + if (!game.clientData().gameData().isInGame()) { + System.out.println("Bot thread: Exiting."); return; + } else { + System.out.println("Bot thread: Handled events."); } } }); + botThread.setName("JBWAPI Bot"); + botThread.start(); } else { frameBuffer = null; botThread = null; } } + Game getGame() { + return game; + } + + boolean botIdle() { + // TODO: This returns true if the bot is still processing the newest frame, leaving the bot permanently one frame behind + return frameBuffer.empty(); + } + void step() { if (configuration.async) { - frameBuffer.enqueueFrame(sharedMemory); - frameBuffer.notifyAll(); + frameBuffer.enqueueFrame(); } else { handleEvents(); } } private void handleEvents() { - for (int i = 0; i < clientData.new GameData(0).getEventCount(); i++) { - eventHandler.operation(clientData.new GameData(0).getEvents(i)); + ClientData.GameData gameData = game.clientData().gameData(); + for (int i = 0; i < gameData.getEventCount(); i++) { + EventHandler.operation(eventListener, game, gameData.getEvents(i)); } } - - } diff --git a/src/main/java/bwapi/Client.java b/src/main/java/bwapi/Client.java index cc41b351..05784a9b 100644 --- a/src/main/java/bwapi/Client.java +++ b/src/main/java/bwapi/Client.java @@ -41,15 +41,10 @@ interface MappingKernel extends Kernel32 { HANDLE OpenFileMapping(int desiredAccess, boolean inherit, String name); } - public interface EventHandler { - void operation(ClientData.Event event); - } - private static final int READ_WRITE = 0x1 | 0x2 | 0x4; private static final int SUPPORTED_BWAPI_VERSION = 10003; private ClientData clientData; - private ClientData.GameData gameData; private boolean connected = false; private RandomAccessFile pipeObjectHandle = null; private ByteBuffer gameTableFileHandle = null; @@ -67,17 +62,12 @@ public interface EventHandler { Client(ByteBuffer buffer) { clientData = new ClientData(); clientData.setBuffer(buffer); - gameData = clientData.new GameData(0); } ClientData clientData() { return clientData; } - GameData gameData() { - return gameData; - } - ByteBuffer mapFile() { return mapFileHandle; } @@ -92,7 +82,7 @@ void reconnect() { } } - void disconnect() { + private void disconnect() { if (configuration.debugConnection) { System.err.print("Disconnect called by: "); System.err.println(Thread.currentThread().getStackTrace()[2]); @@ -113,7 +103,7 @@ void disconnect() { gameTableFileHandle = null; mapFileHandle = null; - gameData = null; + clientData = null; connected = false; } @@ -204,7 +194,6 @@ boolean connect() { try { clientData = new ClientData(); clientData.setBuffer(mapFileHandle); - gameData = clientData.new GameData(0); } catch (Exception e) { System.err.println("Unable to map game data."); @@ -214,10 +203,10 @@ boolean connect() { return false; } - if (SUPPORTED_BWAPI_VERSION != gameData.getClient_version()) { + if (SUPPORTED_BWAPI_VERSION != clientData.gameData().getClient_version()) { System.err.println("Error: Client and Server are not compatible!"); System.err.println("Client version: " + SUPPORTED_BWAPI_VERSION); - System.err.println("Server version: " + gameData.getClient_version()); + System.err.println("Server version: " + clientData.gameData().getClient_version()); disconnect(); sleep(2000); return false; diff --git a/src/main/java/bwapi/ClientData.java b/src/main/java/bwapi/ClientData.java index e5b51bb1..2dca5ac7 100644 --- a/src/main/java/bwapi/ClientData.java +++ b/src/main/java/bwapi/ClientData.java @@ -1,13 +1,16 @@ package bwapi; import java.nio.ByteBuffer; final class ClientData { - WrappedBuffer buffer; - private int bufferOffset = 0; - public void setBuffer(ByteBuffer buffer) { - this.buffer = new WrappedBuffer(buffer); + private WrappedBuffer buffer; + private GameData gameData; + ClientData() { + gameData = new GameData(0); + } + GameData gameData() { + return gameData; } - public void setBufferOffset(int offset) { - this.bufferOffset = offset; + void setBuffer(ByteBuffer buffer) { + this.buffer = new WrappedBuffer(buffer); } class UnitCommand { static final int SIZE = 24; @@ -16,601 +19,601 @@ public UnitCommand(int myOffset) { this.myOffset = myOffset; } int getTid() { - int offset = bufferOffset + myOffset + 0; + int offset = myOffset + 0; return buffer.getInt(offset); } void setTid(int value) { - buffer.putInt(bufferOffset + myOffset + 0, value); + buffer.putInt(myOffset + 0, value); } int getUnitIndex() { - int offset = bufferOffset + myOffset + 4; + int offset = myOffset + 4; return buffer.getInt(offset); } void setUnitIndex(int value) { - buffer.putInt(bufferOffset + myOffset + 4, value); + buffer.putInt(myOffset + 4, value); } int getTargetIndex() { - int offset = bufferOffset + myOffset + 8; + int offset = myOffset + 8; return buffer.getInt(offset); } void setTargetIndex(int value) { - buffer.putInt(bufferOffset + myOffset + 8, value); + buffer.putInt(myOffset + 8, value); } int getX() { - int offset = bufferOffset + myOffset + 12; + int offset = myOffset + 12; return buffer.getInt(offset); } void setX(int value) { - buffer.putInt(bufferOffset + myOffset + 12, value); + buffer.putInt(myOffset + 12, value); } int getY() { - int offset = bufferOffset + myOffset + 16; + int offset = myOffset + 16; return buffer.getInt(offset); } void setY(int value) { - buffer.putInt(bufferOffset + myOffset + 16, value); + buffer.putInt(myOffset + 16, value); } int getExtra() { - int offset = bufferOffset + myOffset + 20; + int offset = myOffset + 20; return buffer.getInt(offset); } void setExtra(int value) { - buffer.putInt(bufferOffset + myOffset + 20, value); + buffer.putInt(myOffset + 20, value); } } class GameData { static final int SIZE = 33017048; private int myOffset; - public GameData(int myOffset) { + GameData(int myOffset) { this.myOffset = myOffset; } int getClient_version() { - int offset = bufferOffset + myOffset + 0; + int offset = myOffset + 0; return buffer.getInt(offset); } void setClient_version(int value) { - buffer.putInt(bufferOffset + myOffset + 0, value); + buffer.putInt(myOffset + 0, value); } int getRevision() { - int offset = bufferOffset + myOffset + 4; + int offset = myOffset + 4; return buffer.getInt(offset); } void setRevision(int value) { - buffer.putInt(bufferOffset + myOffset + 4, value); + buffer.putInt(myOffset + 4, value); } boolean isDebug() { - int offset = bufferOffset + myOffset + 8; + int offset = myOffset + 8; return buffer.getByte(offset) != 0; } void setIsDebug(boolean value) { - buffer.putByte(bufferOffset + myOffset + 8, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 8, (byte) (value ? 1 : 0)); } int getInstanceID() { - int offset = bufferOffset + myOffset + 12; + int offset = myOffset + 12; return buffer.getInt(offset); } void setInstanceID(int value) { - buffer.putInt(bufferOffset + myOffset + 12, value); + buffer.putInt(myOffset + 12, value); } int getBotAPM_noselects() { - int offset = bufferOffset + myOffset + 16; + int offset = myOffset + 16; return buffer.getInt(offset); } void setBotAPM_noselects(int value) { - buffer.putInt(bufferOffset + myOffset + 16, value); + buffer.putInt(myOffset + 16, value); } int getBotAPM_selects() { - int offset = bufferOffset + myOffset + 20; + int offset = myOffset + 20; return buffer.getInt(offset); } void setBotAPM_selects(int value) { - buffer.putInt(bufferOffset + myOffset + 20, value); + buffer.putInt(myOffset + 20, value); } int getForceCount() { - int offset = bufferOffset + myOffset + 24; + int offset = myOffset + 24; return buffer.getInt(offset); } void setForceCount(int value) { - buffer.putInt(bufferOffset + myOffset + 24, value); + buffer.putInt(myOffset + 24, value); } ForceData getForces(int i) { - int offset = bufferOffset + myOffset + 28 + 32 * 1 * i; + int offset = myOffset + 28 + 32 * 1 * i; return new ForceData(offset); } int getPlayerCount() { - int offset = bufferOffset + myOffset + 188; + int offset = myOffset + 188; return buffer.getInt(offset); } void setPlayerCount(int value) { - buffer.putInt(bufferOffset + myOffset + 188, value); + buffer.putInt(myOffset + 188, value); } PlayerData getPlayers(int i) { - int offset = bufferOffset + myOffset + 192 + 5788 * 1 * i; + int offset = myOffset + 192 + 5788 * 1 * i; return new PlayerData(offset); } int getInitialUnitCount() { - int offset = bufferOffset + myOffset + 69648; + int offset = myOffset + 69648; return buffer.getInt(offset); } void setInitialUnitCount(int value) { - buffer.putInt(bufferOffset + myOffset + 69648, value); + buffer.putInt(myOffset + 69648, value); } UnitData getUnits(int i) { - int offset = bufferOffset + myOffset + 69656 + 336 * 1 * i; + int offset = myOffset + 69656 + 336 * 1 * i; return new UnitData(offset); } int getUnitArray(int i) { - int offset = bufferOffset + myOffset + 3429656 + 4 * 1 * i; + int offset = myOffset + 3429656 + 4 * 1 * i; return buffer.getInt(offset); } void setUnitArray(int i, int value) { - buffer.putInt(bufferOffset + myOffset + 3429656 + 4 * 1 * i, value); + buffer.putInt(myOffset + 3429656 + 4 * 1 * i, value); } BulletData getBullets(int i) { - int offset = bufferOffset + myOffset + 3436456 + 80 * 1 * i; + int offset = myOffset + 3436456 + 80 * 1 * i; return new BulletData(offset); } int getNukeDotCount() { - int offset = bufferOffset + myOffset + 3444456; + int offset = myOffset + 3444456; return buffer.getInt(offset); } void setNukeDotCount(int value) { - buffer.putInt(bufferOffset + myOffset + 3444456, value); + buffer.putInt(myOffset + 3444456, value); } Position getNukeDots(int i) { - int offset = bufferOffset + myOffset + 3444460 + 8 * 1 * i; + int offset = myOffset + 3444460 + 8 * 1 * i; return new Position(offset); } int getGameType() { - int offset = bufferOffset + myOffset + 3446060; + int offset = myOffset + 3446060; return buffer.getInt(offset); } void setGameType(int value) { - buffer.putInt(bufferOffset + myOffset + 3446060, value); + buffer.putInt(myOffset + 3446060, value); } int getLatency() { - int offset = bufferOffset + myOffset + 3446064; + int offset = myOffset + 3446064; return buffer.getInt(offset); } void setLatency(int value) { - buffer.putInt(bufferOffset + myOffset + 3446064, value); + buffer.putInt(myOffset + 3446064, value); } int getLatencyFrames() { - int offset = bufferOffset + myOffset + 3446068; + int offset = myOffset + 3446068; return buffer.getInt(offset); } void setLatencyFrames(int value) { - buffer.putInt(bufferOffset + myOffset + 3446068, value); + buffer.putInt(myOffset + 3446068, value); } int getLatencyTime() { - int offset = bufferOffset + myOffset + 3446072; + int offset = myOffset + 3446072; return buffer.getInt(offset); } void setLatencyTime(int value) { - buffer.putInt(bufferOffset + myOffset + 3446072, value); + buffer.putInt(myOffset + 3446072, value); } int getRemainingLatencyFrames() { - int offset = bufferOffset + myOffset + 3446076; + int offset = myOffset + 3446076; return buffer.getInt(offset); } void setRemainingLatencyFrames(int value) { - buffer.putInt(bufferOffset + myOffset + 3446076, value); + buffer.putInt(myOffset + 3446076, value); } int getRemainingLatencyTime() { - int offset = bufferOffset + myOffset + 3446080; + int offset = myOffset + 3446080; return buffer.getInt(offset); } void setRemainingLatencyTime(int value) { - buffer.putInt(bufferOffset + myOffset + 3446080, value); + buffer.putInt(myOffset + 3446080, value); } boolean getHasLatCom() { - int offset = bufferOffset + myOffset + 3446084; + int offset = myOffset + 3446084; return buffer.getByte(offset) != 0; } void setHasLatCom(boolean value) { - buffer.putByte(bufferOffset + myOffset + 3446084, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 3446084, (byte) (value ? 1 : 0)); } boolean getHasGUI() { - int offset = bufferOffset + myOffset + 3446085; + int offset = myOffset + 3446085; return buffer.getByte(offset) != 0; } void setHasGUI(boolean value) { - buffer.putByte(bufferOffset + myOffset + 3446085, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 3446085, (byte) (value ? 1 : 0)); } int getReplayFrameCount() { - int offset = bufferOffset + myOffset + 3446088; + int offset = myOffset + 3446088; return buffer.getInt(offset); } void setReplayFrameCount(int value) { - buffer.putInt(bufferOffset + myOffset + 3446088, value); + buffer.putInt(myOffset + 3446088, value); } int getRandomSeed() { - int offset = bufferOffset + myOffset + 3446092; + int offset = myOffset + 3446092; return buffer.getInt(offset); } void setRandomSeed(int value) { - buffer.putInt(bufferOffset + myOffset + 3446092, value); + buffer.putInt(myOffset + 3446092, value); } int getFrameCount() { - int offset = bufferOffset + myOffset + 3446096; + int offset = myOffset + 3446096; return buffer.getInt(offset); } void setFrameCount(int value) { - buffer.putInt(bufferOffset + myOffset + 3446096, value); + buffer.putInt(myOffset + 3446096, value); } int getElapsedTime() { - int offset = bufferOffset + myOffset + 3446100; + int offset = myOffset + 3446100; return buffer.getInt(offset); } void setElapsedTime(int value) { - buffer.putInt(bufferOffset + myOffset + 3446100, value); + buffer.putInt(myOffset + 3446100, value); } int getCountdownTimer() { - int offset = bufferOffset + myOffset + 3446104; + int offset = myOffset + 3446104; return buffer.getInt(offset); } void setCountdownTimer(int value) { - buffer.putInt(bufferOffset + myOffset + 3446104, value); + buffer.putInt(myOffset + 3446104, value); } int getFps() { - int offset = bufferOffset + myOffset + 3446108; + int offset = myOffset + 3446108; return buffer.getInt(offset); } void setFps(int value) { - buffer.putInt(bufferOffset + myOffset + 3446108, value); + buffer.putInt(myOffset + 3446108, value); } double getAverageFPS() { - int offset = bufferOffset + myOffset + 3446112; + int offset = myOffset + 3446112; return buffer.getDouble(offset); } void setAverageFPS(double value) { - buffer.putDouble(bufferOffset + myOffset + 3446112, value); + buffer.putDouble(myOffset + 3446112, value); } int getMouseX() { - int offset = bufferOffset + myOffset + 3446120; + int offset = myOffset + 3446120; return buffer.getInt(offset); } void setMouseX(int value) { - buffer.putInt(bufferOffset + myOffset + 3446120, value); + buffer.putInt(myOffset + 3446120, value); } int getMouseY() { - int offset = bufferOffset + myOffset + 3446124; + int offset = myOffset + 3446124; return buffer.getInt(offset); } void setMouseY(int value) { - buffer.putInt(bufferOffset + myOffset + 3446124, value); + buffer.putInt(myOffset + 3446124, value); } boolean getMouseState(int i) { - int offset = bufferOffset + myOffset + 3446128 + 1 * 1 * i; + int offset = myOffset + 3446128 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setMouseState(int i, boolean value) { - buffer.putByte(bufferOffset + myOffset + 3446128 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 3446128 + 1 * 1 * i, (byte) (value ? 1 : 0)); } boolean getKeyState(int i) { - int offset = bufferOffset + myOffset + 3446131 + 1 * 1 * i; + int offset = myOffset + 3446131 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setKeyState(int i, boolean value) { - buffer.putByte(bufferOffset + myOffset + 3446131 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 3446131 + 1 * 1 * i, (byte) (value ? 1 : 0)); } int getScreenX() { - int offset = bufferOffset + myOffset + 3446388; + int offset = myOffset + 3446388; return buffer.getInt(offset); } void setScreenX(int value) { - buffer.putInt(bufferOffset + myOffset + 3446388, value); + buffer.putInt(myOffset + 3446388, value); } int getScreenY() { - int offset = bufferOffset + myOffset + 3446392; + int offset = myOffset + 3446392; return buffer.getInt(offset); } void setScreenY(int value) { - buffer.putInt(bufferOffset + myOffset + 3446392, value); + buffer.putInt(myOffset + 3446392, value); } boolean getFlags(int i) { - int offset = bufferOffset + myOffset + 3446396 + 1 * 1 * i; + int offset = myOffset + 3446396 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setFlags(int i, boolean value) { - buffer.putByte(bufferOffset + myOffset + 3446396 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 3446396 + 1 * 1 * i, (byte) (value ? 1 : 0)); } int getMapWidth() { - int offset = bufferOffset + myOffset + 3446400; + int offset = myOffset + 3446400; return buffer.getInt(offset); } void setMapWidth(int value) { - buffer.putInt(bufferOffset + myOffset + 3446400, value); + buffer.putInt(myOffset + 3446400, value); } int getMapHeight() { - int offset = bufferOffset + myOffset + 3446404; + int offset = myOffset + 3446404; return buffer.getInt(offset); } void setMapHeight(int value) { - buffer.putInt(bufferOffset + myOffset + 3446404, value); + buffer.putInt(myOffset + 3446404, value); } String getMapFileName() { - int offset = bufferOffset + myOffset + 3446408; + int offset = myOffset + 3446408; return buffer.getString(offset, 261); } void setMapFileName(String value) { - buffer.putString(bufferOffset + myOffset + 3446408, 261, value); + buffer.putString(myOffset + 3446408, 261, value); } String getMapPathName() { - int offset = bufferOffset + myOffset + 3446669; + int offset = myOffset + 3446669; return buffer.getString(offset, 261); } void setMapPathName(String value) { - buffer.putString(bufferOffset + myOffset + 3446669, 261, value); + buffer.putString(myOffset + 3446669, 261, value); } String getMapName() { - int offset = bufferOffset + myOffset + 3446930; + int offset = myOffset + 3446930; return buffer.getString(offset, 33); } void setMapName(String value) { - buffer.putString(bufferOffset + myOffset + 3446930, 33, value); + buffer.putString(myOffset + 3446930, 33, value); } String getMapHash() { - int offset = bufferOffset + myOffset + 3446963; + int offset = myOffset + 3446963; return buffer.getString(offset, 41); } void setMapHash(String value) { - buffer.putString(bufferOffset + myOffset + 3446963, 41, value); + buffer.putString(myOffset + 3446963, 41, value); } int getGroundHeight(int i, int j) { - int offset = bufferOffset + myOffset + 3447004 + 4 * 1 * j + 4 * 256 * i; + int offset = myOffset + 3447004 + 4 * 1 * j + 4 * 256 * i; return buffer.getInt(offset); } void setGetGroundHeight(int i, int j, int value) { - buffer.putInt(bufferOffset + myOffset + 3447004 + 4 * 1 * j + 4 * 256 * i, value); + buffer.putInt(myOffset + 3447004 + 4 * 1 * j + 4 * 256 * i, value); } boolean isWalkable(int i, int j) { - int offset = bufferOffset + myOffset + 3709148 + 1 * 1 * j + 1 * 1024 * i; + int offset = myOffset + 3709148 + 1 * 1 * j + 1 * 1024 * i; return buffer.getByte(offset) != 0; } void setIsWalkable(int i, int j, boolean value) { - buffer.putByte(bufferOffset + myOffset + 3709148 + 1 * 1 * j + 1 * 1024 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 3709148 + 1 * 1 * j + 1 * 1024 * i, (byte) (value ? 1 : 0)); } boolean isBuildable(int i, int j) { - int offset = bufferOffset + myOffset + 4757724 + 1 * 1 * j + 1 * 256 * i; + int offset = myOffset + 4757724 + 1 * 1 * j + 1 * 256 * i; return buffer.getByte(offset) != 0; } void setIsBuildable(int i, int j, boolean value) { - buffer.putByte(bufferOffset + myOffset + 4757724 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 4757724 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); } boolean isVisible(int i, int j) { - int offset = bufferOffset + myOffset + 4823260 + 1 * 1 * j + 1 * 256 * i; + int offset = myOffset + 4823260 + 1 * 1 * j + 1 * 256 * i; return buffer.getByte(offset) != 0; } void setIsVisible(int i, int j, boolean value) { - buffer.putByte(bufferOffset + myOffset + 4823260 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 4823260 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); } boolean isExplored(int i, int j) { - int offset = bufferOffset + myOffset + 4888796 + 1 * 1 * j + 1 * 256 * i; + int offset = myOffset + 4888796 + 1 * 1 * j + 1 * 256 * i; return buffer.getByte(offset) != 0; } void setIsExplored(int i, int j, boolean value) { - buffer.putByte(bufferOffset + myOffset + 4888796 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 4888796 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); } boolean getHasCreep(int i, int j) { - int offset = bufferOffset + myOffset + 4954332 + 1 * 1 * j + 1 * 256 * i; + int offset = myOffset + 4954332 + 1 * 1 * j + 1 * 256 * i; return buffer.getByte(offset) != 0; } void setHasCreep(int i, int j, boolean value) { - buffer.putByte(bufferOffset + myOffset + 4954332 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 4954332 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); } boolean isOccupied(int i, int j) { - int offset = bufferOffset + myOffset + 5019868 + 1 * 1 * j + 1 * 256 * i; + int offset = myOffset + 5019868 + 1 * 1 * j + 1 * 256 * i; return buffer.getByte(offset) != 0; } void setIsOccupied(int i, int j, boolean value) { - buffer.putByte(bufferOffset + myOffset + 5019868 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 5019868 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); } short getMapTileRegionId(int i, int j) { - int offset = bufferOffset + myOffset + 5085404 + 2 * 1 * j + 2 * 256 * i; + int offset = myOffset + 5085404 + 2 * 1 * j + 2 * 256 * i; return buffer.getShort(offset); } void setMapTileRegionId(int i, int j, short value) { - buffer.putShort(bufferOffset + myOffset + 5085404 + 2 * 1 * j + 2 * 256 * i, value); + buffer.putShort(myOffset + 5085404 + 2 * 1 * j + 2 * 256 * i, value); } short getMapSplitTilesMiniTileMask(int i) { - int offset = bufferOffset + myOffset + 5216476 + 2 * 1 * i; + int offset = myOffset + 5216476 + 2 * 1 * i; return buffer.getShort(offset); } void setMapSplitTilesMiniTileMask(int i, short value) { - buffer.putShort(bufferOffset + myOffset + 5216476 + 2 * 1 * i, value); + buffer.putShort(myOffset + 5216476 + 2 * 1 * i, value); } short getMapSplitTilesRegion1(int i) { - int offset = bufferOffset + myOffset + 5226476 + 2 * 1 * i; + int offset = myOffset + 5226476 + 2 * 1 * i; return buffer.getShort(offset); } void setMapSplitTilesRegion1(int i, short value) { - buffer.putShort(bufferOffset + myOffset + 5226476 + 2 * 1 * i, value); + buffer.putShort(myOffset + 5226476 + 2 * 1 * i, value); } short getMapSplitTilesRegion2(int i) { - int offset = bufferOffset + myOffset + 5236476 + 2 * 1 * i; + int offset = myOffset + 5236476 + 2 * 1 * i; return buffer.getShort(offset); } void setMapSplitTilesRegion2(int i, short value) { - buffer.putShort(bufferOffset + myOffset + 5236476 + 2 * 1 * i, value); + buffer.putShort(myOffset + 5236476 + 2 * 1 * i, value); } int getRegionCount() { - int offset = bufferOffset + myOffset + 5246476; + int offset = myOffset + 5246476; return buffer.getInt(offset); } void setRegionCount(int value) { - buffer.putInt(bufferOffset + myOffset + 5246476, value); + buffer.putInt(myOffset + 5246476, value); } RegionData getRegions(int i) { - int offset = bufferOffset + myOffset + 5246480 + 1068 * 1 * i; + int offset = myOffset + 5246480 + 1068 * 1 * i; return new RegionData(offset); } int getStartLocationCount() { - int offset = bufferOffset + myOffset + 10586480; + int offset = myOffset + 10586480; return buffer.getInt(offset); } void setStartLocationCount(int value) { - buffer.putInt(bufferOffset + myOffset + 10586480, value); + buffer.putInt(myOffset + 10586480, value); } Position getStartLocations(int i) { - int offset = bufferOffset + myOffset + 10586484 + 8 * 1 * i; + int offset = myOffset + 10586484 + 8 * 1 * i; return new Position(offset); } boolean isInGame() { - int offset = bufferOffset + myOffset + 10586548; + int offset = myOffset + 10586548; return buffer.getByte(offset) != 0; } void setIsInGame(boolean value) { - buffer.putByte(bufferOffset + myOffset + 10586548, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 10586548, (byte) (value ? 1 : 0)); } boolean isMultiplayer() { - int offset = bufferOffset + myOffset + 10586549; + int offset = myOffset + 10586549; return buffer.getByte(offset) != 0; } void setIsMultiplayer(boolean value) { - buffer.putByte(bufferOffset + myOffset + 10586549, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 10586549, (byte) (value ? 1 : 0)); } boolean isBattleNet() { - int offset = bufferOffset + myOffset + 10586550; + int offset = myOffset + 10586550; return buffer.getByte(offset) != 0; } void setIsBattleNet(boolean value) { - buffer.putByte(bufferOffset + myOffset + 10586550, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 10586550, (byte) (value ? 1 : 0)); } boolean isPaused() { - int offset = bufferOffset + myOffset + 10586551; + int offset = myOffset + 10586551; return buffer.getByte(offset) != 0; } void setIsPaused(boolean value) { - buffer.putByte(bufferOffset + myOffset + 10586551, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 10586551, (byte) (value ? 1 : 0)); } boolean isReplay() { - int offset = bufferOffset + myOffset + 10586552; + int offset = myOffset + 10586552; return buffer.getByte(offset) != 0; } void setIsReplay(boolean value) { - buffer.putByte(bufferOffset + myOffset + 10586552, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 10586552, (byte) (value ? 1 : 0)); } int getSelectedUnitCount() { - int offset = bufferOffset + myOffset + 10586556; + int offset = myOffset + 10586556; return buffer.getInt(offset); } void setSelectedUnitCount(int value) { - buffer.putInt(bufferOffset + myOffset + 10586556, value); + buffer.putInt(myOffset + 10586556, value); } int getSelectedUnits(int i) { - int offset = bufferOffset + myOffset + 10586560 + 4 * 1 * i; + int offset = myOffset + 10586560 + 4 * 1 * i; return buffer.getInt(offset); } void setSelectedUnits(int i, int value) { - buffer.putInt(bufferOffset + myOffset + 10586560 + 4 * 1 * i, value); + buffer.putInt(myOffset + 10586560 + 4 * 1 * i, value); } int getSelf() { - int offset = bufferOffset + myOffset + 10586608; + int offset = myOffset + 10586608; return buffer.getInt(offset); } void setSelf(int value) { - buffer.putInt(bufferOffset + myOffset + 10586608, value); + buffer.putInt(myOffset + 10586608, value); } int getEnemy() { - int offset = bufferOffset + myOffset + 10586612; + int offset = myOffset + 10586612; return buffer.getInt(offset); } void setEnemy(int value) { - buffer.putInt(bufferOffset + myOffset + 10586612, value); + buffer.putInt(myOffset + 10586612, value); } int getNeutral() { - int offset = bufferOffset + myOffset + 10586616; + int offset = myOffset + 10586616; return buffer.getInt(offset); } void setNeutral(int value) { - buffer.putInt(bufferOffset + myOffset + 10586616, value); + buffer.putInt(myOffset + 10586616, value); } int getEventCount() { - int offset = bufferOffset + myOffset + 10586620; + int offset = myOffset + 10586620; return buffer.getInt(offset); } void setEventCount(int value) { - buffer.putInt(bufferOffset + myOffset + 10586620, value); + buffer.putInt(myOffset + 10586620, value); } Event getEvents(int i) { - int offset = bufferOffset + myOffset + 10586624 + 12 * 1 * i; + int offset = myOffset + 10586624 + 12 * 1 * i; return new Event(offset); } int getEventStringCount() { - int offset = bufferOffset + myOffset + 10706624; + int offset = myOffset + 10706624; return buffer.getInt(offset); } void setEventStringCount(int value) { - buffer.putInt(bufferOffset + myOffset + 10706624, value); + buffer.putInt(myOffset + 10706624, value); } String getEventStrings(int i) { - int offset = bufferOffset + myOffset + 10706628 + 1 * 256 * i; + int offset = myOffset + 10706628 + 1 * 256 * i; return buffer.getString(offset, 256); } void setEventStrings(int i, String value) { - buffer.putString(bufferOffset + myOffset + 10706628 + 1 * 256 * i, 256, value); + buffer.putString(myOffset + 10706628 + 1 * 256 * i, 256, value); } int getStringCount() { - int offset = bufferOffset + myOffset + 10962628; + int offset = myOffset + 10962628; return buffer.getInt(offset); } void setStringCount(int value) { - buffer.putInt(bufferOffset + myOffset + 10962628, value); + buffer.putInt(myOffset + 10962628, value); } String getStrings(int i) { - int offset = bufferOffset + myOffset + 10962632 + 1 * 1024 * i; + int offset = myOffset + 10962632 + 1 * 1024 * i; return buffer.getString(offset, 1024); } void setStrings(int i, String value) { - buffer.putString(bufferOffset + myOffset + 10962632 + 1 * 1024 * i, 1024, value); + buffer.putString(myOffset + 10962632 + 1 * 1024 * i, 1024, value); } int getShapeCount() { - int offset = bufferOffset + myOffset + 31442632; + int offset = myOffset + 31442632; return buffer.getInt(offset); } void setShapeCount(int value) { - buffer.putInt(bufferOffset + myOffset + 31442632, value); + buffer.putInt(myOffset + 31442632, value); } Shape getShapes(int i) { - int offset = bufferOffset + myOffset + 31442636 + 40 * 1 * i; + int offset = myOffset + 31442636 + 40 * 1 * i; return new Shape(offset); } int getCommandCount() { - int offset = bufferOffset + myOffset + 32242636; + int offset = myOffset + 32242636; return buffer.getInt(offset); } void setCommandCount(int value) { - buffer.putInt(bufferOffset + myOffset + 32242636, value); + buffer.putInt(myOffset + 32242636, value); } Command getCommands(int i) { - int offset = bufferOffset + myOffset + 32242640 + 12 * 1 * i; + int offset = myOffset + 32242640 + 12 * 1 * i; return new Command(offset); } int getUnitCommandCount() { - int offset = bufferOffset + myOffset + 32482640; + int offset = myOffset + 32482640; return buffer.getInt(offset); } void setUnitCommandCount(int value) { - buffer.putInt(bufferOffset + myOffset + 32482640, value); + buffer.putInt(myOffset + 32482640, value); } UnitCommand getUnitCommands(int i) { - int offset = bufferOffset + myOffset + 32482644 + 24 * 1 * i; + int offset = myOffset + 32482644 + 24 * 1 * i; return new UnitCommand(offset); } int getUnitSearchSize() { - int offset = bufferOffset + myOffset + 32962644; + int offset = myOffset + 32962644; return buffer.getInt(offset); } void setUnitSearchSize(int value) { - buffer.putInt(bufferOffset + myOffset + 32962644, value); + buffer.putInt(myOffset + 32962644, value); } unitFinder getXUnitSearch(int i) { - int offset = bufferOffset + myOffset + 32962648 + 8 * 1 * i; + int offset = myOffset + 32962648 + 8 * 1 * i; return new unitFinder(offset); } unitFinder getYUnitSearch(int i) { - int offset = bufferOffset + myOffset + 32989848 + 8 * 1 * i; + int offset = myOffset + 32989848 + 8 * 1 * i; return new unitFinder(offset); } } @@ -621,74 +624,74 @@ public Shape(int myOffset) { this.myOffset = myOffset; } ShapeType getType() { - int offset = bufferOffset + myOffset + 0; + int offset = myOffset + 0; return ShapeType.idToEnum[buffer.getInt(offset)]; } void setType(ShapeType value) { - buffer.putInt(bufferOffset + myOffset + 0, value.id); + buffer.putInt(myOffset + 0, value.id); } CoordinateType getCtype() { - int offset = bufferOffset + myOffset + 4; + int offset = myOffset + 4; return CoordinateType.idToEnum[buffer.getInt(offset)]; } void setCtype(CoordinateType value) { - buffer.putInt(bufferOffset + myOffset + 4, value.id); + buffer.putInt(myOffset + 4, value.id); } int getX1() { - int offset = bufferOffset + myOffset + 8; + int offset = myOffset + 8; return buffer.getInt(offset); } void setX1(int value) { - buffer.putInt(bufferOffset + myOffset + 8, value); + buffer.putInt(myOffset + 8, value); } int getY1() { - int offset = bufferOffset + myOffset + 12; + int offset = myOffset + 12; return buffer.getInt(offset); } void setY1(int value) { - buffer.putInt(bufferOffset + myOffset + 12, value); + buffer.putInt(myOffset + 12, value); } int getX2() { - int offset = bufferOffset + myOffset + 16; + int offset = myOffset + 16; return buffer.getInt(offset); } void setX2(int value) { - buffer.putInt(bufferOffset + myOffset + 16, value); + buffer.putInt(myOffset + 16, value); } int getY2() { - int offset = bufferOffset + myOffset + 20; + int offset = myOffset + 20; return buffer.getInt(offset); } void setY2(int value) { - buffer.putInt(bufferOffset + myOffset + 20, value); + buffer.putInt(myOffset + 20, value); } int getExtra1() { - int offset = bufferOffset + myOffset + 24; + int offset = myOffset + 24; return buffer.getInt(offset); } void setExtra1(int value) { - buffer.putInt(bufferOffset + myOffset + 24, value); + buffer.putInt(myOffset + 24, value); } int getExtra2() { - int offset = bufferOffset + myOffset + 28; + int offset = myOffset + 28; return buffer.getInt(offset); } void setExtra2(int value) { - buffer.putInt(bufferOffset + myOffset + 28, value); + buffer.putInt(myOffset + 28, value); } int getColor() { - int offset = bufferOffset + myOffset + 32; + int offset = myOffset + 32; return buffer.getInt(offset); } void setColor(int value) { - buffer.putInt(bufferOffset + myOffset + 32, value); + buffer.putInt(myOffset + 32, value); } boolean isSolid() { - int offset = bufferOffset + myOffset + 36; + int offset = myOffset + 36; return buffer.getByte(offset) != 0; } void setIsSolid(boolean value) { - buffer.putByte(bufferOffset + myOffset + 36, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 36, (byte) (value ? 1 : 0)); } } class Command { @@ -698,25 +701,25 @@ public Command(int myOffset) { this.myOffset = myOffset; } CommandType getType() { - int offset = bufferOffset + myOffset + 0; + int offset = myOffset + 0; return CommandType.idToEnum[buffer.getInt(offset)]; } void setType(CommandType value) { - buffer.putInt(bufferOffset + myOffset + 0, value.id); + buffer.putInt(myOffset + 0, value.id); } int getValue1() { - int offset = bufferOffset + myOffset + 4; + int offset = myOffset + 4; return buffer.getInt(offset); } void setValue1(int value) { - buffer.putInt(bufferOffset + myOffset + 4, value); + buffer.putInt(myOffset + 4, value); } int getValue2() { - int offset = bufferOffset + myOffset + 8; + int offset = myOffset + 8; return buffer.getInt(offset); } void setValue2(int value) { - buffer.putInt(bufferOffset + myOffset + 8, value); + buffer.putInt(myOffset + 8, value); } } class Position { @@ -726,18 +729,18 @@ public Position(int myOffset) { this.myOffset = myOffset; } int getX() { - int offset = bufferOffset + myOffset + 0; + int offset = myOffset + 0; return buffer.getInt(offset); } void setX(int value) { - buffer.putInt(bufferOffset + myOffset + 0, value); + buffer.putInt(myOffset + 0, value); } int getY() { - int offset = bufferOffset + myOffset + 4; + int offset = myOffset + 4; return buffer.getInt(offset); } void setY(int value) { - buffer.putInt(bufferOffset + myOffset + 4, value); + buffer.putInt(myOffset + 4, value); } } class Event { @@ -747,25 +750,25 @@ public Event(int myOffset) { this.myOffset = myOffset; } EventType getType() { - int offset = bufferOffset + myOffset + 0; + int offset = myOffset + 0; return EventType.idToEnum[buffer.getInt(offset)]; } void setType(EventType value) { - buffer.putInt(bufferOffset + myOffset + 0, value.id); + buffer.putInt(myOffset + 0, value.id); } int getV1() { - int offset = bufferOffset + myOffset + 4; + int offset = myOffset + 4; return buffer.getInt(offset); } void setV1(int value) { - buffer.putInt(bufferOffset + myOffset + 4, value); + buffer.putInt(myOffset + 4, value); } int getV2() { - int offset = bufferOffset + myOffset + 8; + int offset = myOffset + 8; return buffer.getInt(offset); } void setV2(int value) { - buffer.putInt(bufferOffset + myOffset + 8, value); + buffer.putInt(myOffset + 8, value); } } class RegionData { @@ -775,95 +778,95 @@ public RegionData(int myOffset) { this.myOffset = myOffset; } int getId() { - int offset = bufferOffset + myOffset + 0; + int offset = myOffset + 0; return buffer.getInt(offset); } void setId(int value) { - buffer.putInt(bufferOffset + myOffset + 0, value); + buffer.putInt(myOffset + 0, value); } int islandID() { - int offset = bufferOffset + myOffset + 4; + int offset = myOffset + 4; return buffer.getInt(offset); } void setIslandID(int value) { - buffer.putInt(bufferOffset + myOffset + 4, value); + buffer.putInt(myOffset + 4, value); } int getCenter_x() { - int offset = bufferOffset + myOffset + 8; + int offset = myOffset + 8; return buffer.getInt(offset); } void setCenter_x(int value) { - buffer.putInt(bufferOffset + myOffset + 8, value); + buffer.putInt(myOffset + 8, value); } int getCenter_y() { - int offset = bufferOffset + myOffset + 12; + int offset = myOffset + 12; return buffer.getInt(offset); } void setCenter_y(int value) { - buffer.putInt(bufferOffset + myOffset + 12, value); + buffer.putInt(myOffset + 12, value); } int getPriority() { - int offset = bufferOffset + myOffset + 16; + int offset = myOffset + 16; return buffer.getInt(offset); } void setPriority(int value) { - buffer.putInt(bufferOffset + myOffset + 16, value); + buffer.putInt(myOffset + 16, value); } int getLeftMost() { - int offset = bufferOffset + myOffset + 20; + int offset = myOffset + 20; return buffer.getInt(offset); } void setLeftMost(int value) { - buffer.putInt(bufferOffset + myOffset + 20, value); + buffer.putInt(myOffset + 20, value); } int getRightMost() { - int offset = bufferOffset + myOffset + 24; + int offset = myOffset + 24; return buffer.getInt(offset); } void setRightMost(int value) { - buffer.putInt(bufferOffset + myOffset + 24, value); + buffer.putInt(myOffset + 24, value); } int getTopMost() { - int offset = bufferOffset + myOffset + 28; + int offset = myOffset + 28; return buffer.getInt(offset); } void setTopMost(int value) { - buffer.putInt(bufferOffset + myOffset + 28, value); + buffer.putInt(myOffset + 28, value); } int getBottomMost() { - int offset = bufferOffset + myOffset + 32; + int offset = myOffset + 32; return buffer.getInt(offset); } void setBottomMost(int value) { - buffer.putInt(bufferOffset + myOffset + 32, value); + buffer.putInt(myOffset + 32, value); } int getNeighborCount() { - int offset = bufferOffset + myOffset + 36; + int offset = myOffset + 36; return buffer.getInt(offset); } void setNeighborCount(int value) { - buffer.putInt(bufferOffset + myOffset + 36, value); + buffer.putInt(myOffset + 36, value); } int getNeighbors(int i) { - int offset = bufferOffset + myOffset + 40 + 4 * 1 * i; + int offset = myOffset + 40 + 4 * 1 * i; return buffer.getInt(offset); } void setNeighbors(int i, int value) { - buffer.putInt(bufferOffset + myOffset + 40 + 4 * 1 * i, value); + buffer.putInt(myOffset + 40 + 4 * 1 * i, value); } boolean isAccessible() { - int offset = bufferOffset + myOffset + 1064; + int offset = myOffset + 1064; return buffer.getByte(offset) != 0; } void setIsAccessible(boolean value) { - buffer.putByte(bufferOffset + myOffset + 1064, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 1064, (byte) (value ? 1 : 0)); } boolean isHigherGround() { - int offset = bufferOffset + myOffset + 1065; + int offset = myOffset + 1065; return buffer.getByte(offset) != 0; } void setIsHigherGround(boolean value) { - buffer.putByte(bufferOffset + myOffset + 1065, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 1065, (byte) (value ? 1 : 0)); } } class ForceData { @@ -873,11 +876,11 @@ public ForceData(int myOffset) { this.myOffset = myOffset; } String getName() { - int offset = bufferOffset + myOffset + 0; + int offset = myOffset + 0; return buffer.getString(offset, 32); } void setName(String value) { - buffer.putString(bufferOffset + myOffset + 0, 32, value); + buffer.putString(myOffset + 0, 32, value); } } class PlayerData { @@ -887,291 +890,291 @@ public PlayerData(int myOffset) { this.myOffset = myOffset; } String getName() { - int offset = bufferOffset + myOffset + 0; + int offset = myOffset + 0; return buffer.getString(offset, 25); } void setName(String value) { - buffer.putString(bufferOffset + myOffset + 0, 25, value); + buffer.putString(myOffset + 0, 25, value); } int getRace() { - int offset = bufferOffset + myOffset + 28; + int offset = myOffset + 28; return buffer.getInt(offset); } void setRace(int value) { - buffer.putInt(bufferOffset + myOffset + 28, value); + buffer.putInt(myOffset + 28, value); } int getType() { - int offset = bufferOffset + myOffset + 32; + int offset = myOffset + 32; return buffer.getInt(offset); } void setType(int value) { - buffer.putInt(bufferOffset + myOffset + 32, value); + buffer.putInt(myOffset + 32, value); } int getForce() { - int offset = bufferOffset + myOffset + 36; + int offset = myOffset + 36; return buffer.getInt(offset); } void setForce(int value) { - buffer.putInt(bufferOffset + myOffset + 36, value); + buffer.putInt(myOffset + 36, value); } boolean isAlly(int i) { - int offset = bufferOffset + myOffset + 40 + 1 * 1 * i; + int offset = myOffset + 40 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setIsAlly(int i, boolean value) { - buffer.putByte(bufferOffset + myOffset + 40 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 40 + 1 * 1 * i, (byte) (value ? 1 : 0)); } boolean isEnemy(int i) { - int offset = bufferOffset + myOffset + 52 + 1 * 1 * i; + int offset = myOffset + 52 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setIsEnemy(int i, boolean value) { - buffer.putByte(bufferOffset + myOffset + 52 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 52 + 1 * 1 * i, (byte) (value ? 1 : 0)); } boolean isNeutral() { - int offset = bufferOffset + myOffset + 64; + int offset = myOffset + 64; return buffer.getByte(offset) != 0; } void setIsNeutral(boolean value) { - buffer.putByte(bufferOffset + myOffset + 64, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 64, (byte) (value ? 1 : 0)); } int getStartLocationX() { - int offset = bufferOffset + myOffset + 68; + int offset = myOffset + 68; return buffer.getInt(offset); } void setStartLocationX(int value) { - buffer.putInt(bufferOffset + myOffset + 68, value); + buffer.putInt(myOffset + 68, value); } int getStartLocationY() { - int offset = bufferOffset + myOffset + 72; + int offset = myOffset + 72; return buffer.getInt(offset); } void setStartLocationY(int value) { - buffer.putInt(bufferOffset + myOffset + 72, value); + buffer.putInt(myOffset + 72, value); } boolean isVictorious() { - int offset = bufferOffset + myOffset + 76; + int offset = myOffset + 76; return buffer.getByte(offset) != 0; } void setIsVictorious(boolean value) { - buffer.putByte(bufferOffset + myOffset + 76, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 76, (byte) (value ? 1 : 0)); } boolean isDefeated() { - int offset = bufferOffset + myOffset + 77; + int offset = myOffset + 77; return buffer.getByte(offset) != 0; } void setIsDefeated(boolean value) { - buffer.putByte(bufferOffset + myOffset + 77, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 77, (byte) (value ? 1 : 0)); } boolean getLeftGame() { - int offset = bufferOffset + myOffset + 78; + int offset = myOffset + 78; return buffer.getByte(offset) != 0; } void setLeftGame(boolean value) { - buffer.putByte(bufferOffset + myOffset + 78, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 78, (byte) (value ? 1 : 0)); } boolean isParticipating() { - int offset = bufferOffset + myOffset + 79; + int offset = myOffset + 79; return buffer.getByte(offset) != 0; } void setIsParticipating(boolean value) { - buffer.putByte(bufferOffset + myOffset + 79, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 79, (byte) (value ? 1 : 0)); } int getMinerals() { - int offset = bufferOffset + myOffset + 80; + int offset = myOffset + 80; return buffer.getInt(offset); } void setMinerals(int value) { - buffer.putInt(bufferOffset + myOffset + 80, value); + buffer.putInt(myOffset + 80, value); } int getGas() { - int offset = bufferOffset + myOffset + 84; + int offset = myOffset + 84; return buffer.getInt(offset); } void setGas(int value) { - buffer.putInt(bufferOffset + myOffset + 84, value); + buffer.putInt(myOffset + 84, value); } int getGatheredMinerals() { - int offset = bufferOffset + myOffset + 88; + int offset = myOffset + 88; return buffer.getInt(offset); } void setGatheredMinerals(int value) { - buffer.putInt(bufferOffset + myOffset + 88, value); + buffer.putInt(myOffset + 88, value); } int getGatheredGas() { - int offset = bufferOffset + myOffset + 92; + int offset = myOffset + 92; return buffer.getInt(offset); } void setGatheredGas(int value) { - buffer.putInt(bufferOffset + myOffset + 92, value); + buffer.putInt(myOffset + 92, value); } int getRepairedMinerals() { - int offset = bufferOffset + myOffset + 96; + int offset = myOffset + 96; return buffer.getInt(offset); } void setRepairedMinerals(int value) { - buffer.putInt(bufferOffset + myOffset + 96, value); + buffer.putInt(myOffset + 96, value); } int getRepairedGas() { - int offset = bufferOffset + myOffset + 100; + int offset = myOffset + 100; return buffer.getInt(offset); } void setRepairedGas(int value) { - buffer.putInt(bufferOffset + myOffset + 100, value); + buffer.putInt(myOffset + 100, value); } int getRefundedMinerals() { - int offset = bufferOffset + myOffset + 104; + int offset = myOffset + 104; return buffer.getInt(offset); } void setRefundedMinerals(int value) { - buffer.putInt(bufferOffset + myOffset + 104, value); + buffer.putInt(myOffset + 104, value); } int getRefundedGas() { - int offset = bufferOffset + myOffset + 108; + int offset = myOffset + 108; return buffer.getInt(offset); } void setRefundedGas(int value) { - buffer.putInt(bufferOffset + myOffset + 108, value); + buffer.putInt(myOffset + 108, value); } int getSupplyTotal(int i) { - int offset = bufferOffset + myOffset + 112 + 4 * 1 * i; + int offset = myOffset + 112 + 4 * 1 * i; return buffer.getInt(offset); } void setSupplyTotal(int i, int value) { - buffer.putInt(bufferOffset + myOffset + 112 + 4 * 1 * i, value); + buffer.putInt(myOffset + 112 + 4 * 1 * i, value); } int getSupplyUsed(int i) { - int offset = bufferOffset + myOffset + 124 + 4 * 1 * i; + int offset = myOffset + 124 + 4 * 1 * i; return buffer.getInt(offset); } void setSupplyUsed(int i, int value) { - buffer.putInt(bufferOffset + myOffset + 124 + 4 * 1 * i, value); + buffer.putInt(myOffset + 124 + 4 * 1 * i, value); } int getAllUnitCount(int i) { - int offset = bufferOffset + myOffset + 136 + 4 * 1 * i; + int offset = myOffset + 136 + 4 * 1 * i; return buffer.getInt(offset); } void setAllUnitCount(int i, int value) { - buffer.putInt(bufferOffset + myOffset + 136 + 4 * 1 * i, value); + buffer.putInt(myOffset + 136 + 4 * 1 * i, value); } int getVisibleUnitCount(int i) { - int offset = bufferOffset + myOffset + 1072 + 4 * 1 * i; + int offset = myOffset + 1072 + 4 * 1 * i; return buffer.getInt(offset); } void setVisibleUnitCount(int i, int value) { - buffer.putInt(bufferOffset + myOffset + 1072 + 4 * 1 * i, value); + buffer.putInt(myOffset + 1072 + 4 * 1 * i, value); } int getCompletedUnitCount(int i) { - int offset = bufferOffset + myOffset + 2008 + 4 * 1 * i; + int offset = myOffset + 2008 + 4 * 1 * i; return buffer.getInt(offset); } void setCompletedUnitCount(int i, int value) { - buffer.putInt(bufferOffset + myOffset + 2008 + 4 * 1 * i, value); + buffer.putInt(myOffset + 2008 + 4 * 1 * i, value); } int getDeadUnitCount(int i) { - int offset = bufferOffset + myOffset + 2944 + 4 * 1 * i; + int offset = myOffset + 2944 + 4 * 1 * i; return buffer.getInt(offset); } void setDeadUnitCount(int i, int value) { - buffer.putInt(bufferOffset + myOffset + 2944 + 4 * 1 * i, value); + buffer.putInt(myOffset + 2944 + 4 * 1 * i, value); } int getKilledUnitCount(int i) { - int offset = bufferOffset + myOffset + 3880 + 4 * 1 * i; + int offset = myOffset + 3880 + 4 * 1 * i; return buffer.getInt(offset); } void setKilledUnitCount(int i, int value) { - buffer.putInt(bufferOffset + myOffset + 3880 + 4 * 1 * i, value); + buffer.putInt(myOffset + 3880 + 4 * 1 * i, value); } int getUpgradeLevel(int i) { - int offset = bufferOffset + myOffset + 4816 + 4 * 1 * i; + int offset = myOffset + 4816 + 4 * 1 * i; return buffer.getInt(offset); } void setUpgradeLevel(int i, int value) { - buffer.putInt(bufferOffset + myOffset + 4816 + 4 * 1 * i, value); + buffer.putInt(myOffset + 4816 + 4 * 1 * i, value); } boolean getHasResearched(int i) { - int offset = bufferOffset + myOffset + 5068 + 1 * 1 * i; + int offset = myOffset + 5068 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setHasResearched(int i, boolean value) { - buffer.putByte(bufferOffset + myOffset + 5068 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 5068 + 1 * 1 * i, (byte) (value ? 1 : 0)); } boolean isResearching(int i) { - int offset = bufferOffset + myOffset + 5115 + 1 * 1 * i; + int offset = myOffset + 5115 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setIsResearching(int i, boolean value) { - buffer.putByte(bufferOffset + myOffset + 5115 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 5115 + 1 * 1 * i, (byte) (value ? 1 : 0)); } boolean isUpgrading(int i) { - int offset = bufferOffset + myOffset + 5162 + 1 * 1 * i; + int offset = myOffset + 5162 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setIsUpgrading(int i, boolean value) { - buffer.putByte(bufferOffset + myOffset + 5162 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 5162 + 1 * 1 * i, (byte) (value ? 1 : 0)); } int getColor() { - int offset = bufferOffset + myOffset + 5228; + int offset = myOffset + 5228; return buffer.getInt(offset); } void setColor(int value) { - buffer.putInt(bufferOffset + myOffset + 5228, value); + buffer.putInt(myOffset + 5228, value); } int getTotalUnitScore() { - int offset = bufferOffset + myOffset + 5232; + int offset = myOffset + 5232; return buffer.getInt(offset); } void setTotalUnitScore(int value) { - buffer.putInt(bufferOffset + myOffset + 5232, value); + buffer.putInt(myOffset + 5232, value); } int getTotalKillScore() { - int offset = bufferOffset + myOffset + 5236; + int offset = myOffset + 5236; return buffer.getInt(offset); } void setTotalKillScore(int value) { - buffer.putInt(bufferOffset + myOffset + 5236, value); + buffer.putInt(myOffset + 5236, value); } int getTotalBuildingScore() { - int offset = bufferOffset + myOffset + 5240; + int offset = myOffset + 5240; return buffer.getInt(offset); } void setTotalBuildingScore(int value) { - buffer.putInt(bufferOffset + myOffset + 5240, value); + buffer.putInt(myOffset + 5240, value); } int getTotalRazingScore() { - int offset = bufferOffset + myOffset + 5244; + int offset = myOffset + 5244; return buffer.getInt(offset); } void setTotalRazingScore(int value) { - buffer.putInt(bufferOffset + myOffset + 5244, value); + buffer.putInt(myOffset + 5244, value); } int getCustomScore() { - int offset = bufferOffset + myOffset + 5248; + int offset = myOffset + 5248; return buffer.getInt(offset); } void setCustomScore(int value) { - buffer.putInt(bufferOffset + myOffset + 5248, value); + buffer.putInt(myOffset + 5248, value); } int getMaxUpgradeLevel(int i) { - int offset = bufferOffset + myOffset + 5252 + 4 * 1 * i; + int offset = myOffset + 5252 + 4 * 1 * i; return buffer.getInt(offset); } void setMaxUpgradeLevel(int i, int value) { - buffer.putInt(bufferOffset + myOffset + 5252 + 4 * 1 * i, value); + buffer.putInt(myOffset + 5252 + 4 * 1 * i, value); } boolean isResearchAvailable(int i) { - int offset = bufferOffset + myOffset + 5504 + 1 * 1 * i; + int offset = myOffset + 5504 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setIsResearchAvailable(int i, boolean value) { - buffer.putByte(bufferOffset + myOffset + 5504 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 5504 + 1 * 1 * i, (byte) (value ? 1 : 0)); } boolean isUnitAvailable(int i) { - int offset = bufferOffset + myOffset + 5551 + 1 * 1 * i; + int offset = myOffset + 5551 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setIsUnitAvailable(int i, boolean value) { - buffer.putByte(bufferOffset + myOffset + 5551 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 5551 + 1 * 1 * i, (byte) (value ? 1 : 0)); } } class BulletData { @@ -1181,109 +1184,109 @@ public BulletData(int myOffset) { this.myOffset = myOffset; } int getId() { - int offset = bufferOffset + myOffset + 0; + int offset = myOffset + 0; return buffer.getInt(offset); } void setId(int value) { - buffer.putInt(bufferOffset + myOffset + 0, value); + buffer.putInt(myOffset + 0, value); } int getPlayer() { - int offset = bufferOffset + myOffset + 4; + int offset = myOffset + 4; return buffer.getInt(offset); } void setPlayer(int value) { - buffer.putInt(bufferOffset + myOffset + 4, value); + buffer.putInt(myOffset + 4, value); } int getType() { - int offset = bufferOffset + myOffset + 8; + int offset = myOffset + 8; return buffer.getInt(offset); } void setType(int value) { - buffer.putInt(bufferOffset + myOffset + 8, value); + buffer.putInt(myOffset + 8, value); } int getSource() { - int offset = bufferOffset + myOffset + 12; + int offset = myOffset + 12; return buffer.getInt(offset); } void setSource(int value) { - buffer.putInt(bufferOffset + myOffset + 12, value); + buffer.putInt(myOffset + 12, value); } int getPositionX() { - int offset = bufferOffset + myOffset + 16; + int offset = myOffset + 16; return buffer.getInt(offset); } void setPositionX(int value) { - buffer.putInt(bufferOffset + myOffset + 16, value); + buffer.putInt(myOffset + 16, value); } int getPositionY() { - int offset = bufferOffset + myOffset + 20; + int offset = myOffset + 20; return buffer.getInt(offset); } void setPositionY(int value) { - buffer.putInt(bufferOffset + myOffset + 20, value); + buffer.putInt(myOffset + 20, value); } double getAngle() { - int offset = bufferOffset + myOffset + 24; + int offset = myOffset + 24; return buffer.getDouble(offset); } void setAngle(double value) { - buffer.putDouble(bufferOffset + myOffset + 24, value); + buffer.putDouble(myOffset + 24, value); } double getVelocityX() { - int offset = bufferOffset + myOffset + 32; + int offset = myOffset + 32; return buffer.getDouble(offset); } void setVelocityX(double value) { - buffer.putDouble(bufferOffset + myOffset + 32, value); + buffer.putDouble(myOffset + 32, value); } double getVelocityY() { - int offset = bufferOffset + myOffset + 40; + int offset = myOffset + 40; return buffer.getDouble(offset); } void setVelocityY(double value) { - buffer.putDouble(bufferOffset + myOffset + 40, value); + buffer.putDouble(myOffset + 40, value); } int getTarget() { - int offset = bufferOffset + myOffset + 48; + int offset = myOffset + 48; return buffer.getInt(offset); } void setTarget(int value) { - buffer.putInt(bufferOffset + myOffset + 48, value); + buffer.putInt(myOffset + 48, value); } int getTargetPositionX() { - int offset = bufferOffset + myOffset + 52; + int offset = myOffset + 52; return buffer.getInt(offset); } void setTargetPositionX(int value) { - buffer.putInt(bufferOffset + myOffset + 52, value); + buffer.putInt(myOffset + 52, value); } int getTargetPositionY() { - int offset = bufferOffset + myOffset + 56; + int offset = myOffset + 56; return buffer.getInt(offset); } void setTargetPositionY(int value) { - buffer.putInt(bufferOffset + myOffset + 56, value); + buffer.putInt(myOffset + 56, value); } int getRemoveTimer() { - int offset = bufferOffset + myOffset + 60; + int offset = myOffset + 60; return buffer.getInt(offset); } void setRemoveTimer(int value) { - buffer.putInt(bufferOffset + myOffset + 60, value); + buffer.putInt(myOffset + 60, value); } boolean getExists() { - int offset = bufferOffset + myOffset + 64; + int offset = myOffset + 64; return buffer.getByte(offset) != 0; } void setExists(boolean value) { - buffer.putByte(bufferOffset + myOffset + 64, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 64, (byte) (value ? 1 : 0)); } boolean isVisible(int i) { - int offset = bufferOffset + myOffset + 65 + 1 * 1 * i; + int offset = myOffset + 65 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setIsVisible(int i, boolean value) { - buffer.putByte(bufferOffset + myOffset + 65 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 65 + 1 * 1 * i, (byte) (value ? 1 : 0)); } } class unitFinder { @@ -1293,18 +1296,18 @@ public unitFinder(int myOffset) { this.myOffset = myOffset; } int getUnitIndex() { - int offset = bufferOffset + myOffset + 0; + int offset = myOffset + 0; return buffer.getInt(offset); } void setUnitIndex(int value) { - buffer.putInt(bufferOffset + myOffset + 0, value); + buffer.putInt(myOffset + 0, value); } int getSearchValue() { - int offset = bufferOffset + myOffset + 4; + int offset = myOffset + 4; return buffer.getInt(offset); } void setSearchValue(int value) { - buffer.putInt(bufferOffset + myOffset + 4, value); + buffer.putInt(myOffset + 4, value); } } class UnitData { @@ -1314,683 +1317,683 @@ public UnitData(int myOffset) { this.myOffset = myOffset; } int getClearanceLevel() { - int offset = bufferOffset + myOffset + 0; + int offset = myOffset + 0; return buffer.getInt(offset); } void setClearanceLevel(int value) { - buffer.putInt(bufferOffset + myOffset + 0, value); + buffer.putInt(myOffset + 0, value); } int getId() { - int offset = bufferOffset + myOffset + 4; + int offset = myOffset + 4; return buffer.getInt(offset); } void setId(int value) { - buffer.putInt(bufferOffset + myOffset + 4, value); + buffer.putInt(myOffset + 4, value); } int getPlayer() { - int offset = bufferOffset + myOffset + 8; + int offset = myOffset + 8; return buffer.getInt(offset); } void setPlayer(int value) { - buffer.putInt(bufferOffset + myOffset + 8, value); + buffer.putInt(myOffset + 8, value); } int getType() { - int offset = bufferOffset + myOffset + 12; + int offset = myOffset + 12; return buffer.getInt(offset); } void setType(int value) { - buffer.putInt(bufferOffset + myOffset + 12, value); + buffer.putInt(myOffset + 12, value); } int getPositionX() { - int offset = bufferOffset + myOffset + 16; + int offset = myOffset + 16; return buffer.getInt(offset); } void setPositionX(int value) { - buffer.putInt(bufferOffset + myOffset + 16, value); + buffer.putInt(myOffset + 16, value); } int getPositionY() { - int offset = bufferOffset + myOffset + 20; + int offset = myOffset + 20; return buffer.getInt(offset); } void setPositionY(int value) { - buffer.putInt(bufferOffset + myOffset + 20, value); + buffer.putInt(myOffset + 20, value); } double getAngle() { - int offset = bufferOffset + myOffset + 24; + int offset = myOffset + 24; return buffer.getDouble(offset); } void setAngle(double value) { - buffer.putDouble(bufferOffset + myOffset + 24, value); + buffer.putDouble(myOffset + 24, value); } double getVelocityX() { - int offset = bufferOffset + myOffset + 32; + int offset = myOffset + 32; return buffer.getDouble(offset); } void setVelocityX(double value) { - buffer.putDouble(bufferOffset + myOffset + 32, value); + buffer.putDouble(myOffset + 32, value); } double getVelocityY() { - int offset = bufferOffset + myOffset + 40; + int offset = myOffset + 40; return buffer.getDouble(offset); } void setVelocityY(double value) { - buffer.putDouble(bufferOffset + myOffset + 40, value); + buffer.putDouble(myOffset + 40, value); } int getHitPoints() { - int offset = bufferOffset + myOffset + 48; + int offset = myOffset + 48; return buffer.getInt(offset); } void setHitPoints(int value) { - buffer.putInt(bufferOffset + myOffset + 48, value); + buffer.putInt(myOffset + 48, value); } int getLastHitPoints() { - int offset = bufferOffset + myOffset + 52; + int offset = myOffset + 52; return buffer.getInt(offset); } void setLastHitPoints(int value) { - buffer.putInt(bufferOffset + myOffset + 52, value); + buffer.putInt(myOffset + 52, value); } int getShields() { - int offset = bufferOffset + myOffset + 56; + int offset = myOffset + 56; return buffer.getInt(offset); } void setShields(int value) { - buffer.putInt(bufferOffset + myOffset + 56, value); + buffer.putInt(myOffset + 56, value); } int getEnergy() { - int offset = bufferOffset + myOffset + 60; + int offset = myOffset + 60; return buffer.getInt(offset); } void setEnergy(int value) { - buffer.putInt(bufferOffset + myOffset + 60, value); + buffer.putInt(myOffset + 60, value); } int getResources() { - int offset = bufferOffset + myOffset + 64; + int offset = myOffset + 64; return buffer.getInt(offset); } void setResources(int value) { - buffer.putInt(bufferOffset + myOffset + 64, value); + buffer.putInt(myOffset + 64, value); } int getResourceGroup() { - int offset = bufferOffset + myOffset + 68; + int offset = myOffset + 68; return buffer.getInt(offset); } void setResourceGroup(int value) { - buffer.putInt(bufferOffset + myOffset + 68, value); + buffer.putInt(myOffset + 68, value); } int getKillCount() { - int offset = bufferOffset + myOffset + 72; + int offset = myOffset + 72; return buffer.getInt(offset); } void setKillCount(int value) { - buffer.putInt(bufferOffset + myOffset + 72, value); + buffer.putInt(myOffset + 72, value); } int getAcidSporeCount() { - int offset = bufferOffset + myOffset + 76; + int offset = myOffset + 76; return buffer.getInt(offset); } void setAcidSporeCount(int value) { - buffer.putInt(bufferOffset + myOffset + 76, value); + buffer.putInt(myOffset + 76, value); } int getScarabCount() { - int offset = bufferOffset + myOffset + 80; + int offset = myOffset + 80; return buffer.getInt(offset); } void setScarabCount(int value) { - buffer.putInt(bufferOffset + myOffset + 80, value); + buffer.putInt(myOffset + 80, value); } int getInterceptorCount() { - int offset = bufferOffset + myOffset + 84; + int offset = myOffset + 84; return buffer.getInt(offset); } void setInterceptorCount(int value) { - buffer.putInt(bufferOffset + myOffset + 84, value); + buffer.putInt(myOffset + 84, value); } int getSpiderMineCount() { - int offset = bufferOffset + myOffset + 88; + int offset = myOffset + 88; return buffer.getInt(offset); } void setSpiderMineCount(int value) { - buffer.putInt(bufferOffset + myOffset + 88, value); + buffer.putInt(myOffset + 88, value); } int getGroundWeaponCooldown() { - int offset = bufferOffset + myOffset + 92; + int offset = myOffset + 92; return buffer.getInt(offset); } void setGroundWeaponCooldown(int value) { - buffer.putInt(bufferOffset + myOffset + 92, value); + buffer.putInt(myOffset + 92, value); } int getAirWeaponCooldown() { - int offset = bufferOffset + myOffset + 96; + int offset = myOffset + 96; return buffer.getInt(offset); } void setAirWeaponCooldown(int value) { - buffer.putInt(bufferOffset + myOffset + 96, value); + buffer.putInt(myOffset + 96, value); } int getSpellCooldown() { - int offset = bufferOffset + myOffset + 100; + int offset = myOffset + 100; return buffer.getInt(offset); } void setSpellCooldown(int value) { - buffer.putInt(bufferOffset + myOffset + 100, value); + buffer.putInt(myOffset + 100, value); } int getDefenseMatrixPoints() { - int offset = bufferOffset + myOffset + 104; + int offset = myOffset + 104; return buffer.getInt(offset); } void setDefenseMatrixPoints(int value) { - buffer.putInt(bufferOffset + myOffset + 104, value); + buffer.putInt(myOffset + 104, value); } int getDefenseMatrixTimer() { - int offset = bufferOffset + myOffset + 108; + int offset = myOffset + 108; return buffer.getInt(offset); } void setDefenseMatrixTimer(int value) { - buffer.putInt(bufferOffset + myOffset + 108, value); + buffer.putInt(myOffset + 108, value); } int getEnsnareTimer() { - int offset = bufferOffset + myOffset + 112; + int offset = myOffset + 112; return buffer.getInt(offset); } void setEnsnareTimer(int value) { - buffer.putInt(bufferOffset + myOffset + 112, value); + buffer.putInt(myOffset + 112, value); } int getIrradiateTimer() { - int offset = bufferOffset + myOffset + 116; + int offset = myOffset + 116; return buffer.getInt(offset); } void setIrradiateTimer(int value) { - buffer.putInt(bufferOffset + myOffset + 116, value); + buffer.putInt(myOffset + 116, value); } int getLockdownTimer() { - int offset = bufferOffset + myOffset + 120; + int offset = myOffset + 120; return buffer.getInt(offset); } void setLockdownTimer(int value) { - buffer.putInt(bufferOffset + myOffset + 120, value); + buffer.putInt(myOffset + 120, value); } int getMaelstromTimer() { - int offset = bufferOffset + myOffset + 124; + int offset = myOffset + 124; return buffer.getInt(offset); } void setMaelstromTimer(int value) { - buffer.putInt(bufferOffset + myOffset + 124, value); + buffer.putInt(myOffset + 124, value); } int getOrderTimer() { - int offset = bufferOffset + myOffset + 128; + int offset = myOffset + 128; return buffer.getInt(offset); } void setOrderTimer(int value) { - buffer.putInt(bufferOffset + myOffset + 128, value); + buffer.putInt(myOffset + 128, value); } int getPlagueTimer() { - int offset = bufferOffset + myOffset + 132; + int offset = myOffset + 132; return buffer.getInt(offset); } void setPlagueTimer(int value) { - buffer.putInt(bufferOffset + myOffset + 132, value); + buffer.putInt(myOffset + 132, value); } int getRemoveTimer() { - int offset = bufferOffset + myOffset + 136; + int offset = myOffset + 136; return buffer.getInt(offset); } void setRemoveTimer(int value) { - buffer.putInt(bufferOffset + myOffset + 136, value); + buffer.putInt(myOffset + 136, value); } int getStasisTimer() { - int offset = bufferOffset + myOffset + 140; + int offset = myOffset + 140; return buffer.getInt(offset); } void setStasisTimer(int value) { - buffer.putInt(bufferOffset + myOffset + 140, value); + buffer.putInt(myOffset + 140, value); } int getStimTimer() { - int offset = bufferOffset + myOffset + 144; + int offset = myOffset + 144; return buffer.getInt(offset); } void setStimTimer(int value) { - buffer.putInt(bufferOffset + myOffset + 144, value); + buffer.putInt(myOffset + 144, value); } int getBuildType() { - int offset = bufferOffset + myOffset + 148; + int offset = myOffset + 148; return buffer.getInt(offset); } void setBuildType(int value) { - buffer.putInt(bufferOffset + myOffset + 148, value); + buffer.putInt(myOffset + 148, value); } int getTrainingQueueCount() { - int offset = bufferOffset + myOffset + 152; + int offset = myOffset + 152; return buffer.getInt(offset); } void setTrainingQueueCount(int value) { - buffer.putInt(bufferOffset + myOffset + 152, value); + buffer.putInt(myOffset + 152, value); } int getTrainingQueue(int i) { - int offset = bufferOffset + myOffset + 156 + 4 * 1 * i; + int offset = myOffset + 156 + 4 * 1 * i; return buffer.getInt(offset); } void setTrainingQueue(int i, int value) { - buffer.putInt(bufferOffset + myOffset + 156 + 4 * 1 * i, value); + buffer.putInt(myOffset + 156 + 4 * 1 * i, value); } int getTech() { - int offset = bufferOffset + myOffset + 176; + int offset = myOffset + 176; return buffer.getInt(offset); } void setTech(int value) { - buffer.putInt(bufferOffset + myOffset + 176, value); + buffer.putInt(myOffset + 176, value); } int getUpgrade() { - int offset = bufferOffset + myOffset + 180; + int offset = myOffset + 180; return buffer.getInt(offset); } void setUpgrade(int value) { - buffer.putInt(bufferOffset + myOffset + 180, value); + buffer.putInt(myOffset + 180, value); } int getRemainingBuildTime() { - int offset = bufferOffset + myOffset + 184; + int offset = myOffset + 184; return buffer.getInt(offset); } void setRemainingBuildTime(int value) { - buffer.putInt(bufferOffset + myOffset + 184, value); + buffer.putInt(myOffset + 184, value); } int getRemainingTrainTime() { - int offset = bufferOffset + myOffset + 188; + int offset = myOffset + 188; return buffer.getInt(offset); } void setRemainingTrainTime(int value) { - buffer.putInt(bufferOffset + myOffset + 188, value); + buffer.putInt(myOffset + 188, value); } int getRemainingResearchTime() { - int offset = bufferOffset + myOffset + 192; + int offset = myOffset + 192; return buffer.getInt(offset); } void setRemainingResearchTime(int value) { - buffer.putInt(bufferOffset + myOffset + 192, value); + buffer.putInt(myOffset + 192, value); } int getRemainingUpgradeTime() { - int offset = bufferOffset + myOffset + 196; + int offset = myOffset + 196; return buffer.getInt(offset); } void setRemainingUpgradeTime(int value) { - buffer.putInt(bufferOffset + myOffset + 196, value); + buffer.putInt(myOffset + 196, value); } int getBuildUnit() { - int offset = bufferOffset + myOffset + 200; + int offset = myOffset + 200; return buffer.getInt(offset); } void setBuildUnit(int value) { - buffer.putInt(bufferOffset + myOffset + 200, value); + buffer.putInt(myOffset + 200, value); } int getTarget() { - int offset = bufferOffset + myOffset + 204; + int offset = myOffset + 204; return buffer.getInt(offset); } void setTarget(int value) { - buffer.putInt(bufferOffset + myOffset + 204, value); + buffer.putInt(myOffset + 204, value); } int getTargetPositionX() { - int offset = bufferOffset + myOffset + 208; + int offset = myOffset + 208; return buffer.getInt(offset); } void setTargetPositionX(int value) { - buffer.putInt(bufferOffset + myOffset + 208, value); + buffer.putInt(myOffset + 208, value); } int getTargetPositionY() { - int offset = bufferOffset + myOffset + 212; + int offset = myOffset + 212; return buffer.getInt(offset); } void setTargetPositionY(int value) { - buffer.putInt(bufferOffset + myOffset + 212, value); + buffer.putInt(myOffset + 212, value); } int getOrder() { - int offset = bufferOffset + myOffset + 216; + int offset = myOffset + 216; return buffer.getInt(offset); } void setOrder(int value) { - buffer.putInt(bufferOffset + myOffset + 216, value); + buffer.putInt(myOffset + 216, value); } int getOrderTarget() { - int offset = bufferOffset + myOffset + 220; + int offset = myOffset + 220; return buffer.getInt(offset); } void setOrderTarget(int value) { - buffer.putInt(bufferOffset + myOffset + 220, value); + buffer.putInt(myOffset + 220, value); } int getOrderTargetPositionX() { - int offset = bufferOffset + myOffset + 224; + int offset = myOffset + 224; return buffer.getInt(offset); } void setOrderTargetPositionX(int value) { - buffer.putInt(bufferOffset + myOffset + 224, value); + buffer.putInt(myOffset + 224, value); } int getOrderTargetPositionY() { - int offset = bufferOffset + myOffset + 228; + int offset = myOffset + 228; return buffer.getInt(offset); } void setOrderTargetPositionY(int value) { - buffer.putInt(bufferOffset + myOffset + 228, value); + buffer.putInt(myOffset + 228, value); } int getSecondaryOrder() { - int offset = bufferOffset + myOffset + 232; + int offset = myOffset + 232; return buffer.getInt(offset); } void setSecondaryOrder(int value) { - buffer.putInt(bufferOffset + myOffset + 232, value); + buffer.putInt(myOffset + 232, value); } int getRallyPositionX() { - int offset = bufferOffset + myOffset + 236; + int offset = myOffset + 236; return buffer.getInt(offset); } void setRallyPositionX(int value) { - buffer.putInt(bufferOffset + myOffset + 236, value); + buffer.putInt(myOffset + 236, value); } int getRallyPositionY() { - int offset = bufferOffset + myOffset + 240; + int offset = myOffset + 240; return buffer.getInt(offset); } void setRallyPositionY(int value) { - buffer.putInt(bufferOffset + myOffset + 240, value); + buffer.putInt(myOffset + 240, value); } int getRallyUnit() { - int offset = bufferOffset + myOffset + 244; + int offset = myOffset + 244; return buffer.getInt(offset); } void setRallyUnit(int value) { - buffer.putInt(bufferOffset + myOffset + 244, value); + buffer.putInt(myOffset + 244, value); } int getAddon() { - int offset = bufferOffset + myOffset + 248; + int offset = myOffset + 248; return buffer.getInt(offset); } void setAddon(int value) { - buffer.putInt(bufferOffset + myOffset + 248, value); + buffer.putInt(myOffset + 248, value); } int getNydusExit() { - int offset = bufferOffset + myOffset + 252; + int offset = myOffset + 252; return buffer.getInt(offset); } void setNydusExit(int value) { - buffer.putInt(bufferOffset + myOffset + 252, value); + buffer.putInt(myOffset + 252, value); } int getPowerUp() { - int offset = bufferOffset + myOffset + 256; + int offset = myOffset + 256; return buffer.getInt(offset); } void setPowerUp(int value) { - buffer.putInt(bufferOffset + myOffset + 256, value); + buffer.putInt(myOffset + 256, value); } int getTransport() { - int offset = bufferOffset + myOffset + 260; + int offset = myOffset + 260; return buffer.getInt(offset); } void setTransport(int value) { - buffer.putInt(bufferOffset + myOffset + 260, value); + buffer.putInt(myOffset + 260, value); } int getCarrier() { - int offset = bufferOffset + myOffset + 264; + int offset = myOffset + 264; return buffer.getInt(offset); } void setCarrier(int value) { - buffer.putInt(bufferOffset + myOffset + 264, value); + buffer.putInt(myOffset + 264, value); } int getHatchery() { - int offset = bufferOffset + myOffset + 268; + int offset = myOffset + 268; return buffer.getInt(offset); } void setHatchery(int value) { - buffer.putInt(bufferOffset + myOffset + 268, value); + buffer.putInt(myOffset + 268, value); } boolean getExists() { - int offset = bufferOffset + myOffset + 272; + int offset = myOffset + 272; return buffer.getByte(offset) != 0; } void setExists(boolean value) { - buffer.putByte(bufferOffset + myOffset + 272, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 272, (byte) (value ? 1 : 0)); } boolean getHasNuke() { - int offset = bufferOffset + myOffset + 273; + int offset = myOffset + 273; return buffer.getByte(offset) != 0; } void setHasNuke(boolean value) { - buffer.putByte(bufferOffset + myOffset + 273, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 273, (byte) (value ? 1 : 0)); } boolean isAccelerating() { - int offset = bufferOffset + myOffset + 274; + int offset = myOffset + 274; return buffer.getByte(offset) != 0; } void setIsAccelerating(boolean value) { - buffer.putByte(bufferOffset + myOffset + 274, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 274, (byte) (value ? 1 : 0)); } boolean isAttacking() { - int offset = bufferOffset + myOffset + 275; + int offset = myOffset + 275; return buffer.getByte(offset) != 0; } void setIsAttacking(boolean value) { - buffer.putByte(bufferOffset + myOffset + 275, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 275, (byte) (value ? 1 : 0)); } boolean isAttackFrame() { - int offset = bufferOffset + myOffset + 276; + int offset = myOffset + 276; return buffer.getByte(offset) != 0; } void setIsAttackFrame(boolean value) { - buffer.putByte(bufferOffset + myOffset + 276, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 276, (byte) (value ? 1 : 0)); } boolean isBeingGathered() { - int offset = bufferOffset + myOffset + 277; + int offset = myOffset + 277; return buffer.getByte(offset) != 0; } void setIsBeingGathered(boolean value) { - buffer.putByte(bufferOffset + myOffset + 277, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 277, (byte) (value ? 1 : 0)); } boolean isBlind() { - int offset = bufferOffset + myOffset + 278; + int offset = myOffset + 278; return buffer.getByte(offset) != 0; } void setIsBlind(boolean value) { - buffer.putByte(bufferOffset + myOffset + 278, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 278, (byte) (value ? 1 : 0)); } boolean isBraking() { - int offset = bufferOffset + myOffset + 279; + int offset = myOffset + 279; return buffer.getByte(offset) != 0; } void setIsBraking(boolean value) { - buffer.putByte(bufferOffset + myOffset + 279, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 279, (byte) (value ? 1 : 0)); } boolean isBurrowed() { - int offset = bufferOffset + myOffset + 280; + int offset = myOffset + 280; return buffer.getByte(offset) != 0; } void setIsBurrowed(boolean value) { - buffer.putByte(bufferOffset + myOffset + 280, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 280, (byte) (value ? 1 : 0)); } int getCarryResourceType() { - int offset = bufferOffset + myOffset + 284; + int offset = myOffset + 284; return buffer.getInt(offset); } void setCarryResourceType(int value) { - buffer.putInt(bufferOffset + myOffset + 284, value); + buffer.putInt(myOffset + 284, value); } boolean isCloaked() { - int offset = bufferOffset + myOffset + 288; + int offset = myOffset + 288; return buffer.getByte(offset) != 0; } void setIsCloaked(boolean value) { - buffer.putByte(bufferOffset + myOffset + 288, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 288, (byte) (value ? 1 : 0)); } boolean isCompleted() { - int offset = bufferOffset + myOffset + 289; + int offset = myOffset + 289; return buffer.getByte(offset) != 0; } void setIsCompleted(boolean value) { - buffer.putByte(bufferOffset + myOffset + 289, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 289, (byte) (value ? 1 : 0)); } boolean isConstructing() { - int offset = bufferOffset + myOffset + 290; + int offset = myOffset + 290; return buffer.getByte(offset) != 0; } void setIsConstructing(boolean value) { - buffer.putByte(bufferOffset + myOffset + 290, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 290, (byte) (value ? 1 : 0)); } boolean isDetected() { - int offset = bufferOffset + myOffset + 291; + int offset = myOffset + 291; return buffer.getByte(offset) != 0; } void setIsDetected(boolean value) { - buffer.putByte(bufferOffset + myOffset + 291, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 291, (byte) (value ? 1 : 0)); } boolean isGathering() { - int offset = bufferOffset + myOffset + 292; + int offset = myOffset + 292; return buffer.getByte(offset) != 0; } void setIsGathering(boolean value) { - buffer.putByte(bufferOffset + myOffset + 292, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 292, (byte) (value ? 1 : 0)); } boolean isHallucination() { - int offset = bufferOffset + myOffset + 293; + int offset = myOffset + 293; return buffer.getByte(offset) != 0; } void setIsHallucination(boolean value) { - buffer.putByte(bufferOffset + myOffset + 293, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 293, (byte) (value ? 1 : 0)); } boolean isIdle() { - int offset = bufferOffset + myOffset + 294; + int offset = myOffset + 294; return buffer.getByte(offset) != 0; } void setIsIdle(boolean value) { - buffer.putByte(bufferOffset + myOffset + 294, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 294, (byte) (value ? 1 : 0)); } boolean isInterruptible() { - int offset = bufferOffset + myOffset + 295; + int offset = myOffset + 295; return buffer.getByte(offset) != 0; } void setIsInterruptible(boolean value) { - buffer.putByte(bufferOffset + myOffset + 295, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 295, (byte) (value ? 1 : 0)); } boolean isInvincible() { - int offset = bufferOffset + myOffset + 296; + int offset = myOffset + 296; return buffer.getByte(offset) != 0; } void setIsInvincible(boolean value) { - buffer.putByte(bufferOffset + myOffset + 296, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 296, (byte) (value ? 1 : 0)); } boolean isLifted() { - int offset = bufferOffset + myOffset + 297; + int offset = myOffset + 297; return buffer.getByte(offset) != 0; } void setIsLifted(boolean value) { - buffer.putByte(bufferOffset + myOffset + 297, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 297, (byte) (value ? 1 : 0)); } boolean isMorphing() { - int offset = bufferOffset + myOffset + 298; + int offset = myOffset + 298; return buffer.getByte(offset) != 0; } void setIsMorphing(boolean value) { - buffer.putByte(bufferOffset + myOffset + 298, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 298, (byte) (value ? 1 : 0)); } boolean isMoving() { - int offset = bufferOffset + myOffset + 299; + int offset = myOffset + 299; return buffer.getByte(offset) != 0; } void setIsMoving(boolean value) { - buffer.putByte(bufferOffset + myOffset + 299, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 299, (byte) (value ? 1 : 0)); } boolean isParasited() { - int offset = bufferOffset + myOffset + 300; + int offset = myOffset + 300; return buffer.getByte(offset) != 0; } void setIsParasited(boolean value) { - buffer.putByte(bufferOffset + myOffset + 300, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 300, (byte) (value ? 1 : 0)); } boolean isSelected() { - int offset = bufferOffset + myOffset + 301; + int offset = myOffset + 301; return buffer.getByte(offset) != 0; } void setIsSelected(boolean value) { - buffer.putByte(bufferOffset + myOffset + 301, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 301, (byte) (value ? 1 : 0)); } boolean isStartingAttack() { - int offset = bufferOffset + myOffset + 302; + int offset = myOffset + 302; return buffer.getByte(offset) != 0; } void setIsStartingAttack(boolean value) { - buffer.putByte(bufferOffset + myOffset + 302, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 302, (byte) (value ? 1 : 0)); } boolean isStuck() { - int offset = bufferOffset + myOffset + 303; + int offset = myOffset + 303; return buffer.getByte(offset) != 0; } void setIsStuck(boolean value) { - buffer.putByte(bufferOffset + myOffset + 303, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 303, (byte) (value ? 1 : 0)); } boolean isTraining() { - int offset = bufferOffset + myOffset + 304; + int offset = myOffset + 304; return buffer.getByte(offset) != 0; } void setIsTraining(boolean value) { - buffer.putByte(bufferOffset + myOffset + 304, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 304, (byte) (value ? 1 : 0)); } boolean isUnderStorm() { - int offset = bufferOffset + myOffset + 305; + int offset = myOffset + 305; return buffer.getByte(offset) != 0; } void setIsUnderStorm(boolean value) { - buffer.putByte(bufferOffset + myOffset + 305, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 305, (byte) (value ? 1 : 0)); } boolean isUnderDarkSwarm() { - int offset = bufferOffset + myOffset + 306; + int offset = myOffset + 306; return buffer.getByte(offset) != 0; } void setIsUnderDarkSwarm(boolean value) { - buffer.putByte(bufferOffset + myOffset + 306, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 306, (byte) (value ? 1 : 0)); } boolean isUnderDWeb() { - int offset = bufferOffset + myOffset + 307; + int offset = myOffset + 307; return buffer.getByte(offset) != 0; } void setIsUnderDWeb(boolean value) { - buffer.putByte(bufferOffset + myOffset + 307, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 307, (byte) (value ? 1 : 0)); } boolean isPowered() { - int offset = bufferOffset + myOffset + 308; + int offset = myOffset + 308; return buffer.getByte(offset) != 0; } void setIsPowered(boolean value) { - buffer.putByte(bufferOffset + myOffset + 308, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 308, (byte) (value ? 1 : 0)); } boolean isVisible(int i) { - int offset = bufferOffset + myOffset + 309 + 1 * 1 * i; + int offset = myOffset + 309 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setIsVisible(int i, boolean value) { - buffer.putByte(bufferOffset + myOffset + 309 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 309 + 1 * 1 * i, (byte) (value ? 1 : 0)); } int getButtonset() { - int offset = bufferOffset + myOffset + 320; + int offset = myOffset + 320; return buffer.getInt(offset); } void setButtonset(int value) { - buffer.putInt(bufferOffset + myOffset + 320, value); + buffer.putInt(myOffset + 320, value); } int getLastAttackerPlayer() { - int offset = bufferOffset + myOffset + 324; + int offset = myOffset + 324; return buffer.getInt(offset); } void setLastAttackerPlayer(int value) { - buffer.putInt(bufferOffset + myOffset + 324, value); + buffer.putInt(myOffset + 324, value); } boolean getRecentlyAttacked() { - int offset = bufferOffset + myOffset + 328; + int offset = myOffset + 328; return buffer.getByte(offset) != 0; } void setRecentlyAttacked(boolean value) { - buffer.putByte(bufferOffset + myOffset + 328, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 328, (byte) (value ? 1 : 0)); } int getReplayID() { - int offset = bufferOffset + myOffset + 332; + int offset = myOffset + 332; return buffer.getInt(offset); } void setReplayID(int value) { - buffer.putInt(bufferOffset + myOffset + 332, value); + buffer.putInt(myOffset + 332, value); } } } diff --git a/src/main/java/bwapi/EventHandler.java b/src/main/java/bwapi/EventHandler.java index 495f445e..9f5ecd9d 100644 --- a/src/main/java/bwapi/EventHandler.java +++ b/src/main/java/bwapi/EventHandler.java @@ -1,19 +1,7 @@ package bwapi; - -class EventHandler implements Client.EventHandler { - private final BWEventListener eventListener; - private final Game game; - private final Client client; - - EventHandler(final BWEventListener eventListener, final Client client) { - this.eventListener = eventListener; - this.game = new Game(client); - this.client = client; - } - - @Override - public void operation(final ClientData.Event event) { +class EventHandler { + static void operation(BWEventListener eventListener, Game game, final ClientData.Event event) { final Unit u; final int frames = game.getFrameCount(); switch (event.getType()) { @@ -30,10 +18,10 @@ public void operation(final ClientData.Event event) { break; //case 3: //MenuFrame case SendText: - eventListener.onSendText(GameDataUtils.eventString(client.gameData(), event.getV1())); + eventListener.onSendText(game.clientData().gameData().getEventStrings(event.getV1())); break; case ReceiveText: - eventListener.onReceiveText(game.getPlayer(event.getV1()), GameDataUtils.eventString(client.gameData(), event.getV2())); + eventListener.onReceiveText(game.getPlayer(event.getV1()), game.clientData().gameData().getEventStrings(event.getV2())); break; case PlayerLeft: eventListener.onPlayerLeft(game.getPlayer(event.getV1())); @@ -42,7 +30,7 @@ public void operation(final ClientData.Event event) { eventListener.onNukeDetect(new Position(event.getV1(), event.getV2())); break; case SaveGame: - eventListener.onSaveGame(GameDataUtils.eventString(client.gameData(), event.getV1())); + eventListener.onSaveGame(game.clientData().gameData().getEventStrings(event.getV1())); break; case UnitDiscover: game.unitCreate(event.getV1()); @@ -93,8 +81,4 @@ public void operation(final ClientData.Event event) { break; } } - - public Game getGame() { - return game; - } } \ No newline at end of file diff --git a/src/main/java/bwapi/FrameBuffer.java b/src/main/java/bwapi/FrameBuffer.java index 295749d9..7d3a4be3 100644 --- a/src/main/java/bwapi/FrameBuffer.java +++ b/src/main/java/bwapi/FrameBuffer.java @@ -27,93 +27,79 @@ of this software and associated documentation files (the "Software"), to deal import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; /** * Circular buffer of game states. */ -public class FrameBuffer { +class FrameBuffer { - private ByteBuffer data; + private ByteBuffer dataSource; private int size; private int stepGame = 0; private int stepBot = 0; + private ClientData clientData = new ClientData(); + private ArrayList dataBuffer = new ArrayList<>(); // Synchronization locks - private Object stepCount; - private ArrayList frameLocks; + private Object stepCount = new Object(); - FrameBuffer(int size) { + FrameBuffer(int size, ByteBuffer source) { this.size = size; - data = ByteBuffer.allocateDirect(size * ClientData.GameData.SIZE); - while (frameLocks.size() < size) { - frameLocks.add(new ReentrantLock()); + this.dataSource = source; + while(dataBuffer.size() < size) { + dataBuffer.add(ByteBuffer.allocateDirect(ClientData.GameData.SIZE)); } } /** * @return The number of frames currently buffered ahead of the bot's current frame */ - int framesBuffered() { - synchronized (stepCount) { - return stepGame - stepBot; - } + synchronized int framesBuffered() { + return stepGame - stepBot; } /** * @return Whether the frame buffer is empty and has no frames available for the bot to consume. */ - boolean empty() { - synchronized (stepCount) { - return framesBuffered() <= 0; - } + synchronized boolean empty() { + return framesBuffered() <= 0; } /** * @return Whether the frame buffer is full and can not buffer any additional frames. * When the frame buffer is full, JBWAPI must wait for the bot to complete a frame before returning control to StarCraft. */ - boolean full() { - synchronized (stepCount) { - return framesBuffered() >= size - 1; - } + synchronized boolean full() { + return framesBuffered() >= size - 1; + } + + synchronized private int indexGame() { + return stepGame % size; + } + + synchronized private int indexBot() { + return stepBot % size; } /** - * Copy data from shared memory into the head of the frame buffer. + * Copy dataBuffer from shared memory into the head of the frame buffer. */ - void enqueueFrame(ByteBuffer source) { - while(full()) try { - wait(); - } catch (InterruptedException ignored) {} - synchronized (stepCount) { - data.put(source.array(), indexGame() * ClientData.GameData.SIZE, ClientData.GameData.SIZE); - ++stepGame; - } - notifyAll(); + void enqueueFrame() { + while(full()) try { Thread.sleep(0, 100); } catch (InterruptedException ignored) {} + int indexGame = indexGame(); + System.out.println("FrameBuffer: Moving game to " + indexGame); + dataBuffer.get(indexGame).put(dataSource); + ++stepGame; } /** * Points the bot to the next frame in the buffer. */ - void dequeueFrame(ClientData clientData) { - while(empty()) try { - wait(); - } catch (InterruptedException ignored) {} - synchronized (stepCount) { - clientData.setBuffer(data); - clientData.setBufferOffset(indexBot() * ClientData.GameData.SIZE); - ++stepBot; - } - notifyAll(); - } - - private int indexGame() { - return stepGame % size; - } - - private int indexBot() { - return stepBot % size; + ByteBuffer dequeueFrame() { + while(empty()) try { Thread.sleep(0, 100); } catch (InterruptedException ignored) {} + System.out.println("FrameBuffer: Moving bot to " + indexBot()); + ByteBuffer output = dataBuffer.get(indexBot()); + ++stepBot; + return output; } } diff --git a/src/main/java/bwapi/Game.java b/src/main/java/bwapi/Game.java index 1983af3e..612d3e3e 100644 --- a/src/main/java/bwapi/Game.java +++ b/src/main/java/bwapi/Game.java @@ -46,8 +46,7 @@ public class Game { private final Set visibleUnits = new HashSet<>(); private List allUnits; - private final Client client; - private final GameData gameData; + private final ClientData clientData; private List staticMinerals; private List staticGeysers; @@ -100,14 +99,16 @@ public class Game { private Text.Size textSize = Text.Size.Default; private boolean latcom = true; + Game(ClientData clientData) { + this.clientData = clientData; + } - Game(Client client) { - this.client = client; - this.gameData = client.gameData(); + ClientData clientData() { + return clientData; } - Client getClient() { - return client; + private GameData gameData() { + return clientData.gameData(); } private static boolean hasPower(final int x, final int y, final UnitType unitType, final List pylons) { @@ -141,18 +142,18 @@ private static boolean hasPower(final int x, final int y, final UnitType unitTyp void init() { visibleUnits.clear(); - final int forceCount = gameData.getForceCount(); + final int forceCount = gameData().getForceCount(); forces = new Force[forceCount]; for (int id = 0; id < forceCount; id++) { - forces[id] = new Force(gameData.getForces(id), id, this); + forces[id] = new Force(gameData().getForces(id), id, this); } forceSet = Collections.unmodifiableList(Arrays.asList(forces)); - final int playerCount = gameData.getPlayerCount(); + final int playerCount = gameData().getPlayerCount(); players = new Player[playerCount]; for (int id = 0; id < playerCount; id++) { - players[id] = new Player(gameData.getPlayers(id), id, this); + players[id] = new Player(gameData().getPlayers(id), id, this); } playerSet = Collections.unmodifiableList(Arrays.asList(players)); @@ -160,13 +161,13 @@ void init() { final int bulletCount = 100; bullets = new Bullet[bulletCount]; for (int id = 0; id < bulletCount; id++) { - bullets[id] = new Bullet(gameData.getBullets(id), id, this); + bullets[id] = new Bullet(gameData().getBullets(id), id, this); } - final int regionCount = gameData.getRegionCount(); + final int regionCount = gameData().getRegionCount(); regions = new Region[regionCount]; for (int id = 0; id < regionCount; id++) { - regions[id] = new Region(gameData.getRegions(id), this); + regions[id] = new Region(gameData().getRegions(id), this); } for (final Region region : regions) { @@ -177,32 +178,32 @@ void init() { units = new Unit[10000]; - randomSeed = gameData.getRandomSeed(); - - revision = gameData.getRevision(); - debug = gameData.isDebug(); - replay = gameData.isReplay(); - neutral = players[gameData.getNeutral()]; - self = isReplay() ? null : players[gameData.getSelf()]; - enemy = isReplay() ? null : players[gameData.getEnemy()]; - multiplayer = gameData.isMultiplayer(); - battleNet = gameData.isBattleNet(); - startLocations = IntStream.range(0, gameData.getStartLocationCount()) - .mapToObj(i -> new TilePosition(gameData.getStartLocations(i))) + randomSeed = gameData().getRandomSeed(); + + revision = gameData().getRevision(); + debug = gameData().isDebug(); + replay = gameData().isReplay(); + neutral = players[gameData().getNeutral()]; + self = isReplay() ? null : players[gameData().getSelf()]; + enemy = isReplay() ? null : players[gameData().getEnemy()]; + multiplayer = gameData().isMultiplayer(); + battleNet = gameData().isBattleNet(); + startLocations = IntStream.range(0, gameData().getStartLocationCount()) + .mapToObj(i -> new TilePosition(gameData().getStartLocations(i))) .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)); - mapWidth = gameData.getMapWidth(); - mapHeight = gameData.getMapHeight(); - mapFileName = gameData.getMapFileName(); - mapPathName = gameData.getMapPathName(); - mapName = gameData.getMapName(); - mapHash = gameData.getMapHash(); + mapWidth = gameData().getMapWidth(); + mapHeight = gameData().getMapHeight(); + mapFileName = gameData().getMapFileName(); + mapPathName = gameData().getMapPathName(); + mapName = gameData().getMapName(); + mapHash = gameData().getMapHash(); final List staticMinerals = new ArrayList<>(); final List staticGeysers = new ArrayList<>(); final List staticNeutralUnits = new ArrayList<>(); final List allUnits = new ArrayList<>(); - for (int id = 0; id < gameData.getInitialUnitCount(); id++) { - final Unit unit = new Unit(gameData.getUnits(id), id, this); + for (int id = 0; id < gameData().getInitialUnitCount(); id++) { + final Unit unit = new Unit(gameData().getUnits(id), id, this); //skip ghost units if (unit.getInitialType() == UnitType.Terran_Marine && unit.getInitialHitPoints() == 0) { continue; @@ -231,15 +232,15 @@ void init() { mapTileRegionID = new short[mapWidth][mapHeight]; for (int x = 0; x < mapWidth; x++) { for (int y = 0; y < mapHeight; y++) { - buildable[x][y] = gameData.isBuildable(x, y); - groundHeight[x][y] = gameData.getGroundHeight(x, y); - mapTileRegionID[x][y] = gameData.getMapTileRegionId(x, y); + buildable[x][y] = gameData().isBuildable(x, y); + groundHeight[x][y] = gameData().getGroundHeight(x, y); + mapTileRegionID[x][y] = gameData().getMapTileRegionId(x, y); } } walkable = new boolean[mapWidth * TILE_WALK_FACTOR][mapHeight * TILE_WALK_FACTOR]; for (int i = 0; i < mapWidth * TILE_WALK_FACTOR; i++) { for (int j = 0; j < mapHeight * TILE_WALK_FACTOR; j++) { - walkable[i][j] = gameData.isWalkable(i, j); + walkable[i][j] = gameData().isWalkable(i, j); } } @@ -247,9 +248,9 @@ void init() { mapSplitTilesRegion1 = new short[REGION_DATA_SIZE]; mapSplitTilesRegion2 = new short[REGION_DATA_SIZE]; for (int i = 0; i < REGION_DATA_SIZE; i++) { - mapSplitTilesMiniTileMask[i] = gameData.getMapSplitTilesMiniTileMask(i); - mapSplitTilesRegion1[i] = gameData.getMapSplitTilesRegion1(i); - mapSplitTilesRegion2[i] = gameData.getMapSplitTilesRegion2(i); + mapSplitTilesMiniTileMask[i] = gameData().getMapSplitTilesMiniTileMask(i); + mapSplitTilesRegion1[i] = gameData().getMapSplitTilesRegion1(i); + mapSplitTilesRegion2[i] = gameData().getMapSplitTilesRegion2(i); } mapPixelWidth = mapWidth * TilePosition.SIZE_IN_PIXELS; @@ -281,7 +282,7 @@ void unitCreate(final int id) { } if (units[id] == null) { - final Unit u = new Unit(gameData.getUnits(id), id, this); + final Unit u = new Unit(gameData().getUnits(id), id, this); units[id] = u; } } @@ -305,7 +306,7 @@ void onFrame(final int frame) { } void addUnitCommand(final int type, final int unit, final int target, final int x, final int y, final int extra) { - ClientData.UnitCommand unitCommand = GameDataUtils.addUnitCommand(client.gameData()); + ClientData.UnitCommand unitCommand = GameDataUtils.addUnitCommand(gameData()); unitCommand.setTid(type); unitCommand.setUnitIndex(unit); unitCommand.setTargetIndex(target); @@ -315,14 +316,14 @@ void addUnitCommand(final int type, final int unit, final int target, final int } void addCommand(final CommandType type, final int value1, final int value2) { - Command command = GameDataUtils.addCommand(client.gameData()); + Command command = GameDataUtils.addCommand(gameData()); command.setType(type); command.setValue1(value1); command.setValue2(value2); } void 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) { - Shape shape = GameDataUtils.addShape(client.gameData()); + Shape shape = GameDataUtils.addShape(gameData()); shape.setType(type); shape.setCtype(coordType); shape.setX1(x1); @@ -457,8 +458,8 @@ public List getBullets() { * @return Set of Positions giving the coordinates of nuke locations. */ public List getNukeDots() { - return IntStream.range(0, gameData.getNukeDotCount()) - .mapToObj(id -> new Position(gameData.getNukeDots(id))) + return IntStream.range(0, gameData().getNukeDotCount()) + .mapToObj(id -> new Position(gameData().getNukeDots(id))) .collect(Collectors.toList()); } @@ -521,7 +522,7 @@ public Region getRegion(final int regionID) { * @see GameType */ public GameType getGameType() { - return GameType.idToEnum[gameData.getGameType()]; + return GameType.idToEnum[gameData().getGameType()]; } /** @@ -532,7 +533,7 @@ public GameType getGameType() { * @see Latency */ public Latency getLatency() { - return Latency.idToEnum[gameData.getLatency()]; + return Latency.idToEnum[gameData().getLatency()]; } /** @@ -542,7 +543,7 @@ public Latency getLatency() { * @return Number of logical frames that have elapsed since the game started as an integer. */ public int getFrameCount() { - return gameData.getFrameCount(); + return gameData().getFrameCount(); } /** @@ -552,7 +553,7 @@ public int getFrameCount() { * @return The number of logical frames that the replay contains. */ public int getReplayFrameCount() { - return gameData.getReplayFrameCount(); + return gameData().getReplayFrameCount(); } /** @@ -562,7 +563,7 @@ public int getReplayFrameCount() { * @see #getAverageFPS */ public int getFPS() { - return gameData.getFps(); + return gameData().getFps(); } /** @@ -573,7 +574,7 @@ public int getFPS() { * @see #getFPS */ public double getAverageFPS() { - return gameData.getAverageFPS(); + return gameData().getAverageFPS(); } /** @@ -582,7 +583,7 @@ public double getAverageFPS() { * @return {@link Position} indicating the location of the mouse. Returns {@link Position#Unknown} if {@link Flag#UserInput} is disabled. */ public Position getMousePosition() { - return new Position(gameData.getMouseX(), gameData.getMouseY()); + return new Position(gameData().getMouseX(), gameData().getMouseY()); } /** @@ -594,7 +595,7 @@ public Position getMousePosition() { * @see MouseButton */ public boolean getMouseState(final MouseButton button) { - return gameData.getMouseState(button.id); + return gameData().getMouseState(button.id); } /** @@ -606,7 +607,7 @@ public boolean getMouseState(final MouseButton button) { * @see Key */ public boolean getKeyState(final Key key) { - return gameData.getKeyState(key.id); + return gameData().getKeyState(key.id); } /** @@ -617,7 +618,7 @@ public boolean getKeyState(final Key key) { * @see #setScreenPosition */ public Position getScreenPosition() { - return new Position(gameData.getScreenX(), gameData.getScreenY()); + return new Position(gameData().getScreenX(), gameData().getScreenY()); } public void setScreenPosition(final Position p) { @@ -662,7 +663,7 @@ public void pingMinimap(final Position p) { * @see Flag */ public boolean isFlagEnabled(final Flag flag) { - return gameData.getFlags(flag.id); + return gameData().getFlags(flag.id); } /** @@ -956,7 +957,7 @@ public boolean isBuildable(final TilePosition position, final boolean includeBui if (!position.isValid(this)) { return false; } - return buildable[position.x][position.y] && (!includeBuildings || !gameData.isOccupied(position.x, position.y)); + return buildable[position.x][position.y] && (!includeBuildings || !gameData().isOccupied(position.x, position.y)); } /** @@ -976,7 +977,7 @@ public boolean isVisible(final TilePosition position) { if (!position.isValid(this)) { return false; } - return gameData.isVisible(position.x, position.y); + return gameData().isVisible(position.x, position.y); } /** @@ -997,7 +998,7 @@ public boolean isExplored(final TilePosition position) { if (!position.isValid(this)) { return false; } - return gameData.isExplored(position.x, position.y); + return gameData().isExplored(position.x, position.y); } /** @@ -1015,7 +1016,7 @@ public boolean hasCreep(final TilePosition position) { if (!position.isValid(this)) { return false; } - return gameData.getHasCreep(position.x, position.y); + return gameData().getHasCreep(position.x, position.y); } public boolean hasPowerPrecise(final int x, final int y) { @@ -1513,7 +1514,7 @@ static String formatString(final String string, final Text... colors) { */ public void printf(final String string, final Text... colors) { final String formatted = formatString(string, colors); - addCommand(Printf, GameDataUtils.addString(client.gameData(), formatted), 0); + addCommand(Printf, GameDataUtils.addString(gameData(), formatted), 0); } /** @@ -1538,7 +1539,7 @@ public void sendText(final String string, final Text... colors) { */ public void sendTextEx(final boolean toAllies, final String string, final Text... colors) { final String formatted = formatString(string, colors); - addCommand(SendText, GameDataUtils.addString(client.gameData(), formatted), toAllies ? 1 : 0); + addCommand(SendText, GameDataUtils.addString(gameData(), formatted), toAllies ? 1 : 0); } /** @@ -1547,7 +1548,7 @@ public void sendTextEx(final boolean toAllies, final String string, final Text.. * @return true if the client is in a game, and false if it is not. */ public boolean isInGame() { - return gameData.isInGame(); + return gameData().isInGame(); } /** @@ -1579,7 +1580,7 @@ public boolean isBattleNet() { * @see #resumeGame */ public boolean isPaused() { - return gameData.isPaused(); + return gameData().isPaused(); } /** @@ -1678,8 +1679,8 @@ public List getSelectedUnits() { if (!isFlagEnabled(Flag.UserInput)) { return Collections.emptyList(); } - return IntStream.range(0, gameData.getSelectedUnitCount()) - .mapToObj(i -> units[gameData.getSelectedUnits(i)]) + return IntStream.range(0, gameData().getSelectedUnitCount()) + .mapToObj(i -> units[gameData().getSelectedUnits(i)]) .collect(Collectors.toList()); } @@ -1747,7 +1748,7 @@ public List observers() { public void drawText(final CoordinateType ctype, final int x, final int y, final String string, final Text... colors) { final String formatted = formatString(string, colors); - final int stringId = GameDataUtils.addString(client.gameData(), formatted); + final int stringId = GameDataUtils.addString(gameData(), formatted); addShape(ShapeType.Text, ctype, x, y, 0, 0, stringId, textSize.id, 0, false); } @@ -2130,7 +2131,7 @@ public void drawLineScreen(Position a, Position b, Color color) { * @see #getRemainingLatencyFrames */ public int getLatencyFrames() { - return gameData.getLatencyFrames(); + return gameData().getLatencyFrames(); } /** @@ -2142,7 +2143,7 @@ public int getLatencyFrames() { * @see #getRemainingLatencyTime */ public int getLatencyTime() { - return gameData.getLatencyTime(); + return gameData().getLatencyTime(); } /** @@ -2155,7 +2156,7 @@ public int getLatencyTime() { * @see #getLatencyFrames */ public int getRemainingLatencyFrames() { - return gameData.getRemainingLatencyFrames(); + return gameData().getRemainingLatencyFrames(); } /** @@ -2168,7 +2169,7 @@ public int getRemainingLatencyFrames() { * @see #getLatencyTime */ public int getRemainingLatencyTime() { - return gameData.getRemainingLatencyTime(); + return gameData().getRemainingLatencyTime(); } /** @@ -2210,7 +2211,7 @@ public boolean isLatComEnabled() { */ public void setLatCom(final boolean isEnabled) { //update shared memory - gameData.setHasLatCom(isEnabled); + gameData().setHasLatCom(isEnabled); //update internal memory latcom = isEnabled; //update server @@ -2225,7 +2226,7 @@ public void setLatCom(final boolean isEnabled) { * @return An integer value representing the instance number. */ public int getInstanceNumber() { - return gameData.getInstanceID(); + return gameData().getInstanceID(); } public int getAPM() { @@ -2239,7 +2240,7 @@ public int getAPM() { * @return The number of actions that the bot has executed per minute, on average. */ public int getAPM(final boolean includeSelects) { - return includeSelects ? gameData.getBotAPM_selects() : gameData.getBotAPM_noselects(); + return includeSelects ? gameData().getBotAPM_selects() : gameData().getBotAPM_noselects(); } /** @@ -2316,7 +2317,7 @@ public boolean setVision(Player player, boolean enabled) { * @see #setGUI */ boolean isGUIEnabled() { - return gameData.getHasGUI(); + return gameData().getHasGUI(); } /** @@ -2329,7 +2330,7 @@ boolean isGUIEnabled() { * @see #isGUIEnabled */ public void setGUI(boolean enabled) { - gameData.setHasGUI(enabled); + gameData().setHasGUI(enabled); //queue up command for server so it also applies the change addCommand(CommandType.SetGui, enabled ? 1 : 0, 0); } @@ -2363,7 +2364,7 @@ public boolean setMap(final String mapFileName) { return false; } - addCommand(CommandType.SetMap, GameDataUtils.addString(client.gameData(), mapFileName), 0); + addCommand(CommandType.SetMap, GameDataUtils.addString(gameData(), mapFileName), 0); return true; } @@ -2433,7 +2434,7 @@ public void setTextSize(final Text.Size size) { * @return Time, in seconds, that the game has elapsed as an integer. */ public int elapsedTime() { - return gameData.getElapsedTime(); + return gameData().getElapsedTime(); } /** @@ -2537,7 +2538,7 @@ public void setCommandOptimizationLevel(final int level) { * @return Integer containing the time (in game seconds) on the countdown timer. */ public int countdownTimer() { - return gameData.getCountdownTimer(); + return gameData().getCountdownTimer(); } /** diff --git a/src/main/java/bwapi/GameDataUtils.java b/src/main/java/bwapi/GameDataUtils.java index b6215bc1..1434a0e6 100644 --- a/src/main/java/bwapi/GameDataUtils.java +++ b/src/main/java/bwapi/GameDataUtils.java @@ -34,10 +34,6 @@ public class GameDataUtils { static final int MAX_COUNT = 19999; static final int MAX_STRING_SIZE = 1024; - static String eventString(ClientData.GameData gameData, final int s) { - return gameData.getEventStrings(s); - } - static int addString(ClientData.GameData gameData, final String string) { int stringCount = gameData.getStringCount(); if (stringCount >= MAX_COUNT) { diff --git a/src/test/java/DumpToClient.java b/src/test/java/DumpToClient.java index 5577c706..90d72377 100644 --- a/src/test/java/DumpToClient.java +++ b/src/test/java/DumpToClient.java @@ -92,14 +92,17 @@ public static void main(String[] args) throws IOException { out.println("package bwapi;"); out.println("import java.nio.ByteBuffer;"); out.println("final class ClientData {"); - out.println(" WrappedBuffer buffer;"); - out.println(" private int bufferOffset = 0;"); + out.println(" private WrappedBuffer buffer;"); + out.println(" private GameData gameData;"); + out.println(" ClientData() {"); + out.println(" gameData = new ClientData.GameData(0);"); + out.println(" }"); + out.println(" GameData gameData() {"); + out.println(" return gameData;"); + out.println(" }"); out.println(" public void setBuffer(ByteBuffer buffer) {"); out.println(" this.buffer = new WrappedBuffer(buffer);"); out.println(" }"); - out.println(" public void setBufferOffset(int offset) {"); - out.println(" this.bufferOffset = offset;"); - out.println(" }"); structs.values().forEach(s -> { out.printf(" class %s {\n", s.name); out.printf(" static final int SIZE = %d;\n", s.size); @@ -177,9 +180,9 @@ public static void main(String[] args) throws IOException { } offset *= v.arraySizes.get(i); } - offsetString = "bufferOffset + myOffset + " + v.offset + " + " + String.join(" + ", index); + offsetString = "myOffset + " + v.offset + " + " + String.join(" + ", index); } else { - offsetString = "bufferOffset + myOffset + " + v.offset; + offsetString = "myOffset + " + v.offset; } String paramString = String.join(", ", params); out.printf("%s) {\n", paramString); diff --git a/src/test/java/bwapi/ClientDataBenchmark.java b/src/test/java/bwapi/ClientDataBenchmark.java index 7227619a..6acd3c77 100644 --- a/src/test/java/bwapi/ClientDataBenchmark.java +++ b/src/test/java/bwapi/ClientDataBenchmark.java @@ -20,7 +20,7 @@ public static class EmptyState { @Setup(Level.Invocation) public void setup() { client = new Client(ByteBuffer.allocateDirect(ClientData.GameData.SIZE)); - game = new Game(client); + game = new Game(client.clientData()); strings = buildStrings(); } @@ -35,11 +35,11 @@ public static class FilledWithStrings { @Setup(Level.Invocation) public void setup() { client = new Client(ByteBuffer.allocateDirect(ClientData.GameData.SIZE)); - data = client.gameData(); - game = new Game(client); + data = client.clientData().gameData(); + game = new Game(client.clientData()); String[] strings = buildStrings(); for (String s : strings) { - GameDataUtils.addString(client.gameData(), s); + GameDataUtils.addString(client.clientData().gameData(), s); } } } @@ -69,16 +69,16 @@ public int addUnitCommand(EmptyState s) { for (int i = 0; i < GameDataUtils.MAX_COUNT; i++) { s.game.addUnitCommand(0, 1, 2, 3, 4, 5); } - return s.client.gameData().getCommandCount(); + return s.client.clientData().gameData().getCommandCount(); } @Benchmark @OperationsPerInvocation(GameDataUtils.MAX_COUNT) public int addString(EmptyState s) { for (int i = 0; i < GameDataUtils.MAX_COUNT; i++) { - GameDataUtils.addString(s.client.gameData(), s.strings[i]); + GameDataUtils.addString(s.client.clientData().gameData(), s.strings[i]); } - return s.client.gameData().getStringCount(); + return s.client.clientData().gameData().getStringCount(); } @Benchmark diff --git a/src/test/java/bwapi/GameBuilder.java b/src/test/java/bwapi/GameBuilder.java index e3219cbb..577c7019 100644 --- a/src/test/java/bwapi/GameBuilder.java +++ b/src/test/java/bwapi/GameBuilder.java @@ -33,7 +33,7 @@ public static ByteBuffer binToBuffer(String binLocation) throws IOException { } public static Game createGame(Client client) throws IOException { - final Game game = new Game(client); + final Game game = new Game(client.clientData()); game.init(); return game; } diff --git a/src/test/java/bwapi/GameStateDumper.java b/src/test/java/bwapi/GameStateDumper.java index afd49453..e8f5b9ae 100644 --- a/src/test/java/bwapi/GameStateDumper.java +++ b/src/test/java/bwapi/GameStateDumper.java @@ -36,7 +36,9 @@ public void onStart() { } private void dumpBuffer(String name) throws IOException { - ByteBuffer buf = game.getClient().clientData().buffer.getBuffer(); + // TODO + //ByteBuffer buf = game.getClient().clientData().buffer.getBuffer(); + ByteBuffer buf = null; buf.rewind(); byte[] bytearr = new byte[buf.remaining()]; buf.get(bytearr); diff --git a/src/test/java/bwapi/GameTest.java b/src/test/java/bwapi/GameTest.java index a5b360bb..4633b37c 100644 --- a/src/test/java/bwapi/GameTest.java +++ b/src/test/java/bwapi/GameTest.java @@ -21,7 +21,7 @@ public class GameTest { private List allUnits = new ArrayList<>(); - private Game sut = new Game(mock(Client.class)) { + private Game sut = new Game(mock(ClientData.class)) { @Override public List getAllUnits() { return allUnits; @@ -93,7 +93,7 @@ public void ifReplaySelfAndEnemyShouldBeNull() throws IOException { Client client = new Client(buffer); // modify the buffer to fake a replay - client.gameData().setIsReplay(true); + client.clientData().gameData().setIsReplay(true); Game game = GameBuilder.createGame(client); From 79f6fe05352e3cbad673d04341ed7dd3042297a3 Mon Sep 17 00:00:00 2001 From: dgant Date: Sun, 26 Jul 2020 20:16:00 -0400 Subject: [PATCH 06/56] Corrected copying of shared memory to FrameBuffer. Corrected measurement of bot idleness and frame buffer size. --- src/main/java/bwapi/BWClient.java | 47 ++++++++-------- .../java/bwapi/BWClientConfiguration.java | 2 +- src/main/java/bwapi/BotWrapper.java | 54 ++++++++++--------- src/main/java/bwapi/FrameBuffer.java | 11 ++-- 4 files changed, 62 insertions(+), 52 deletions(-) diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index 58d75331..cfa9c9f0 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -50,42 +50,47 @@ public void startGame(BWClientConfiguration configuration) { client.reconnect(); do { + ClientData.GameData gameData = client.clientData().gameData(); long lastUpdateTimestampMillis = 0; - System.out.println("Client: Starting game"); - while (!getGame().isInGame()) { - if (!client.isConnected()) { + System.out.println("Client: Beginning game loop"); + while (!gameData.isInGame()) { + botWrapper = null; + if (client.isConnected()) { + System.out.println("Client: Not in game; Connected."); + } else { + System.out.println("Client: Not in game; Not connected."); return; } - System.out.println("Client: connected."); lastUpdateTimestampMillis = System.currentTimeMillis(); client.update(); } - - System.out.println("Client: Creating bot wrapper"); - BotWrapper botWrapper = new BotWrapper(configuration, eventListener, client.mapFile()); - - while (getGame().isInGame()) { - System.out.println("Client: Stepping bot wrapper"); + while (gameData.isInGame()) { + System.out.println("Client: In game on frame " + gameData.getFrameCount()); + if (botWrapper == null) { + botWrapper = new BotWrapper(configuration, eventListener, client.mapFile()); + } botWrapper.step(); - System.out.println("Client: Waiting for idle bot or frame duration"); // Proceed immediately once framebuffer is empty // Otherwise, wait for bot to catch up // TODO: Replace with a wait instead of a sleep - // TODO: Respect configuration.asyncWaitOnFrameZero - while ( ! botWrapper.botIdle()) { - long frameDuration = System.currentTimeMillis() - lastUpdateTimestampMillis; - if (frameDuration > configuration.asyncFrameDurationMillis && (client.clientData().gameData().getFrameCount() > 0 || ! configuration.asyncWaitOnFrameZero)) { - System.out.println("Client: Exceeded frame duration while waiting for bot: " + frameDuration + "ms on frame " + client.clientData().gameData().getFrameCount()); + while(true) { + if (botWrapper.botIdle()) { + System.out.println("Client: Proceeding because bot is idle."); break; } - try { - Thread.sleep(1); - } catch (InterruptedException ignored) {} + long frameDurationMillis = System.currentTimeMillis() - lastUpdateTimestampMillis; + if (frameDurationMillis > configuration.asyncFrameDurationMillis && (client.clientData().gameData().getFrameCount() > 0 || ! configuration.asyncWaitOnFrameZero)) { + System.out.println("Client: Proceeding because frame " + botWrapper.getGame().getFrameCount() + " lasted " + frameDurationMillis + "ms"); + break; + } + try { Thread.sleep(1); } catch (InterruptedException ignored) {} } - System.out.println("Client: Ending frame. Frames buffered: "); - lastUpdateTimestampMillis = System.currentTimeMillis(); + long currentTimeMillis = System.currentTimeMillis(); + long frameDurationMillis = currentTimeMillis - lastUpdateTimestampMillis; + lastUpdateTimestampMillis = currentTimeMillis; + System.out.println("Client: Ending frame after " + frameDurationMillis + "ms"); client.update(); if (!client.isConnected()) { System.out.println("Reconnecting..."); diff --git a/src/main/java/bwapi/BWClientConfiguration.java b/src/main/java/bwapi/BWClientConfiguration.java index 5cfa8b38..4664cc62 100644 --- a/src/main/java/bwapi/BWClientConfiguration.java +++ b/src/main/java/bwapi/BWClientConfiguration.java @@ -32,7 +32,7 @@ public class BWClientConfiguration { * If JBWAPI detects that this much time (in milliseconds) has passed since a bot's event handlers began, returns control back to BWAPI. * Real-time human play typically uses the "fastest" game speed, which has 42.86ms (42,860ns) between frames. */ - public int asyncFrameDurationMillis = 40000; + public int asyncFrameDurationMillis = 40; /** * The maximum number of frames to buffer while waiting on a bot. diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java index 7e43c863..a56eba72 100644 --- a/src/main/java/bwapi/BotWrapper.java +++ b/src/main/java/bwapi/BotWrapper.java @@ -26,7 +26,6 @@ of this software and associated documentation files (the "Software"), to deal package bwapi; import java.nio.ByteBuffer; -import java.util.EventListener; /** * Manages invocation of bot event handlers @@ -35,8 +34,9 @@ class BotWrapper { private final BWClientConfiguration configuration; private final BWEventListener eventListener; private final Game game; - private final FrameBuffer frameBuffer; - private final Thread botThread; + private FrameBuffer frameBuffer = null; + private Thread botThread = null; + private volatile boolean idle = false; BotWrapper(BWClientConfiguration configuration, BWEventListener eventListener, ByteBuffer dataSource) { this.configuration = configuration; @@ -48,28 +48,12 @@ class BotWrapper { if (configuration.async) { frameBuffer = new FrameBuffer(configuration.asyncFrameBufferSize, dataSource); - botThread = new Thread(() -> { - while(true) { - while(frameBuffer.empty()) try { Thread.sleep(0, 100); } catch (InterruptedException ignored) {} - - System.out.println("Bot thread: Dequeuing frame. There are " + frameBuffer.framesBuffered() + " frames buffered."); - game.clientData().setBuffer(frameBuffer.dequeueFrame()); + } + } - System.out.println("Bot thread: Handling events."); - handleEvents(); - if (!game.clientData().gameData().isInGame()) { - System.out.println("Bot thread: Exiting."); - return; - } else { - System.out.println("Bot thread: Handled events."); - } - } - }); - botThread.setName("JBWAPI Bot"); + void startBot() { + if (configuration.async) { botThread.start(); - } else { - frameBuffer = null; - botThread = null; } } @@ -77,14 +61,34 @@ Game getGame() { return game; } + /** + * True if the bot has handled all enqueued frames and is waiting for a new frame from StarCraft. + */ boolean botIdle() { - // TODO: This returns true if the bot is still processing the newest frame, leaving the bot permanently one frame behind - return frameBuffer.empty(); + return idle || ! configuration.async; } void step() { if (configuration.async) { frameBuffer.enqueueFrame(); + if (botThread == null) { + botThread = new Thread(() -> { + //noinspection InfiniteLoopStatement + while(true) { + while(frameBuffer.empty()) try { + idle = true; + Thread.sleep(0, 100); + } catch (InterruptedException ignored) {} + idle = false; + System.out.println("Bot thread: Dequeuing frame. There are " + frameBuffer.framesBuffered() + " frames buffered."); + game.clientData().setBuffer(frameBuffer.dequeueFrame()); + System.out.println("Bot thread: Handling events."); + handleEvents(); + System.out.println("Bot thread: Handled events."); + }}); + botThread.setName("JBWAPI Bot"); + botThread.start(); + } } else { handleEvents(); } diff --git a/src/main/java/bwapi/FrameBuffer.java b/src/main/java/bwapi/FrameBuffer.java index 7d3a4be3..6e7999e4 100644 --- a/src/main/java/bwapi/FrameBuffer.java +++ b/src/main/java/bwapi/FrameBuffer.java @@ -37,7 +37,6 @@ class FrameBuffer { private int size; private int stepGame = 0; private int stepBot = 0; - private ClientData clientData = new ClientData(); private ArrayList dataBuffer = new ArrayList<>(); // Synchronization locks @@ -86,9 +85,11 @@ synchronized private int indexBot() { */ void enqueueFrame() { while(full()) try { Thread.sleep(0, 100); } catch (InterruptedException ignored) {} - int indexGame = indexGame(); - System.out.println("FrameBuffer: Moving game to " + indexGame); - dataBuffer.get(indexGame).put(dataSource); + System.out.println("FrameBuffer: Enqueuing buffer " + indexGame() + " on game step #" + stepGame + " with " + framesBuffered() + " frames buffered."); + ByteBuffer dataTarget = dataBuffer.get(indexGame()); + dataSource.rewind(); + dataTarget.rewind(); + dataTarget.put(dataSource); ++stepGame; } @@ -97,7 +98,7 @@ void enqueueFrame() { */ ByteBuffer dequeueFrame() { while(empty()) try { Thread.sleep(0, 100); } catch (InterruptedException ignored) {} - System.out.println("FrameBuffer: Moving bot to " + indexBot()); + System.out.println("FrameBuffer: Dequeuing buffer " + indexBot() + " on bot step #" + stepBot); ByteBuffer output = dataBuffer.get(indexBot()); ++stepBot; return output; From d4d03b6a143bac09d3778a53935619aa09edfb09 Mon Sep 17 00:00:00 2001 From: dgant Date: Tue, 28 Jul 2020 13:43:43 -0400 Subject: [PATCH 07/56] First end-to-end working version. Added side effect queue to route commands to live shared memory instead of uselessly writing them to the frame buffer. --- src/main/java/bwapi/BWClient.java | 5 +- src/main/java/bwapi/BotWrapper.java | 13 ++--- src/main/java/bwapi/Game.java | 54 +++++++++------------ src/main/java/bwapi/SideEffect.java | 62 ++++++++++++++++++++++++ src/main/java/bwapi/SideEffectQueue.java | 32 ++++++++++++ 5 files changed, 126 insertions(+), 40 deletions(-) create mode 100644 src/main/java/bwapi/SideEffect.java create mode 100644 src/main/java/bwapi/SideEffectQueue.java diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index cfa9c9f0..b97d97a9 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -80,7 +80,7 @@ public void startGame(BWClientConfiguration configuration) { break; } long frameDurationMillis = System.currentTimeMillis() - lastUpdateTimestampMillis; - if (frameDurationMillis > configuration.asyncFrameDurationMillis && (client.clientData().gameData().getFrameCount() > 0 || ! configuration.asyncWaitOnFrameZero)) { + if (botWrapper.frameDurationMillis > configuration.asyncFrameDurationMillis && (client.clientData().gameData().getFrameCount() > 0 || ! configuration.asyncWaitOnFrameZero)) { System.out.println("Client: Proceeding because frame " + botWrapper.getGame().getFrameCount() + " lasted " + frameDurationMillis + "ms"); break; } @@ -91,6 +91,7 @@ public void startGame(BWClientConfiguration configuration) { long frameDurationMillis = currentTimeMillis - lastUpdateTimestampMillis; lastUpdateTimestampMillis = currentTimeMillis; System.out.println("Client: Ending frame after " + frameDurationMillis + "ms"); + getGame().sideEffects.flush(client.clientData()); client.update(); if (!client.isConnected()) { System.out.println("Reconnecting..."); @@ -101,7 +102,5 @@ public void startGame(BWClientConfiguration configuration) { // TODO: Before exiting give async bot time to complete onEnd(), maybe via thread.join(). } while (configuration.autoContinue); // lgtm [java/constant-loop-condition] - - } } \ No newline at end of file diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java index a56eba72..d1ab4075 100644 --- a/src/main/java/bwapi/BotWrapper.java +++ b/src/main/java/bwapi/BotWrapper.java @@ -51,12 +51,6 @@ class BotWrapper { } } - void startBot() { - if (configuration.async) { - botThread.start(); - } - } - Game getGame() { return game; } @@ -68,6 +62,13 @@ boolean botIdle() { return idle || ! configuration.async; } + /** + * True if there is a frame buffer with free capacity. + */ + boolean canBuffer() { + return configuration.async && ! frameBuffer.full(); + } + void step() { if (configuration.async) { frameBuffer.enqueueFrame(); diff --git a/src/main/java/bwapi/Game.java b/src/main/java/bwapi/Game.java index 612d3e3e..33249dd3 100644 --- a/src/main/java/bwapi/Game.java +++ b/src/main/java/bwapi/Game.java @@ -99,6 +99,8 @@ public class Game { private Text.Size textSize = Text.Size.Default; private boolean latcom = true; + public final SideEffectQueue sideEffects = new SideEffectQueue(); + Game(ClientData clientData) { this.clientData = clientData; } @@ -305,37 +307,6 @@ void onFrame(final int frame) { getAllUnits().forEach(u -> u.updatePosition(frame)); } - void addUnitCommand(final int type, final int unit, final int target, final int x, final int y, final int extra) { - ClientData.UnitCommand unitCommand = GameDataUtils.addUnitCommand(gameData()); - unitCommand.setTid(type); - unitCommand.setUnitIndex(unit); - unitCommand.setTargetIndex(target); - unitCommand.setX(x); - unitCommand.setY(y); - unitCommand.setExtra(extra); - } - - void addCommand(final CommandType type, final int value1, final int value2) { - Command command = GameDataUtils.addCommand(gameData()); - command.setType(type); - command.setValue1(value1); - command.setValue2(value2); - } - - void 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) { - Shape shape = GameDataUtils.addShape(gameData()); - shape.setType(type); - shape.setCtype(coordType); - shape.setX1(x1); - shape.setY1(y1); - shape.setX2(x2); - shape.setY2(y2); - shape.setExtra1(extra1); - shape.setExtra2(extra2); - shape.setColor(color); - shape.setIsSolid(isSolid); - } - /** * Retrieves the set of all teams/forces. Forces are commonly seen in @UMS * game types and some others such as @TvB and the team versions of game types. @@ -2688,4 +2659,25 @@ public int getDamageTo(final UnitType toType, final UnitType fromType, final Pla public int getRandomSeed() { return randomSeed; } + + /** + * Convenience method for adding a unit command from raw arguments. + */ + void addUnitCommand(final int type, final int unit, final int target, final int x, final int y, final int extra) { + sideEffects.enqueue(SideEffect.addUnitCommand(type, unit, target, x, y, extra)); + } + + /** + * Convenience method for adding a game command from raw arguments. + */ + void addCommand(final CommandType type, final int value1, final int value2) { + sideEffects.enqueue(SideEffect.addCommand(type, value1, value2)); + } + + /** + * Convenience method for adding a shape from raw arguments. + */ + void 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) { + sideEffects.enqueue(SideEffect.addShape(type, coordType, x1, y1, x2, y2, extra1, extra2, color, isSolid)); + } } diff --git a/src/main/java/bwapi/SideEffect.java b/src/main/java/bwapi/SideEffect.java new file mode 100644 index 00000000..d652b6f4 --- /dev/null +++ b/src/main/java/bwapi/SideEffect.java @@ -0,0 +1,62 @@ +package bwapi; + +import java.util.function.Consumer; +import java.util.function.Function; + +/** +* A side effect is an interaction that a bot attempts to have with the game. +* This entails sending a game or unit command, or drawing a shape. +*/ +class SideEffect { + + private Consumer application; + + void apply(ClientData clientData) { + application.accept(clientData); + } + + private SideEffect() {} + + static SideEffect addUnitCommand(final int type, final int unit, final int target, final int x, final int y, final int extra) { + SideEffect output = new SideEffect(); + output.application = (ClientData clientData) -> { + ClientData.UnitCommand unitCommand = GameDataUtils.addUnitCommand(clientData.gameData()); + unitCommand.setTid(type); + unitCommand.setUnitIndex(unit); + unitCommand.setTargetIndex(target); + unitCommand.setX(x); + unitCommand.setY(y); + unitCommand.setExtra(extra); + }; + return output; + } + + static SideEffect addCommand(final CommandType type, final int value1, final int value2) { + SideEffect output = new SideEffect(); + output.application = (ClientData clientData) -> { + ClientData.Command command = GameDataUtils.addCommand(clientData.gameData()); + command.setType(type); + command.setValue1(value1); + command.setValue2(value2); + }; + return output; + } + + 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) { + SideEffect output = new SideEffect(); + output.application = (ClientData clientData) -> { + ClientData.Shape shape = GameDataUtils.addShape(clientData.gameData()); + shape.setType(type); + shape.setCtype(coordType); + shape.setX1(x1); + shape.setY1(y1); + shape.setX2(x2); + shape.setY2(y2); + shape.setExtra1(extra1); + shape.setExtra2(extra2); + shape.setColor(color); + shape.setIsSolid(isSolid); + }; + return output; + } +} \ No newline at end of file diff --git a/src/main/java/bwapi/SideEffectQueue.java b/src/main/java/bwapi/SideEffectQueue.java new file mode 100644 index 00000000..4e4c394a --- /dev/null +++ b/src/main/java/bwapi/SideEffectQueue.java @@ -0,0 +1,32 @@ +package bwapi; + +import java.util.ArrayList; + +/** + * Queue of intended bot interactions with the game, to be flushed as JBWAPI returns control to StarCraft after a frame. + */ +public class SideEffectQueue { + + private ArrayList queue = new ArrayList<>(); + + /** + * Includes a side effect to be sent back to BWAPI in the future. + * + * @param sideEffect + * A side effect to be applied to the game state the next time the queue is flushed. + */ + synchronized void enqueue(SideEffect sideEffect) { + queue.add(sideEffect); + } + + /** + * Applies all enqueued side effects to the current BWAPI frame. + * + * @param liveClientData + * The live game frame's data, using the BWAPI shared memory. + */ + synchronized void flush(ClientData liveClientData) { + queue.forEach(x -> x.apply(liveClientData)); + queue.clear(); + } +} From fd921e81befb56b38b279cea248e7118dcc7cc3a Mon Sep 17 00:00:00 2001 From: dgant Date: Wed, 29 Jul 2020 17:57:53 -0400 Subject: [PATCH 08/56] Fixed strings --- src/main/java/bwapi/BWClient.java | 2 +- src/main/java/bwapi/Game.java | 24 ++++++++++++++++---- src/main/java/bwapi/GameDataUtils.java | 4 ++-- src/main/java/bwapi/SideEffect.java | 29 ++++++++++++++++++++++++ src/main/java/bwapi/SideEffectQueue.java | 2 +- 5 files changed, 52 insertions(+), 9 deletions(-) diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index b97d97a9..bf240f16 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -80,7 +80,7 @@ public void startGame(BWClientConfiguration configuration) { break; } long frameDurationMillis = System.currentTimeMillis() - lastUpdateTimestampMillis; - if (botWrapper.frameDurationMillis > configuration.asyncFrameDurationMillis && (client.clientData().gameData().getFrameCount() > 0 || ! configuration.asyncWaitOnFrameZero)) { + if (frameDurationMillis > configuration.asyncFrameDurationMillis && (client.clientData().gameData().getFrameCount() > 0 || ! configuration.asyncWaitOnFrameZero)) { System.out.println("Client: Proceeding because frame " + botWrapper.getGame().getFrameCount() + " lasted " + frameDurationMillis + "ms"); break; } diff --git a/src/main/java/bwapi/Game.java b/src/main/java/bwapi/Game.java index 33249dd3..efb5b3ae 100644 --- a/src/main/java/bwapi/Game.java +++ b/src/main/java/bwapi/Game.java @@ -1485,7 +1485,7 @@ static String formatString(final String string, final Text... colors) { */ public void printf(final String string, final Text... colors) { final String formatted = formatString(string, colors); - addCommand(Printf, GameDataUtils.addString(gameData(), formatted), 0); + addCommand(Printf, formatted, 0); } /** @@ -1510,7 +1510,7 @@ public void sendText(final String string, final Text... colors) { */ public void sendTextEx(final boolean toAllies, final String string, final Text... colors) { final String formatted = formatString(string, colors); - addCommand(SendText, GameDataUtils.addString(gameData(), formatted), toAllies ? 1 : 0); + addCommand(SendText, formatted, toAllies ? 1 : 0); } /** @@ -1719,8 +1719,7 @@ public List observers() { public void drawText(final CoordinateType ctype, final int x, final int y, final String string, final Text... colors) { final String formatted = formatString(string, colors); - final int stringId = GameDataUtils.addString(gameData(), formatted); - addShape(ShapeType.Text, ctype, x, y, 0, 0, stringId, textSize.id, 0, false); + addShape(ShapeType.Text, ctype, x, y, 0, 0, formatted, textSize.id, 0, false); } public void drawTextMap(final int x, final int y, final String string, final Text... colors) { @@ -2335,7 +2334,7 @@ public boolean setMap(final String mapFileName) { return false; } - addCommand(CommandType.SetMap, GameDataUtils.addString(gameData(), mapFileName), 0); + addCommand(CommandType.SetMap, mapFileName, 0); return true; } @@ -2674,10 +2673,25 @@ void addCommand(final CommandType type, final int value1, final int value2) { sideEffects.enqueue(SideEffect.addCommand(type, value1, value2)); } + + /** + * Convenience method for adding a game command from raw arguments. + */ + void addCommand(final CommandType type, final String value1, final int value2) { + sideEffects.enqueue(SideEffect.addCommand(type, value1, value2)); + } + /** * Convenience method for adding a shape from raw arguments. */ void 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) { sideEffects.enqueue(SideEffect.addShape(type, coordType, x1, y1, x2, y2, extra1, extra2, color, isSolid)); } + + /** + * Convenience method for adding a shape from raw arguments. + */ + void 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) { + sideEffects.enqueue(SideEffect.addShape(type, coordType, x1, y1, x2, y2, text, extra2, color, isSolid)); + } } diff --git a/src/main/java/bwapi/GameDataUtils.java b/src/main/java/bwapi/GameDataUtils.java index 1434a0e6..c637c5b9 100644 --- a/src/main/java/bwapi/GameDataUtils.java +++ b/src/main/java/bwapi/GameDataUtils.java @@ -29,10 +29,10 @@ of this software and associated documentation files (the "Software"), to deal * Static functions for modifying GameData. * These functions live outside GameData because GameData is auto-generated. */ -public class GameDataUtils { +class GameDataUtils { static final int MAX_COUNT = 19999; - static final int MAX_STRING_SIZE = 1024; + private static final int MAX_STRING_SIZE = 1024; static int addString(ClientData.GameData gameData, final String string) { int stringCount = gameData.getStringCount(); diff --git a/src/main/java/bwapi/SideEffect.java b/src/main/java/bwapi/SideEffect.java index d652b6f4..109c62de 100644 --- a/src/main/java/bwapi/SideEffect.java +++ b/src/main/java/bwapi/SideEffect.java @@ -42,6 +42,17 @@ static SideEffect addCommand(final CommandType type, final int value1, final int return output; } + static SideEffect addCommand(final CommandType type, final String text, final int value2) { + SideEffect output = new SideEffect(); + output.application = (ClientData clientData) -> { + ClientData.Command command = GameDataUtils.addCommand(clientData.gameData()); + command.setType(type); + command.setValue1(GameDataUtils.addString(clientData.gameData(), text)); + command.setValue2(value2); + }; + return output; + } + 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) { SideEffect output = new SideEffect(); output.application = (ClientData clientData) -> { @@ -59,4 +70,22 @@ static SideEffect addShape(final ShapeType type, final CoordinateType coordType, }; return output; } + + 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) { + SideEffect output = new SideEffect(); + output.application = (ClientData clientData) -> { + ClientData.Shape shape = GameDataUtils.addShape(clientData.gameData()); + shape.setType(type); + shape.setCtype(coordType); + shape.setX1(x1); + shape.setY1(y1); + shape.setX2(x2); + shape.setY2(y2); + shape.setExtra1(GameDataUtils.addString(clientData.gameData(), text)); + shape.setExtra2(extra2); + shape.setColor(color); + shape.setIsSolid(isSolid); + }; + return output; + } } \ No newline at end of file diff --git a/src/main/java/bwapi/SideEffectQueue.java b/src/main/java/bwapi/SideEffectQueue.java index 4e4c394a..a8a4b974 100644 --- a/src/main/java/bwapi/SideEffectQueue.java +++ b/src/main/java/bwapi/SideEffectQueue.java @@ -5,7 +5,7 @@ /** * Queue of intended bot interactions with the game, to be flushed as JBWAPI returns control to StarCraft after a frame. */ -public class SideEffectQueue { +class SideEffectQueue { private ArrayList queue = new ArrayList<>(); From 931beb1388e1cb324d29476a5f2cf50d19d5fc65 Mon Sep 17 00:00:00 2001 From: dgant Date: Thu, 30 Jul 2020 13:49:01 -0400 Subject: [PATCH 09/56] Replaced sleeps with awaits --- src/main/java/bwapi/BWClient.java | 38 ++++----- src/main/java/bwapi/BotWrapper.java | 55 ++++++++---- src/main/java/bwapi/FrameBuffer.java | 101 ++++++++++++++++++----- src/main/java/bwapi/SideEffectQueue.java | 2 +- 4 files changed, 138 insertions(+), 58 deletions(-) diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index bf240f16..a6050039 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -50,10 +50,10 @@ public void startGame(BWClientConfiguration configuration) { client.reconnect(); do { - ClientData.GameData gameData = client.clientData().gameData(); - long lastUpdateTimestampMillis = 0; + ClientData liveClientData = client.clientData(); + ClientData.GameData liveGameData = liveClientData.gameData(); System.out.println("Client: Beginning game loop"); - while (!gameData.isInGame()) { + while (!liveGameData.isInGame()) { botWrapper = null; if (client.isConnected()) { System.out.println("Client: Not in game; Connected."); @@ -61,37 +61,29 @@ public void startGame(BWClientConfiguration configuration) { System.out.println("Client: Not in game; Not connected."); return; } - lastUpdateTimestampMillis = System.currentTimeMillis(); client.update(); } - while (gameData.isInGame()) { - System.out.println("Client: In game on frame " + gameData.getFrameCount()); + while (liveGameData.isInGame()) { + System.out.println("Client: In game on frame " + liveGameData.getFrameCount()); if (botWrapper == null) { botWrapper = new BotWrapper(configuration, eventListener, client.mapFile()); } botWrapper.step(); - // Proceed immediately once framebuffer is empty + // Proceed immediately once frame buffer is empty // Otherwise, wait for bot to catch up - // TODO: Replace with a wait instead of a sleep - while(true) { - if (botWrapper.botIdle()) { - System.out.println("Client: Proceeding because bot is idle."); - break; + botWrapper.idleLock.lock(); + try { + while ( ! botWrapper.botIdle()) { // and we have time remaining (with optional extra for frame 0) or no room left in frame buffer + System.out.println("Client: Waiting for idle bot on frame " + liveGameData.getFrameCount()); + botWrapper.idleCondition.awaitUninterruptibly(); } - long frameDurationMillis = System.currentTimeMillis() - lastUpdateTimestampMillis; - if (frameDurationMillis > configuration.asyncFrameDurationMillis && (client.clientData().gameData().getFrameCount() > 0 || ! configuration.asyncWaitOnFrameZero)) { - System.out.println("Client: Proceeding because frame " + botWrapper.getGame().getFrameCount() + " lasted " + frameDurationMillis + "ms"); - break; - } - try { Thread.sleep(1); } catch (InterruptedException ignored) {} + } finally { + botWrapper.idleLock.unlock(); } - long currentTimeMillis = System.currentTimeMillis(); - long frameDurationMillis = currentTimeMillis - lastUpdateTimestampMillis; - lastUpdateTimestampMillis = currentTimeMillis; - System.out.println("Client: Ending frame after " + frameDurationMillis + "ms"); - getGame().sideEffects.flush(client.clientData()); + System.out.println("Client: Sending commands on frame " + liveGameData.getFrameCount()); + getGame().sideEffects.flushTo(liveClientData); client.update(); if (!client.isConnected()) { System.out.println("Reconnecting..."); diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java index d1ab4075..2bb1392b 100644 --- a/src/main/java/bwapi/BotWrapper.java +++ b/src/main/java/bwapi/BotWrapper.java @@ -26,6 +26,9 @@ of this software and associated documentation files (the "Software"), to deal package bwapi; import java.nio.ByteBuffer; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; /** * Manages invocation of bot event handlers @@ -36,7 +39,10 @@ class BotWrapper { private final Game game; private FrameBuffer frameBuffer = null; private Thread botThread = null; - private volatile boolean idle = false; + private boolean idle = false; + + Lock idleLock = new ReentrantLock(); + Condition idleCondition = idleLock.newCondition(); BotWrapper(BWClientConfiguration configuration, BWEventListener eventListener, ByteBuffer dataSource) { this.configuration = configuration; @@ -62,13 +68,6 @@ boolean botIdle() { return idle || ! configuration.async; } - /** - * True if there is a frame buffer with free capacity. - */ - boolean canBuffer() { - return configuration.async && ! frameBuffer.full(); - } - void step() { if (configuration.async) { frameBuffer.enqueueFrame(); @@ -76,16 +75,42 @@ void step() { botThread = new Thread(() -> { //noinspection InfiniteLoopStatement while(true) { - while(frameBuffer.empty()) try { - idle = true; - Thread.sleep(0, 100); - } catch (InterruptedException ignored) {} - idle = false; - System.out.println("Bot thread: Dequeuing frame. There are " + frameBuffer.framesBuffered() + " frames buffered."); + // Await non-empty frame buffer + frameBuffer.lockCapacity.lock(); + try { + while(frameBuffer.empty()) { + // Signal idleness + idleLock.lock(); + try { + idle = true; + idleCondition.signalAll(); + } finally { + idleLock.unlock(); + } + frameBuffer.conditionEmpty.awaitUninterruptibly(); + } + } finally { + frameBuffer.lockCapacity.unlock(); + } + + // Signal non-idleness + idleLock.lock(); + try { + idle = false; + idleCondition.signalAll(); + } finally { + idleLock.unlock(); + } + game.clientData().setBuffer(frameBuffer.dequeueFrame()); - System.out.println("Bot thread: Handling events."); + System.out.println("Bot thread: Handling events while " + frameBuffer.framesBuffered() + " frames behind."); handleEvents(); System.out.println("Bot thread: Handled events."); + + if ( ! game.clientData().gameData().isInGame()) { + System.out.println("Bot thread: Ending because game is over."); + return; + } }}); botThread.setName("JBWAPI Bot"); botThread.start(); diff --git a/src/main/java/bwapi/FrameBuffer.java b/src/main/java/bwapi/FrameBuffer.java index 6e7999e4..7fab8936 100644 --- a/src/main/java/bwapi/FrameBuffer.java +++ b/src/main/java/bwapi/FrameBuffer.java @@ -27,6 +27,9 @@ of this software and associated documentation files (the "Software"), to deal import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; /** * Circular buffer of game states. @@ -40,7 +43,11 @@ class FrameBuffer { private ArrayList dataBuffer = new ArrayList<>(); // Synchronization locks - private Object stepCount = new Object(); + private final Lock writeLock = new ReentrantLock(); + private final Lock readLock = writeLock; + final Lock lockCapacity = new ReentrantLock(); + final Condition conditionFull = lockCapacity.newCondition(); + final Condition conditionEmpty = lockCapacity.newCondition(); FrameBuffer(int size, ByteBuffer source) { this.size = size; @@ -60,23 +67,33 @@ synchronized int framesBuffered() { /** * @return Whether the frame buffer is empty and has no frames available for the bot to consume. */ - synchronized boolean empty() { - return framesBuffered() <= 0; + boolean empty() { + lockCapacity.lock(); + try { + return framesBuffered() <= 0; + } finally { + lockCapacity.unlock(); + } } /** * @return Whether the frame buffer is full and can not buffer any additional frames. * When the frame buffer is full, JBWAPI must wait for the bot to complete a frame before returning control to StarCraft. */ - synchronized boolean full() { - return framesBuffered() >= size - 1; + boolean full() { + lockCapacity.lock(); + try { + return framesBuffered() >= size - 1; + } finally { + lockCapacity.unlock(); + } } - synchronized private int indexGame() { + private int indexGame() { return stepGame % size; } - synchronized private int indexBot() { + private int indexBot() { return stepBot % size; } @@ -84,23 +101,69 @@ synchronized private int indexBot() { * Copy dataBuffer from shared memory into the head of the frame buffer. */ void enqueueFrame() { - while(full()) try { Thread.sleep(0, 100); } catch (InterruptedException ignored) {} - System.out.println("FrameBuffer: Enqueuing buffer " + indexGame() + " on game step #" + stepGame + " with " + framesBuffered() + " frames buffered."); - ByteBuffer dataTarget = dataBuffer.get(indexGame()); - dataSource.rewind(); - dataTarget.rewind(); - dataTarget.put(dataSource); - ++stepGame; + // In practice we don't particularly expect multiple threads to write to this, but support it just to be safe. + writeLock.lock(); + try { + // Wait for the buffer to have space to enqueue + lockCapacity.lock(); + try { + while (full()) { + conditionFull.awaitUninterruptibly(); + } + } finally { + lockCapacity.unlock(); + } + + System.out.println("FrameBuffer: Enqueuing buffer " + indexGame() + " on game step #" + stepGame + " with " + framesBuffered() + " frames buffered."); + ByteBuffer dataTarget = dataBuffer.get(indexGame()); + dataSource.rewind(); + dataTarget.rewind(); + dataTarget.put(dataSource); + ++stepGame; + + // Notify anyone waiting for something to dequeue + lockCapacity.lock(); + try { + conditionEmpty.signalAll(); + } finally { + lockCapacity.unlock(); + } + } finally { + writeLock.unlock(); + } } /** * Points the bot to the next frame in the buffer. */ ByteBuffer dequeueFrame() { - while(empty()) try { Thread.sleep(0, 100); } catch (InterruptedException ignored) {} - System.out.println("FrameBuffer: Dequeuing buffer " + indexBot() + " on bot step #" + stepBot); - ByteBuffer output = dataBuffer.get(indexBot()); - ++stepBot; - return output; + // In practice we don't particularly expect multiple threads to read from this, but support it just to be safe. + readLock.lock(); + try { + // Wait for the buffer to have something to dequeue + lockCapacity.lock(); + try { + while (empty()) { + conditionEmpty.awaitUninterruptibly(); + } + } finally { + lockCapacity.unlock(); + } + + System.out.println("FrameBuffer: Dequeuing buffer " + indexBot() + " on bot step #" + stepBot); + ByteBuffer output = dataBuffer.get(indexBot()); + ++stepBot; + + // Notify anyone waiting for capacity to enqueue + lockCapacity.lock(); + try { + conditionFull.signalAll(); + return output; + } finally { + lockCapacity.unlock(); + } + } finally { + readLock.unlock(); + } } } diff --git a/src/main/java/bwapi/SideEffectQueue.java b/src/main/java/bwapi/SideEffectQueue.java index a8a4b974..f07b1042 100644 --- a/src/main/java/bwapi/SideEffectQueue.java +++ b/src/main/java/bwapi/SideEffectQueue.java @@ -25,7 +25,7 @@ synchronized void enqueue(SideEffect sideEffect) { * @param liveClientData * The live game frame's data, using the BWAPI shared memory. */ - synchronized void flush(ClientData liveClientData) { + synchronized void flushTo(ClientData liveClientData) { queue.forEach(x -> x.apply(liveClientData)); queue.clear(); } From 5c9dc3658c19d83899b48c7c3e1d159f31a30a0d Mon Sep 17 00:00:00 2001 From: dgant Date: Thu, 30 Jul 2020 16:41:00 -0400 Subject: [PATCH 10/56] 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. --- src/main/java/bwapi/BWClient.java | 16 ++++++------ src/main/java/bwapi/BotWrapper.java | 31 +++++++++++++++--------- src/main/java/bwapi/FrameBuffer.java | 12 +++++++-- src/main/java/bwapi/SideEffect.java | 31 ++++++++++++------------ src/main/java/bwapi/SideEffectQueue.java | 6 ++--- 5 files changed, 55 insertions(+), 41 deletions(-) diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index a6050039..fe7da978 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -46,15 +46,14 @@ public void startGame(boolean autoContinue) { */ public void startGame(BWClientConfiguration configuration) { configuration.validate(); + botWrapper = new BotWrapper(configuration, eventListener); Client client = new Client(configuration); client.reconnect(); do { - ClientData liveClientData = client.clientData(); - ClientData.GameData liveGameData = liveClientData.gameData(); System.out.println("Client: Beginning game loop"); + ClientData.GameData liveGameData = client.clientData().gameData(); while (!liveGameData.isInGame()) { - botWrapper = null; if (client.isConnected()) { System.out.println("Client: Not in game; Connected."); } else { @@ -62,12 +61,13 @@ public void startGame(BWClientConfiguration configuration) { return; } client.update(); + if (liveGameData.isInGame()) { + botWrapper.initialize(client.mapFile()); + } } while (liveGameData.isInGame()) { System.out.println("Client: In game on frame " + liveGameData.getFrameCount()); - if (botWrapper == null) { - botWrapper = new BotWrapper(configuration, eventListener, client.mapFile()); - } + botWrapper.step(); // Proceed immediately once frame buffer is empty @@ -83,16 +83,14 @@ public void startGame(BWClientConfiguration configuration) { } System.out.println("Client: Sending commands on frame " + liveGameData.getFrameCount()); - getGame().sideEffects.flushTo(liveClientData); + getGame().sideEffects.flushTo(liveGameData); client.update(); if (!client.isConnected()) { System.out.println("Reconnecting..."); client.reconnect(); } } - // TODO: Before exiting give async bot time to complete onEnd(), maybe via thread.join(). - } while (configuration.autoContinue); // lgtm [java/constant-loop-condition] } } \ No newline at end of file diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java index 2bb1392b..6d2c798a 100644 --- a/src/main/java/bwapi/BotWrapper.java +++ b/src/main/java/bwapi/BotWrapper.java @@ -34,29 +34,38 @@ of this software and associated documentation files (the "Software"), to deal * Manages invocation of bot event handlers */ class BotWrapper { + private final ClientData liveClientData = new ClientData(); private final BWClientConfiguration configuration; private final BWEventListener eventListener; - private final Game game; - private FrameBuffer frameBuffer = null; - private Thread botThread = null; + private final FrameBuffer frameBuffer; + private Game game; + private Thread botThread; private boolean idle = false; Lock idleLock = new ReentrantLock(); Condition idleCondition = idleLock.newCondition(); - BotWrapper(BWClientConfiguration configuration, BWEventListener eventListener, ByteBuffer dataSource) { + BotWrapper(BWClientConfiguration configuration, BWEventListener eventListener) { this.configuration = configuration; this.eventListener = eventListener; + frameBuffer = configuration.async ? new FrameBuffer(configuration.asyncFrameBufferSize) : null; + } - ClientData currentClientData = new ClientData(); - currentClientData.setBuffer(dataSource); - game = new Game(currentClientData); - - if (configuration.async) { - frameBuffer = new FrameBuffer(configuration.asyncFrameBufferSize, dataSource); - } + /** + * Resets the BotWrapper for a new game. + */ + void initialize(ByteBuffer dataSource) { + frameBuffer.initialize(dataSource); + game = new Game(liveClientData); + liveClientData.setBuffer(dataSource); + botThread = null; + idle = false; } + /** + * @return The Game object used by the bot + * In asynchronous mode this Game object may point at a copy of a previous frame. + */ Game getGame() { return game; } diff --git a/src/main/java/bwapi/FrameBuffer.java b/src/main/java/bwapi/FrameBuffer.java index 7fab8936..f771cc16 100644 --- a/src/main/java/bwapi/FrameBuffer.java +++ b/src/main/java/bwapi/FrameBuffer.java @@ -49,14 +49,22 @@ class FrameBuffer { final Condition conditionFull = lockCapacity.newCondition(); final Condition conditionEmpty = lockCapacity.newCondition(); - FrameBuffer(int size, ByteBuffer source) { + FrameBuffer(int size) { this.size = size; - this.dataSource = source; while(dataBuffer.size() < size) { dataBuffer.add(ByteBuffer.allocateDirect(ClientData.GameData.SIZE)); } } + /** + * Resets for a new game + */ + void initialize(ByteBuffer dataSource) { + this.dataSource = dataSource; + stepGame = 0; + stepBot = 0; + } + /** * @return The number of frames currently buffered ahead of the bot's current frame */ diff --git a/src/main/java/bwapi/SideEffect.java b/src/main/java/bwapi/SideEffect.java index 109c62de..3914f2d3 100644 --- a/src/main/java/bwapi/SideEffect.java +++ b/src/main/java/bwapi/SideEffect.java @@ -1,7 +1,6 @@ package bwapi; import java.util.function.Consumer; -import java.util.function.Function; /** * A side effect is an interaction that a bot attempts to have with the game. @@ -9,18 +8,18 @@ */ class SideEffect { - private Consumer application; + private Consumer application; - void apply(ClientData clientData) { - application.accept(clientData); + void apply(ClientData.GameData gameData) { + application.accept(gameData); } private SideEffect() {} static SideEffect addUnitCommand(final int type, final int unit, final int target, final int x, final int y, final int extra) { SideEffect output = new SideEffect(); - output.application = (ClientData clientData) -> { - ClientData.UnitCommand unitCommand = GameDataUtils.addUnitCommand(clientData.gameData()); + output.application = (ClientData.GameData gameData) -> { + ClientData.UnitCommand unitCommand = GameDataUtils.addUnitCommand(gameData); unitCommand.setTid(type); unitCommand.setUnitIndex(unit); unitCommand.setTargetIndex(target); @@ -33,8 +32,8 @@ static SideEffect addUnitCommand(final int type, final int unit, final int targe static SideEffect addCommand(final CommandType type, final int value1, final int value2) { SideEffect output = new SideEffect(); - output.application = (ClientData clientData) -> { - ClientData.Command command = GameDataUtils.addCommand(clientData.gameData()); + output.application = (ClientData.GameData gameData) -> { + ClientData.Command command = GameDataUtils.addCommand(gameData); command.setType(type); command.setValue1(value1); command.setValue2(value2); @@ -44,10 +43,10 @@ static SideEffect addCommand(final CommandType type, final int value1, final int static SideEffect addCommand(final CommandType type, final String text, final int value2) { SideEffect output = new SideEffect(); - output.application = (ClientData clientData) -> { - ClientData.Command command = GameDataUtils.addCommand(clientData.gameData()); + output.application = (ClientData.GameData gameData) -> { + ClientData.Command command = GameDataUtils.addCommand(gameData); command.setType(type); - command.setValue1(GameDataUtils.addString(clientData.gameData(), text)); + command.setValue1(GameDataUtils.addString(gameData, text)); command.setValue2(value2); }; return output; @@ -55,8 +54,8 @@ static SideEffect addCommand(final CommandType type, final String text, final in 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) { SideEffect output = new SideEffect(); - output.application = (ClientData clientData) -> { - ClientData.Shape shape = GameDataUtils.addShape(clientData.gameData()); + output.application = (ClientData.GameData gameData) -> { + ClientData.Shape shape = GameDataUtils.addShape(gameData); shape.setType(type); shape.setCtype(coordType); shape.setX1(x1); @@ -73,15 +72,15 @@ static SideEffect addShape(final ShapeType type, final CoordinateType coordType, 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) { SideEffect output = new SideEffect(); - output.application = (ClientData clientData) -> { - ClientData.Shape shape = GameDataUtils.addShape(clientData.gameData()); + output.application = (ClientData.GameData gameData) -> { + ClientData.Shape shape = GameDataUtils.addShape(gameData); shape.setType(type); shape.setCtype(coordType); shape.setX1(x1); shape.setY1(y1); shape.setX2(x2); shape.setY2(y2); - shape.setExtra1(GameDataUtils.addString(clientData.gameData(), text)); + shape.setExtra1(GameDataUtils.addString(gameData, text)); shape.setExtra2(extra2); shape.setColor(color); shape.setIsSolid(isSolid); diff --git a/src/main/java/bwapi/SideEffectQueue.java b/src/main/java/bwapi/SideEffectQueue.java index f07b1042..8dc49b8f 100644 --- a/src/main/java/bwapi/SideEffectQueue.java +++ b/src/main/java/bwapi/SideEffectQueue.java @@ -22,11 +22,11 @@ synchronized void enqueue(SideEffect sideEffect) { /** * Applies all enqueued side effects to the current BWAPI frame. * - * @param liveClientData + * @param liveGameData * The live game frame's data, using the BWAPI shared memory. */ - synchronized void flushTo(ClientData liveClientData) { - queue.forEach(x -> x.apply(liveClientData)); + synchronized void flushTo(ClientData.GameData liveGameData) { + queue.forEach(x -> x.apply(liveGameData)); queue.clear(); } } From 341056dcabc13293efdab1eee626f161bab9eec0 Mon Sep 17 00:00:00 2001 From: dgant Date: Thu, 30 Jul 2020 23:27:18 -0400 Subject: [PATCH 11/56] Fixed end-of-game behavior. Moved synchronization out of BWClient and removed 'idleness' concept from BotWrapper. --- src/main/java/bwapi/BWClient.java | 22 ++---- src/main/java/bwapi/BotWrapper.java | 103 ++++++++++++--------------- src/main/java/bwapi/FrameBuffer.java | 96 ++++++++++--------------- 3 files changed, 88 insertions(+), 133 deletions(-) diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index fe7da978..d136eeee 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -62,27 +62,13 @@ public void startGame(BWClientConfiguration configuration) { } client.update(); if (liveGameData.isInGame()) { - botWrapper.initialize(client.mapFile()); + botWrapper.startNewGame(client.mapFile()); } } while (liveGameData.isInGame()) { System.out.println("Client: In game on frame " + liveGameData.getFrameCount()); - - botWrapper.step(); - - // Proceed immediately once frame buffer is empty - // Otherwise, wait for bot to catch up - botWrapper.idleLock.lock(); - try { - while ( ! botWrapper.botIdle()) { // and we have time remaining (with optional extra for frame 0) or no room left in frame buffer - System.out.println("Client: Waiting for idle bot on frame " + liveGameData.getFrameCount()); - botWrapper.idleCondition.awaitUninterruptibly(); - } - } finally { - botWrapper.idleLock.unlock(); - } - - System.out.println("Client: Sending commands on frame " + liveGameData.getFrameCount()); + botWrapper.onFrame(); + System.out.println("Client: Sending commands for frame " + liveGameData.getFrameCount()); getGame().sideEffects.flushTo(liveGameData); client.update(); if (!client.isConnected()) { @@ -90,7 +76,7 @@ public void startGame(BWClientConfiguration configuration) { client.reconnect(); } } - // TODO: Before exiting give async bot time to complete onEnd(), maybe via thread.join(). + botWrapper.endGame(); } while (configuration.autoContinue); // lgtm [java/constant-loop-condition] } } \ No newline at end of file diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java index 6d2c798a..be53a402 100644 --- a/src/main/java/bwapi/BotWrapper.java +++ b/src/main/java/bwapi/BotWrapper.java @@ -26,9 +26,6 @@ of this software and associated documentation files (the "Software"), to deal package bwapi; import java.nio.ByteBuffer; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; /** * Manages invocation of bot event handlers @@ -40,10 +37,7 @@ class BotWrapper { private final FrameBuffer frameBuffer; private Game game; private Thread botThread; - private boolean idle = false; - - Lock idleLock = new ReentrantLock(); - Condition idleCondition = idleLock.newCondition(); + private boolean gameOver; BotWrapper(BWClientConfiguration configuration, BWEventListener eventListener) { this.configuration = configuration; @@ -54,12 +48,12 @@ class BotWrapper { /** * Resets the BotWrapper for a new game. */ - void initialize(ByteBuffer dataSource) { + void startNewGame(ByteBuffer dataSource) { frameBuffer.initialize(dataSource); game = new Game(liveClientData); liveClientData.setBuffer(dataSource); botThread = null; - idle = false; + gameOver = false; } /** @@ -71,68 +65,63 @@ Game getGame() { } /** - * True if the bot has handled all enqueued frames and is waiting for a new frame from StarCraft. + * Handles the arrival of a new frame from BWAPI */ - boolean botIdle() { - return idle || ! configuration.async; - } - - void step() { + void onFrame() { if (configuration.async) { - frameBuffer.enqueueFrame(); if (botThread == null) { - botThread = new Thread(() -> { - //noinspection InfiniteLoopStatement - while(true) { - // Await non-empty frame buffer - frameBuffer.lockCapacity.lock(); - try { - while(frameBuffer.empty()) { - // Signal idleness - idleLock.lock(); - try { - idle = true; - idleCondition.signalAll(); - } finally { - idleLock.unlock(); - } - frameBuffer.conditionEmpty.awaitUninterruptibly(); - } - } finally { - frameBuffer.lockCapacity.unlock(); - } - - // Signal non-idleness - idleLock.lock(); - try { - idle = false; - idleCondition.signalAll(); - } finally { - idleLock.unlock(); - } - - game.clientData().setBuffer(frameBuffer.dequeueFrame()); - System.out.println("Bot thread: Handling events while " + frameBuffer.framesBuffered() + " frames behind."); - handleEvents(); - System.out.println("Bot thread: Handled events."); - - if ( ! game.clientData().gameData().isInGame()) { - System.out.println("Bot thread: Ending because game is over."); - return; - } - }}); + System.out.println("Creating bot thread"); + botThread = createBotThread(); botThread.setName("JBWAPI Bot"); botThread.start(); } + frameBuffer.enqueueFrame(); + frameBuffer.lockSize.lock(); + try { + while ( ! frameBuffer.empty()) frameBuffer.conditionSize.awaitUninterruptibly(); + } finally { + frameBuffer.lockSize.unlock(); + } } else { handleEvents(); } } + /** + * Allows an asynchronous bot time to finish operation + */ + void endGame() { + if (botThread != null) { + try { + botThread.join(); + } catch (InterruptedException ignored) {} + } + } + + private Thread createBotThread() { + return new Thread(() -> { + while ( ! gameOver) { + frameBuffer.lockSize.lock(); + try { while (frameBuffer.empty()) frameBuffer.conditionSize.awaitUninterruptibly(); } finally { frameBuffer.lockSize.unlock(); } + + game.clientData().setBuffer(frameBuffer.peek()); + System.out.println("Bot thread: Handling events."); + handleEvents(); + System.out.println("Bot thread: Handled events."); + frameBuffer.dequeue(); + } + System.out.println("Bot thread: Ending because game is over."); + }); + } + private void handleEvents() { ClientData.GameData gameData = game.clientData().gameData(); for (int i = 0; i < gameData.getEventCount(); i++) { - EventHandler.operation(eventListener, game, gameData.getEvents(i)); + ClientData.Event event = gameData.getEvents(i); + EventHandler.operation(eventListener, game, event); + if (event.getType() == EventType.MatchEnd) { + gameOver = true; + } } } } diff --git a/src/main/java/bwapi/FrameBuffer.java b/src/main/java/bwapi/FrameBuffer.java index f771cc16..2ee7a19b 100644 --- a/src/main/java/bwapi/FrameBuffer.java +++ b/src/main/java/bwapi/FrameBuffer.java @@ -43,11 +43,10 @@ class FrameBuffer { private ArrayList dataBuffer = new ArrayList<>(); // Synchronization locks - private final Lock writeLock = new ReentrantLock(); - private final Lock readLock = writeLock; - final Lock lockCapacity = new ReentrantLock(); - final Condition conditionFull = lockCapacity.newCondition(); - final Condition conditionEmpty = lockCapacity.newCondition(); + private final Lock lockWrite = new ReentrantLock(); + private final Lock lockRead = lockWrite; + final Lock lockSize = new ReentrantLock(); + final Condition conditionSize = lockSize.newCondition(); FrameBuffer(int size) { this.size = size; @@ -76,11 +75,11 @@ synchronized int framesBuffered() { * @return Whether the frame buffer is empty and has no frames available for the bot to consume. */ boolean empty() { - lockCapacity.lock(); + lockSize.lock(); try { return framesBuffered() <= 0; } finally { - lockCapacity.unlock(); + lockSize.unlock(); } } @@ -89,11 +88,11 @@ boolean empty() { * When the frame buffer is full, JBWAPI must wait for the bot to complete a frame before returning control to StarCraft. */ boolean full() { - lockCapacity.lock(); + lockSize.lock(); try { return framesBuffered() >= size - 1; } finally { - lockCapacity.unlock(); + lockSize.unlock(); } } @@ -109,69 +108,50 @@ private int indexBot() { * Copy dataBuffer from shared memory into the head of the frame buffer. */ void enqueueFrame() { - // In practice we don't particularly expect multiple threads to write to this, but support it just to be safe. - writeLock.lock(); + lockWrite.lock(); try { - // Wait for the buffer to have space to enqueue - lockCapacity.lock(); - try { - while (full()) { - conditionFull.awaitUninterruptibly(); - } - } finally { - lockCapacity.unlock(); - } - - System.out.println("FrameBuffer: Enqueuing buffer " + indexGame() + " on game step #" + stepGame + " with " + framesBuffered() + " frames buffered."); + lockSize.lock(); + try { while (full()) conditionSize.awaitUninterruptibly(); } finally { lockSize.unlock(); }; ByteBuffer dataTarget = dataBuffer.get(indexGame()); dataSource.rewind(); dataTarget.rewind(); dataTarget.put(dataSource); - ++stepGame; - // Notify anyone waiting for something to dequeue - lockCapacity.lock(); + lockSize.lock(); try { - conditionEmpty.signalAll(); - } finally { - lockCapacity.unlock(); - } - } finally { - writeLock.unlock(); - } + ++stepGame; + System.out.println("FrameBuffer: Enqueued buffer " + indexGame() + " on game step #" + stepGame); + if (framesBuffered() > 0) { + System.out.println("FrameBuffer: There are now " + framesBuffered() + " frames buffered."); + } + conditionSize.signalAll(); + } finally { lockSize.unlock(); } + } finally { lockWrite.unlock(); } } /** - * Points the bot to the next frame in the buffer. + * Peeks the frontmost value in the buffer. */ - ByteBuffer dequeueFrame() { - // In practice we don't particularly expect multiple threads to read from this, but support it just to be safe. - readLock.lock(); + ByteBuffer peek() { + lockSize.lock(); try { - // Wait for the buffer to have something to dequeue - lockCapacity.lock(); - try { - while (empty()) { - conditionEmpty.awaitUninterruptibly(); - } - } finally { - lockCapacity.unlock(); - } + while(empty()) conditionSize.awaitUninterruptibly(); + System.out.println("FrameBuffer: Sharing buffer " + indexBot() + " on bot step #" + stepBot); + return dataBuffer.get(indexBot()); + } finally { lockSize.unlock(); } + } + + /** + * Removes the frontmost frame in the buffer. + */ + void dequeue() { + lockSize.lock(); + try { + while(empty()) conditionSize.awaitUninterruptibly(); System.out.println("FrameBuffer: Dequeuing buffer " + indexBot() + " on bot step #" + stepBot); - ByteBuffer output = dataBuffer.get(indexBot()); ++stepBot; - - // Notify anyone waiting for capacity to enqueue - lockCapacity.lock(); - try { - conditionFull.signalAll(); - return output; - } finally { - lockCapacity.unlock(); - } - } finally { - readLock.unlock(); - } + conditionSize.signalAll(); + } finally { lockSize.unlock(); } } } From 30e7a52ccc6417e15b0614f35df6aab15503398c Mon Sep 17 00:00:00 2001 From: dgant Date: Fri, 31 Jul 2020 01:09:18 -0400 Subject: [PATCH 12/56] Implemented frame timeouts. --- src/main/java/bwapi/BWClient.java | 1 + src/main/java/bwapi/BWClientConfiguration.java | 8 ++++---- src/main/java/bwapi/BotWrapper.java | 13 ++++++++++++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index d136eeee..1676c5c0 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -47,6 +47,7 @@ public void startGame(boolean autoContinue) { public void startGame(BWClientConfiguration configuration) { configuration.validate(); botWrapper = new BotWrapper(configuration, eventListener); + Client client = new Client(configuration); client.reconnect(); diff --git a/src/main/java/bwapi/BWClientConfiguration.java b/src/main/java/bwapi/BWClientConfiguration.java index 4664cc62..c9c97063 100644 --- a/src/main/java/bwapi/BWClientConfiguration.java +++ b/src/main/java/bwapi/BWClientConfiguration.java @@ -29,10 +29,10 @@ public class BWClientConfiguration { public boolean async = false; /** - * If JBWAPI detects that this much time (in milliseconds) has passed since a bot's event handlers began, returns control back to BWAPI. + * If JBWAPI detects that this much time (in nanoseconds) has passed since a bot's event handlers began, returns control back to BWAPI. * Real-time human play typically uses the "fastest" game speed, which has 42.86ms (42,860ns) between frames. */ - public int asyncFrameDurationMillis = 40; + public int asyncFrameDurationNanos = 40000; /** * The maximum number of frames to buffer while waiting on a bot. @@ -53,8 +53,8 @@ public class BWClientConfiguration { * Checks that the configuration is in a valid state. Throws an IllegalArgumentException if it isn't. */ public void validate() { - if (async && asyncFrameDurationMillis < 0) { - throw new IllegalArgumentException("asyncFrameDurationMillis needs to be a non-negative number (it's how long JBWAPI waits for a bot response before returning control to BWAPI)."); + if (async && asyncFrameDurationNanos < 0) { + throw new IllegalArgumentException("asyncFrameDurationNanos needs to be a non-negative number (it's how long JBWAPI waits for a bot response before returning control to BWAPI)."); } if (async && asyncFrameBufferSize < 1) { throw new IllegalArgumentException("asyncFrameBufferSize needs to be a positive number (There needs to be at least one frame buffer)."); diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java index be53a402..a16fac4d 100644 --- a/src/main/java/bwapi/BotWrapper.java +++ b/src/main/java/bwapi/BotWrapper.java @@ -69,6 +69,8 @@ Game getGame() { */ void onFrame() { if (configuration.async) { + long startNanos = System.nanoTime(); + long endNanos = startNanos + configuration.asyncFrameDurationNanos; if (botThread == null) { System.out.println("Creating bot thread"); botThread = createBotThread(); @@ -78,7 +80,16 @@ void onFrame() { frameBuffer.enqueueFrame(); frameBuffer.lockSize.lock(); try { - while ( ! frameBuffer.empty()) frameBuffer.conditionSize.awaitUninterruptibly(); + while (frameBuffer.empty()) { + if (configuration.asyncWaitOnFrameZero && liveClientData.gameData().getFrameCount() == 0) { + frameBuffer.conditionSize.await(); + } else { + long remainingNanos = endNanos - System.nanoTime(); + if (remainingNanos <= 0) break; + frameBuffer.conditionSize.awaitNanos(remainingNanos); + } + } + } catch(InterruptedException ignored) { } finally { frameBuffer.lockSize.unlock(); } From 5b3617445cfae8e4b62ddc8d0c4ac622580a4a91 Mon Sep 17 00:00:00 2001 From: dgant Date: Fri, 31 Jul 2020 16:17:55 -0400 Subject: [PATCH 13/56] Reduced stdout spamming --- src/main/java/bwapi/BWClient.java | 12 ++++++------ src/main/java/bwapi/BotWrapper.java | 8 ++++---- src/main/java/bwapi/FrameBuffer.java | 8 ++++---- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index 1676c5c0..6c58023e 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -52,13 +52,13 @@ public void startGame(BWClientConfiguration configuration) { client.reconnect(); do { - System.out.println("Client: Beginning game loop"); + //System.out.println("Client: Beginning game loop"); ClientData.GameData liveGameData = client.clientData().gameData(); while (!liveGameData.isInGame()) { if (client.isConnected()) { - System.out.println("Client: Not in game; Connected."); + //System.out.println("Client: Not in game; Connected."); } else { - System.out.println("Client: Not in game; Not connected."); + //System.out.println("Client: Not in game; Not connected."); return; } client.update(); @@ -67,13 +67,13 @@ public void startGame(BWClientConfiguration configuration) { } } while (liveGameData.isInGame()) { - System.out.println("Client: In game on frame " + liveGameData.getFrameCount()); + //System.out.println("Client: In game on frame " + liveGameData.getFrameCount()); botWrapper.onFrame(); - System.out.println("Client: Sending commands for frame " + liveGameData.getFrameCount()); + //System.out.println("Client: Sending commands for frame " + liveGameData.getFrameCount()); getGame().sideEffects.flushTo(liveGameData); client.update(); if (!client.isConnected()) { - System.out.println("Reconnecting..."); + //System.out.println("Reconnecting..."); client.reconnect(); } } diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java index a16fac4d..3acbc7d8 100644 --- a/src/main/java/bwapi/BotWrapper.java +++ b/src/main/java/bwapi/BotWrapper.java @@ -72,7 +72,7 @@ void onFrame() { long startNanos = System.nanoTime(); long endNanos = startNanos + configuration.asyncFrameDurationNanos; if (botThread == null) { - System.out.println("Creating bot thread"); + //System.out.println("Creating bot thread"); botThread = createBotThread(); botThread.setName("JBWAPI Bot"); botThread.start(); @@ -116,12 +116,12 @@ private Thread createBotThread() { try { while (frameBuffer.empty()) frameBuffer.conditionSize.awaitUninterruptibly(); } finally { frameBuffer.lockSize.unlock(); } game.clientData().setBuffer(frameBuffer.peek()); - System.out.println("Bot thread: Handling events."); + //System.out.println("Bot thread: Handling events."); handleEvents(); - System.out.println("Bot thread: Handled events."); + //System.out.println("Bot thread: Handled events."); frameBuffer.dequeue(); } - System.out.println("Bot thread: Ending because game is over."); + //System.out.println("Bot thread: Ending because game is over."); }); } diff --git a/src/main/java/bwapi/FrameBuffer.java b/src/main/java/bwapi/FrameBuffer.java index 2ee7a19b..90b06c75 100644 --- a/src/main/java/bwapi/FrameBuffer.java +++ b/src/main/java/bwapi/FrameBuffer.java @@ -120,9 +120,9 @@ void enqueueFrame() { lockSize.lock(); try { ++stepGame; - System.out.println("FrameBuffer: Enqueued buffer " + indexGame() + " on game step #" + stepGame); + //System.out.println("FrameBuffer: Enqueued buffer " + indexGame() + " on game step #" + stepGame); if (framesBuffered() > 0) { - System.out.println("FrameBuffer: There are now " + framesBuffered() + " frames buffered."); + //System.out.println("FrameBuffer: There are now " + framesBuffered() + " frames buffered."); } conditionSize.signalAll(); } finally { lockSize.unlock(); } @@ -136,7 +136,7 @@ ByteBuffer peek() { lockSize.lock(); try { while(empty()) conditionSize.awaitUninterruptibly(); - System.out.println("FrameBuffer: Sharing buffer " + indexBot() + " on bot step #" + stepBot); + //System.out.println("FrameBuffer: Sharing buffer " + indexBot() + " on bot step #" + stepBot); return dataBuffer.get(indexBot()); } finally { lockSize.unlock(); } @@ -149,7 +149,7 @@ void dequeue() { lockSize.lock(); try { while(empty()) conditionSize.awaitUninterruptibly(); - System.out.println("FrameBuffer: Dequeuing buffer " + indexBot() + " on bot step #" + stepBot); + //System.out.println("FrameBuffer: Dequeuing buffer " + indexBot() + " on bot step #" + stepBot); ++stepBot; conditionSize.signalAll(); } finally { lockSize.unlock(); } From 156c3822510aeb12e307e8367a6c02605023cfdb Mon Sep 17 00:00:00 2001 From: dgant Date: Fri, 7 Aug 2020 12:50:41 -0400 Subject: [PATCH 14/56] Fixed unit test which relied on ClientData being non-final for mocking --- src/main/java/bwapi/ClientData.java | 2 +- src/test/java/DumpToClient.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/bwapi/ClientData.java b/src/main/java/bwapi/ClientData.java index 2dca5ac7..f3723572 100644 --- a/src/main/java/bwapi/ClientData.java +++ b/src/main/java/bwapi/ClientData.java @@ -1,6 +1,6 @@ package bwapi; import java.nio.ByteBuffer; -final class ClientData { +class ClientData { private WrappedBuffer buffer; private GameData gameData; ClientData() { diff --git a/src/test/java/DumpToClient.java b/src/test/java/DumpToClient.java index 90d72377..191f023f 100644 --- a/src/test/java/DumpToClient.java +++ b/src/test/java/DumpToClient.java @@ -91,7 +91,7 @@ public static void main(String[] args) throws IOException { try (PrintWriter out = new PrintWriter(sw)) { out.println("package bwapi;"); out.println("import java.nio.ByteBuffer;"); - out.println("final class ClientData {"); + out.println("class ClientData {"); out.println(" private WrappedBuffer buffer;"); out.println(" private GameData gameData;"); out.println(" ClientData() {"); From 4cf39fcbe68deffe38298a22e31c4333b585be0e Mon Sep 17 00:00:00 2001 From: dgant Date: Fri, 7 Aug 2020 15:42:16 -0400 Subject: [PATCH 15/56] Removed commented-out printlns. Stubbed diagnostics configuration variable. --- src/main/java/bwapi/BWClient.java | 11 ++--------- src/main/java/bwapi/BWClientConfiguration.java | 6 ++++++ src/main/java/bwapi/BotWrapper.java | 4 ---- src/main/java/bwapi/FrameBuffer.java | 11 ++--------- 4 files changed, 10 insertions(+), 22 deletions(-) diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index 6c58023e..5b19442c 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -52,13 +52,9 @@ public void startGame(BWClientConfiguration configuration) { client.reconnect(); do { - //System.out.println("Client: Beginning game loop"); ClientData.GameData liveGameData = client.clientData().gameData(); while (!liveGameData.isInGame()) { - if (client.isConnected()) { - //System.out.println("Client: Not in game; Connected."); - } else { - //System.out.println("Client: Not in game; Not connected."); + if (!client.isConnected()) { return; } client.update(); @@ -67,17 +63,14 @@ public void startGame(BWClientConfiguration configuration) { } } while (liveGameData.isInGame()) { - //System.out.println("Client: In game on frame " + liveGameData.getFrameCount()); botWrapper.onFrame(); - //System.out.println("Client: Sending commands for frame " + liveGameData.getFrameCount()); getGame().sideEffects.flushTo(liveGameData); client.update(); if (!client.isConnected()) { - //System.out.println("Reconnecting..."); client.reconnect(); } } botWrapper.endGame(); - } while (configuration.autoContinue); // lgtm [java/constant-loop-condition] + } while (configuration.autoContinue); } } \ No newline at end of file diff --git a/src/main/java/bwapi/BWClientConfiguration.java b/src/main/java/bwapi/BWClientConfiguration.java index c9c97063..35131783 100644 --- a/src/main/java/bwapi/BWClientConfiguration.java +++ b/src/main/java/bwapi/BWClientConfiguration.java @@ -49,6 +49,12 @@ public class BWClientConfiguration { */ public boolean asyncWaitOnFrameZero = true; + /** + * Enables collection of diagnostics. + * When enabled, JBWAPI collects and publishes performance metrics. + */ + public boolean diagnosePerformance = false; + /** * Checks that the configuration is in a valid state. Throws an IllegalArgumentException if it isn't. */ diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java index 3acbc7d8..030371f3 100644 --- a/src/main/java/bwapi/BotWrapper.java +++ b/src/main/java/bwapi/BotWrapper.java @@ -72,7 +72,6 @@ void onFrame() { long startNanos = System.nanoTime(); long endNanos = startNanos + configuration.asyncFrameDurationNanos; if (botThread == null) { - //System.out.println("Creating bot thread"); botThread = createBotThread(); botThread.setName("JBWAPI Bot"); botThread.start(); @@ -116,12 +115,9 @@ private Thread createBotThread() { try { while (frameBuffer.empty()) frameBuffer.conditionSize.awaitUninterruptibly(); } finally { frameBuffer.lockSize.unlock(); } game.clientData().setBuffer(frameBuffer.peek()); - //System.out.println("Bot thread: Handling events."); handleEvents(); - //System.out.println("Bot thread: Handled events."); frameBuffer.dequeue(); } - //System.out.println("Bot thread: Ending because game is over."); }); } diff --git a/src/main/java/bwapi/FrameBuffer.java b/src/main/java/bwapi/FrameBuffer.java index 90b06c75..3fbb24be 100644 --- a/src/main/java/bwapi/FrameBuffer.java +++ b/src/main/java/bwapi/FrameBuffer.java @@ -44,7 +44,6 @@ class FrameBuffer { // Synchronization locks private final Lock lockWrite = new ReentrantLock(); - private final Lock lockRead = lockWrite; final Lock lockSize = new ReentrantLock(); final Condition conditionSize = lockSize.newCondition(); @@ -120,36 +119,30 @@ void enqueueFrame() { lockSize.lock(); try { ++stepGame; - //System.out.println("FrameBuffer: Enqueued buffer " + indexGame() + " on game step #" + stepGame); - if (framesBuffered() > 0) { - //System.out.println("FrameBuffer: There are now " + framesBuffered() + " frames buffered."); - } conditionSize.signalAll(); } finally { lockSize.unlock(); } } finally { lockWrite.unlock(); } } /** - * Peeks the frontmost value in the buffer. + * Peeks the front-most value in the buffer. */ ByteBuffer peek() { lockSize.lock(); try { while(empty()) conditionSize.awaitUninterruptibly(); - //System.out.println("FrameBuffer: Sharing buffer " + indexBot() + " on bot step #" + stepBot); return dataBuffer.get(indexBot()); } finally { lockSize.unlock(); } } /** - * Removes the frontmost frame in the buffer. + * Removes the front-most frame in the buffer. */ void dequeue() { lockSize.lock(); try { while(empty()) conditionSize.awaitUninterruptibly(); - //System.out.println("FrameBuffer: Dequeuing buffer " + indexBot() + " on bot step #" + stepBot); ++stepBot; conditionSize.signalAll(); } finally { lockSize.unlock(); } From 3159f73e7adb293aa8358785b9ab9a1176ad4e2e Mon Sep 17 00:00:00 2001 From: dgant Date: Sat, 8 Aug 2020 03:04:15 -0400 Subject: [PATCH 16/56] Fixed erroneous conversion between nanoseconds and milliseconds. First pass at performance metrics. --- src/main/java/bwapi/BWClient.java | 28 ++++-- .../java/bwapi/BWClientConfiguration.java | 8 +- src/main/java/bwapi/BotWrapper.java | 40 ++++++--- src/main/java/bwapi/Bullet.java | 4 +- src/main/java/bwapi/CommandTemp.java | 2 +- src/main/java/bwapi/FrameBuffer.java | 13 ++- src/main/java/bwapi/Game.java | 24 ++--- src/main/java/bwapi/PerformanceMetric.java | 87 +++++++++++++++++++ src/main/java/bwapi/PerformanceMetrics.java | 85 ++++++++++++++++++ src/main/java/bwapi/Unit.java | 12 +-- 10 files changed, 255 insertions(+), 48 deletions(-) create mode 100644 src/main/java/bwapi/PerformanceMetric.java create mode 100644 src/main/java/bwapi/PerformanceMetrics.java diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index 5b19442c..4bf28818 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -8,6 +8,7 @@ public class BWClient { private final BWEventListener eventListener; private BotWrapper botWrapper; + private PerformanceMetrics performanceMetrics; public BWClient(final BWEventListener eventListener) { Objects.requireNonNull(eventListener); @@ -22,6 +23,14 @@ public Game getGame() { return botWrapper == null ? null : botWrapper.getGame(); } + /** + * Returns JBWAPI performance metrics. + * Metrics will be mostly empty if metrics collection isn't timersEnabled in the bot configuration + */ + public PerformanceMetrics performanceMetrics() { + return performanceMetrics; + } + public void startGame() { BWClientConfiguration configuration = new BWClientConfiguration(); startGame(configuration); @@ -59,16 +68,21 @@ public void startGame(BWClientConfiguration configuration) { } client.update(); if (liveGameData.isInGame()) { - botWrapper.startNewGame(client.mapFile()); + performanceMetrics = new PerformanceMetrics(configuration); + botWrapper.startNewGame(client.mapFile(), performanceMetrics); } } while (liveGameData.isInGame()) { - botWrapper.onFrame(); - getGame().sideEffects.flushTo(liveGameData); - client.update(); - if (!client.isConnected()) { - client.reconnect(); - } + performanceMetrics.totalFrameDuration.time(() -> { + botWrapper.onFrame(); + performanceMetrics.flushSideEffects.time(() -> { + getGame().sideEffects.flushTo(liveGameData); + }); + client.update(); + if (!client.isConnected()) { + client.reconnect(); + } + }); } botWrapper.endGame(); } while (configuration.autoContinue); diff --git a/src/main/java/bwapi/BWClientConfiguration.java b/src/main/java/bwapi/BWClientConfiguration.java index 35131783..afeea481 100644 --- a/src/main/java/bwapi/BWClientConfiguration.java +++ b/src/main/java/bwapi/BWClientConfiguration.java @@ -32,7 +32,7 @@ public class BWClientConfiguration { * If JBWAPI detects that this much time (in nanoseconds) has passed since a bot's event handlers began, returns control back to BWAPI. * Real-time human play typically uses the "fastest" game speed, which has 42.86ms (42,860ns) between frames. */ - public int asyncFrameDurationNanos = 40000; + public int asyncFrameDurationMs = 40000; /** * The maximum number of frames to buffer while waiting on a bot. @@ -51,7 +51,7 @@ public class BWClientConfiguration { /** * Enables collection of diagnostics. - * When enabled, JBWAPI collects and publishes performance metrics. + * When timersEnabled, JBWAPI collects and publishes performance metrics. */ public boolean diagnosePerformance = false; @@ -59,8 +59,8 @@ public class BWClientConfiguration { * Checks that the configuration is in a valid state. Throws an IllegalArgumentException if it isn't. */ public void validate() { - if (async && asyncFrameDurationNanos < 0) { - throw new IllegalArgumentException("asyncFrameDurationNanos needs to be a non-negative number (it's how long JBWAPI waits for a bot response before returning control to BWAPI)."); + if (async && asyncFrameDurationMs < 0) { + throw new IllegalArgumentException("asyncFrameDurationMs needs to be a non-negative number (it's how long JBWAPI waits for a bot response before returning control to BWAPI)."); } if (async && asyncFrameBufferSize < 1) { throw new IllegalArgumentException("asyncFrameBufferSize needs to be a positive number (There needs to be at least one frame buffer)."); diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java index 030371f3..9f7883cc 100644 --- a/src/main/java/bwapi/BotWrapper.java +++ b/src/main/java/bwapi/BotWrapper.java @@ -38,6 +38,7 @@ class BotWrapper { private Game game; private Thread botThread; private boolean gameOver; + private PerformanceMetrics performanceMetrics; BotWrapper(BWClientConfiguration configuration, BWEventListener eventListener) { this.configuration = configuration; @@ -48,8 +49,9 @@ class BotWrapper { /** * Resets the BotWrapper for a new game. */ - void startNewGame(ByteBuffer dataSource) { - frameBuffer.initialize(dataSource); + void startNewGame(ByteBuffer dataSource, PerformanceMetrics performanceMetrics) { + frameBuffer.initialize(dataSource, performanceMetrics); + this.performanceMetrics = performanceMetrics; game = new Game(liveClientData); liveClientData.setBuffer(dataSource); botThread = null; @@ -70,16 +72,17 @@ Game getGame() { void onFrame() { if (configuration.async) { long startNanos = System.nanoTime(); - long endNanos = startNanos + configuration.asyncFrameDurationNanos; + long endNanos = startNanos + configuration.asyncFrameDurationMs * 1000000; if (botThread == null) { botThread = createBotThread(); botThread.setName("JBWAPI Bot"); botThread.start(); } - frameBuffer.enqueueFrame(); + performanceMetrics.copyingToBuffer.time(frameBuffer::enqueueFrame); frameBuffer.lockSize.lock(); try { while (frameBuffer.empty()) { + performanceMetrics.bwapiResponse.startTiming(); if (configuration.asyncWaitOnFrameZero && liveClientData.gameData().getFrameCount() == 0) { frameBuffer.conditionSize.await(); } else { @@ -88,6 +91,7 @@ void onFrame() { frameBuffer.conditionSize.awaitNanos(remainingNanos); } } + performanceMetrics.bwapiResponse.stopTiming(); } catch(InterruptedException ignored) { } finally { frameBuffer.lockSize.unlock(); @@ -112,9 +116,17 @@ private Thread createBotThread() { return new Thread(() -> { while ( ! gameOver) { frameBuffer.lockSize.lock(); - try { while (frameBuffer.empty()) frameBuffer.conditionSize.awaitUninterruptibly(); } finally { frameBuffer.lockSize.unlock(); } - + try { + while (frameBuffer.empty()) { + performanceMetrics.botIdle.startTiming(); + frameBuffer.conditionSize.awaitUninterruptibly(); + } + performanceMetrics.botIdle.stopTiming(); + } finally { + frameBuffer.lockSize.unlock(); + } game.clientData().setBuffer(frameBuffer.peek()); + performanceMetrics.frameBufferSize.record(frameBuffer.framesBuffered()); handleEvents(); frameBuffer.dequeue(); } @@ -122,13 +134,15 @@ private Thread createBotThread() { } private void handleEvents() { - ClientData.GameData gameData = game.clientData().gameData(); - for (int i = 0; i < gameData.getEventCount(); i++) { - ClientData.Event event = gameData.getEvents(i); - EventHandler.operation(eventListener, game, event); - if (event.getType() == EventType.MatchEnd) { - gameOver = true; + performanceMetrics.botResponse.time(() -> { + ClientData.GameData gameData = game.clientData().gameData(); + for (int i = 0; i < gameData.getEventCount(); i++) { + ClientData.Event event = gameData.getEvents(i); + EventHandler.operation(eventListener, game, event); + if (event.getType() == EventType.MatchEnd) { + gameOver = true; + } } - } + }); } } diff --git a/src/main/java/bwapi/Bullet.java b/src/main/java/bwapi/Bullet.java index 76612209..460f8094 100644 --- a/src/main/java/bwapi/Bullet.java +++ b/src/main/java/bwapi/Bullet.java @@ -21,7 +21,7 @@ * represents a new Bullet. *

* If {@link Flag#CompleteMapInformation} is disabled, then a {@link Bullet} is accessible if and only if - * it is visible. Otherwise if {@link Flag#CompleteMapInformation} is enabled, then all Bullets + * it is visible. Otherwise if {@link Flag#CompleteMapInformation} is timersEnabled, then all Bullets * in the game are accessible. * * @see Game#getBullets @@ -54,7 +54,7 @@ public int getID() { * return value will be false regardless of the Bullet's true existence. This is because * absolutely no state information on invisible enemy bullets is made available to the AI. *

- * If {@link Flag#CompleteMapInformation} is enabled, then this function is accurate for all + * If {@link Flag#CompleteMapInformation} is timersEnabled, then this function is accurate for all * {@link Bullet} information. * * @return true if the bullet exists or is visible, false if the bullet was destroyed or has gone out of scope. diff --git a/src/main/java/bwapi/CommandTemp.java b/src/main/java/bwapi/CommandTemp.java index 11141319..b2fa6fc2 100644 --- a/src/main/java/bwapi/CommandTemp.java +++ b/src/main/java/bwapi/CommandTemp.java @@ -2,7 +2,7 @@ /** * Latency Compensation: - * Only need to implement LatCom for current frame, the server updates the next frame already if latcom is enabled. + * Only need to implement LatCom for current frame, the server updates the next frame already if latcom is timersEnabled. * Use Caches for all internal state that might be affected by latcom, and add the (current) frame, to let Player & Unit * check if they need to use the cached/latcom version of the value or the from server (or a combination of both) *

diff --git a/src/main/java/bwapi/FrameBuffer.java b/src/main/java/bwapi/FrameBuffer.java index 3fbb24be..84f5bfad 100644 --- a/src/main/java/bwapi/FrameBuffer.java +++ b/src/main/java/bwapi/FrameBuffer.java @@ -37,6 +37,7 @@ of this software and associated documentation files (the "Software"), to deal class FrameBuffer { private ByteBuffer dataSource; + private PerformanceMetrics performanceMetrics; private int size; private int stepGame = 0; private int stepBot = 0; @@ -57,8 +58,9 @@ class FrameBuffer { /** * Resets for a new game */ - void initialize(ByteBuffer dataSource) { + void initialize(ByteBuffer dataSource, PerformanceMetrics performanceMetrics) { this.dataSource = dataSource; + this.performanceMetrics = performanceMetrics; stepGame = 0; stepBot = 0; } @@ -110,7 +112,13 @@ void enqueueFrame() { lockWrite.lock(); try { lockSize.lock(); - try { while (full()) conditionSize.awaitUninterruptibly(); } finally { lockSize.unlock(); }; + try { + while (full()) { + performanceMetrics.intentionallyBlocking.startTiming(); + conditionSize.awaitUninterruptibly(); + } + performanceMetrics.intentionallyBlocking.stopTiming(); + } finally { lockSize.unlock(); }; ByteBuffer dataTarget = dataBuffer.get(indexGame()); dataSource.rewind(); dataTarget.rewind(); @@ -133,7 +141,6 @@ ByteBuffer peek() { while(empty()) conditionSize.awaitUninterruptibly(); return dataBuffer.get(indexBot()); } finally { lockSize.unlock(); } - } /** diff --git a/src/main/java/bwapi/Game.java b/src/main/java/bwapi/Game.java index efb5b3ae..be638d5c 100644 --- a/src/main/java/bwapi/Game.java +++ b/src/main/java/bwapi/Game.java @@ -329,7 +329,7 @@ public List getPlayers() { /** * Retrieves the set of all accessible units. - * If {@link Flag#CompleteMapInformation} is enabled, then the set also includes units that are not + * If {@link Flag#CompleteMapInformation} is timersEnabled, then the set also includes units that are not * visible to the player. *

* Units that are inside refineries are not included in this set. @@ -624,13 +624,13 @@ public void pingMinimap(final Position p) { } /** - * Checks if the state of the given flag is enabled or not. + * Checks if the state of the given flag is timersEnabled or not. *

- * Flags may only be enabled at the start of the match during the {@link BWEventListener#onStart} + * Flags may only be timersEnabled at the start of the match during the {@link BWEventListener#onStart} * callback. * * @param flag The {@link Flag} entry describing the flag's effects on BWAPI. - * @return true if the given flag is enabled, false if the flag is disabled. + * @return true if the given flag is timersEnabled, false if the flag is disabled. * @see Flag */ public boolean isFlagEnabled(final Flag flag) { @@ -640,7 +640,7 @@ public boolean isFlagEnabled(final Flag flag) { /** * Enables the state of a given flag. *

- * Flags may only be enabled at the start of the match during the {@link BWEventListener#onStart} + * Flags may only be timersEnabled at the start of the match during the {@link BWEventListener#onStart} * callback. * * @param flag The {@link Flag} entry describing the flag's effects on BWAPI. @@ -1640,7 +1640,7 @@ public boolean issueCommand(final Collection units, final UnitCommand comm /** * Retrieves the set of units that are currently selected by the user outside of - * BWAPI. This function requires that{@link Flag#UserInput} be enabled. + * BWAPI. This function requires that{@link Flag#UserInput} be timersEnabled. * * @return A List containing the user's selected units. If {@link Flag#UserInput} is disabled, * then this set is always empty. @@ -2163,7 +2163,7 @@ public boolean isDebug() { /** * Checks the state of latency compensation. * - * @return true if latency compensation is enabled, false if it is disabled. + * @return true if latency compensation is timersEnabled, false if it is disabled. * @see #setLatCom */ public boolean isLatComEnabled() { @@ -2174,9 +2174,9 @@ public boolean isLatComEnabled() { * Changes the state of latency compensation. Latency compensation * modifies the state of BWAPI's representation of units to reflect the implications of * issuing a command immediately after the command was performed, instead of waiting - * consecutive frames for the results. Latency compensation is enabled by default. + * consecutive frames for the results. Latency compensation is timersEnabled by default. * - * @param isEnabled Set whether the latency compensation feature will be enabled (true) or disabled (false). + * @param isEnabled Set whether the latency compensation feature will be timersEnabled (true) or disabled (false). * @see #isLatComEnabled */ public void setLatCom(final boolean isEnabled) { @@ -2278,11 +2278,11 @@ public boolean setVision(Player player, boolean enabled) { } /** - * Checks if the GUI is enabled. + * Checks if the GUI is timersEnabled. *

* The GUI includes all drawing functions of BWAPI, as well as screen updates from Broodwar. * - * @return true if the GUI is enabled, and everything is visible, false if the GUI is disabled and drawing + * @return true if the GUI is timersEnabled, and everything is visible, false if the GUI is disabled and drawing * functions are rejected * @see #setGUI */ @@ -2341,7 +2341,7 @@ public boolean setMap(final String mapFileName) { /** * Sets the state of the fog of war when watching a replay. * - * @param reveal The state of the reveal all flag. If false, all fog of war will be enabled. If true, + * @param reveal The state of the reveal all flag. If false, all fog of war will be timersEnabled. If true, * then the fog of war will be revealed. It is true by default. */ public boolean setRevealAll(boolean reveal) { diff --git a/src/main/java/bwapi/PerformanceMetric.java b/src/main/java/bwapi/PerformanceMetric.java new file mode 100644 index 00000000..416d9cc4 --- /dev/null +++ b/src/main/java/bwapi/PerformanceMetric.java @@ -0,0 +1,87 @@ +package bwapi; + +import java.text.DecimalFormat; + +public class PerformanceMetric { + private final String name; + private final long maxThreshold; + public final boolean timerEnabled; + + public double minValue = Long.MAX_VALUE; + public double maxValue = Long.MIN_VALUE; + public double avgValue = 0; + public double avgValueExceeding = 0; + public int samples = 0; + public int samplesExceeding = 0; + public int interrupted = 0; + + private long timeStarted = 0; + + PerformanceMetric(String name, long maxThreshold, boolean timerEnabled) { + this.name = name; + this.maxThreshold = maxThreshold; + this.timerEnabled = timerEnabled; + } + + void time(Runnable runnable) { + startTiming(); + runnable.run(); + stopTiming(); + } + + void startTiming() { + if (!timerEnabled) return; + if (timeStarted > 0) { + ++interrupted; + } + timeStarted = System.nanoTime(); + } + + void stopTiming() { + if (!timerEnabled) return; + if (timeStarted <= 0) return; + // Use nanosecond resolution timer, + // and record in units of milliseconds. + long timeEnded = System.nanoTime(); + long timeDiff = timeEnded - timeStarted; + timeStarted = 0; + record(timeDiff / 1000d); + } + + void record(double value) { + minValue = Math.min(minValue, value); + maxValue = Math.max(maxValue, value); + avgValue = (avgValue * samples + value) / (samples + 1d); + ++samples; + if (value > maxThreshold) { + avgValueExceeding = (avgValueExceeding * samplesExceeding + value) / (samplesExceeding + 1d); + ++samplesExceeding; + } + } + + @Override + public String toString() { + DecimalFormat formatter = new DecimalFormat("###,###.#"); + return name + + ": " + + samples + + " averaging " + + formatter.format(avgValue) + + " [" + + formatter.format(minValue) + + " - " + + formatter.format(maxValue) + + "] over " + + samples + + " samples" + + (samplesExceeding > 0 + ? ". " + + samplesExceeding + + " violations averaging " + + formatter.format(avgValueExceeding) + : "") + + (interrupted > 0 + ? ". Interrupted " + interrupted + " times" + : ""); + } +} diff --git a/src/main/java/bwapi/PerformanceMetrics.java b/src/main/java/bwapi/PerformanceMetrics.java new file mode 100644 index 00000000..41901392 --- /dev/null +++ b/src/main/java/bwapi/PerformanceMetrics.java @@ -0,0 +1,85 @@ +package bwapi; + +/** + * When performance diagnostics are timersEnabled, collects various performance metrics. + */ +public class PerformanceMetrics { + + /** + * Total duration of the frame from JBWAPI's perspective. + * Likely to be at least a little bit of an undercount, + * given that the tournament module is timing a superset of JBWAPI's execution time. + */ + public PerformanceMetric totalFrameDuration; + + /** + * Time spent copying game data from system pipe shared memory to a frame buffer. + * Applicable only in asynchronous mode. + */ + public PerformanceMetric copyingToBuffer; + + /** + * Time spent intentionally blocking on bot operation due to a full frame buffer. + * Applicable only in asynchronous mode. + */ + public PerformanceMetric intentionallyBlocking; + + /** + * Number of frames backed up in the frame buffer, after releasing each frame. + * Applicable only in asynchronous mode. + */ + public PerformanceMetric frameBufferSize; + + /** + * Time spent applying bot commands to the live frame. + */ + public PerformanceMetric flushSideEffects; + + /** + * Time spent waiting for bot event handlers to complete for a single frame. + */ + public PerformanceMetric botResponse; + + /** + * Time spent waiting for a response from BWAPI; is likely reflective of the performance of any opponent bots. + */ + public PerformanceMetric bwapiResponse; + + /** + * Whether performance timers have been enabled in the JBWAPI configuration. + * Timers are off by default to spare performance. + */ + public final boolean timersEnabled; + + /** + * Time bot spends idle. + * Applicable only in asynchronous mode. + */ + public PerformanceMetric botIdle; + + public PerformanceMetrics(BWClientConfiguration configuration) { + timersEnabled = configuration.diagnosePerformance; + final int frameDurationBufferMs = 5; + final int sideEffectsBufferMs = 1; + final int realTimeFrameMs = 42; + totalFrameDuration = new PerformanceMetric("Total frame duration", configuration.asyncFrameDurationMs + frameDurationBufferMs, timersEnabled); + copyingToBuffer = new PerformanceMetric("Time copying to buffer", 5, timersEnabled); + intentionallyBlocking = new PerformanceMetric("Intentionally blocking", 0, timersEnabled); + frameBufferSize = new PerformanceMetric("Frame buffer size", 0, timersEnabled); + flushSideEffects = new PerformanceMetric("Flush side effects", sideEffectsBufferMs , timersEnabled); + botResponse = new PerformanceMetric("Bot Responses", configuration.asyncFrameDurationMs, timersEnabled); + bwapiResponse = new PerformanceMetric("BWAPI Responses", realTimeFrameMs, timersEnabled); + botIdle = new PerformanceMetric("Bot idle", Long.MAX_VALUE, timersEnabled); + } + + public String toString() { + return "Performance metrics:" + + "\n" + totalFrameDuration.toString() + + "\n" + copyingToBuffer.toString() + + "\n" + intentionallyBlocking.toString() + + "\n" + frameBufferSize.toString() + + "\n" + flushSideEffects.toString() + + "\n" + botResponse.toString() + + "\n" + bwapiResponse.toString(); + } +} diff --git a/src/main/java/bwapi/Unit.java b/src/main/java/bwapi/Unit.java index 6d491ae9..c13d8c2a 100644 --- a/src/main/java/bwapi/Unit.java +++ b/src/main/java/bwapi/Unit.java @@ -19,8 +19,8 @@ * becoming invalid). *

* Every Unit in the game is either accessible or inaccessible. To determine if an AI can access - * a particular unit, BWAPI checks to see if {@link Flag#CompleteMapInformation} is enabled. So there - * are two cases to consider - either the flag is enabled, or it is disabled: + * a particular unit, BWAPI checks to see if {@link Flag#CompleteMapInformation} is timersEnabled. So there + * are two cases to consider - either the flag is timersEnabled, or it is disabled: *

* If {@link Flag#CompleteMapInformation} is disabled, then a unit is accessible if and only if it is visible. *

@@ -31,7 +31,7 @@ * AI must watch for {@link BWEventListener#onUnitDestroy} messages from BWAPI, which is only called for visible units * which get destroyed. *

- * If {@link Flag#CompleteMapInformation} is enabled, then all units that exist in the game are accessible, and + * If {@link Flag#CompleteMapInformation} is timersEnabled, then all units that exist in the game are accessible, and * {@link Unit#exists} is accurate for all units. Similarly {@link BWEventListener#onUnitDestroy} messages are generated for all * units that get destroyed, not just visible ones. *

@@ -128,7 +128,7 @@ public boolean exists() { /** * Retrieves the unit identifier for this unit as seen in replay data. *

- * This is only available if {@link Flag#CompleteMapInformation} is enabled. + * This is only available if {@link Flag#CompleteMapInformation} is timersEnabled. * * @return An integer containing the replay unit identifier. * @see #getID @@ -1882,7 +1882,7 @@ public boolean isResearching() { /** * Checks if this unit has been selected in the user interface. This - * function is only available if the flag Flag#UserInput is enabled. + * function is only available if the flag Flag#UserInput is timersEnabled. * * @return true if this unit is currently selected, and false if this unit is not selected * @see Game#getSelectedUnits @@ -2046,7 +2046,7 @@ public boolean isVisible() { * @param player The player to check visibility for. If this parameter is omitted, then the BWAPI player obtained from {@link Game#self()} will be used. * @return true if this unit is visible to the specified player, and false if it is not. *

- * If the {@link Flag#CompleteMapInformation} flag is enabled, existing units hidden by the + * If the {@link Flag#CompleteMapInformation} flag is timersEnabled, existing units hidden by the * fog of war will be accessible, but isVisible will still return false. * @see #exists */ From be52a24b749b46a8c184665fae78a939e932cd50 Mon Sep 17 00:00:00 2001 From: dgant Date: Sat, 8 Aug 2020 03:44:52 -0400 Subject: [PATCH 17/56] Fixed some collection and display of performance metrics --- src/main/java/bwapi/BWClient.java | 2 +- .../java/bwapi/BWClientConfiguration.java | 2 +- src/main/java/bwapi/BotWrapper.java | 22 +++++---- src/main/java/bwapi/PerformanceMetric.java | 47 ++++++++++--------- src/main/java/bwapi/PerformanceMetrics.java | 5 +- 5 files changed, 41 insertions(+), 37 deletions(-) diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index 4bf28818..15a39d80 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -27,7 +27,7 @@ public Game getGame() { * Returns JBWAPI performance metrics. * Metrics will be mostly empty if metrics collection isn't timersEnabled in the bot configuration */ - public PerformanceMetrics performanceMetrics() { + public PerformanceMetrics getPerformanceMetrics() { return performanceMetrics; } diff --git a/src/main/java/bwapi/BWClientConfiguration.java b/src/main/java/bwapi/BWClientConfiguration.java index afeea481..30632e8e 100644 --- a/src/main/java/bwapi/BWClientConfiguration.java +++ b/src/main/java/bwapi/BWClientConfiguration.java @@ -53,7 +53,7 @@ public class BWClientConfiguration { * Enables collection of diagnostics. * When timersEnabled, JBWAPI collects and publishes performance metrics. */ - public boolean diagnosePerformance = false; + public boolean collectPerformanceMetrics = false; /** * Checks that the configuration is in a valid state. Throws an IllegalArgumentException if it isn't. diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java index 9f7883cc..74b8ca0d 100644 --- a/src/main/java/bwapi/BotWrapper.java +++ b/src/main/java/bwapi/BotWrapper.java @@ -126,7 +126,7 @@ private Thread createBotThread() { frameBuffer.lockSize.unlock(); } game.clientData().setBuffer(frameBuffer.peek()); - performanceMetrics.frameBufferSize.record(frameBuffer.framesBuffered()); + performanceMetrics.frameBufferSize.record(frameBuffer.framesBuffered() - 1); handleEvents(); frameBuffer.dequeue(); } @@ -134,15 +134,17 @@ private Thread createBotThread() { } private void handleEvents() { - performanceMetrics.botResponse.time(() -> { - ClientData.GameData gameData = game.clientData().gameData(); - for (int i = 0; i < gameData.getEventCount(); i++) { - ClientData.Event event = gameData.getEvents(i); - EventHandler.operation(eventListener, game, event); - if (event.getType() == EventType.MatchEnd) { - gameOver = true; - } + ClientData.GameData gameData = game.clientData().gameData(); + if (gameData.getFrameCount() > 0 || ! configuration.asyncWaitOnFrameZero) { + performanceMetrics.botResponse.startTiming(); + } + for (int i = 0; i < gameData.getEventCount(); i++) { + ClientData.Event event = gameData.getEvents(i); + EventHandler.operation(eventListener, game, event); + if (event.getType() == EventType.MatchEnd) { + gameOver = true; } - }); + } + performanceMetrics.botResponse.stopTiming(); } } diff --git a/src/main/java/bwapi/PerformanceMetric.java b/src/main/java/bwapi/PerformanceMetric.java index 416d9cc4..0f07af89 100644 --- a/src/main/java/bwapi/PerformanceMetric.java +++ b/src/main/java/bwapi/PerformanceMetric.java @@ -4,7 +4,7 @@ public class PerformanceMetric { private final String name; - private final long maxThreshold; + private final long maxAllowed; public final boolean timerEnabled; public double minValue = Long.MAX_VALUE; @@ -17,9 +17,9 @@ public class PerformanceMetric { private long timeStarted = 0; - PerformanceMetric(String name, long maxThreshold, boolean timerEnabled) { + PerformanceMetric(String name, long maxAllowed, boolean timerEnabled) { this.name = name; - this.maxThreshold = maxThreshold; + this.maxAllowed = maxAllowed; this.timerEnabled = timerEnabled; } @@ -40,12 +40,11 @@ void startTiming() { void stopTiming() { if (!timerEnabled) return; if (timeStarted <= 0) return; - // Use nanosecond resolution timer, - // and record in units of milliseconds. + // Use nanosecond resolution timer, but record in units of milliseconds. long timeEnded = System.nanoTime(); long timeDiff = timeEnded - timeStarted; timeStarted = 0; - record(timeDiff / 1000d); + record(timeDiff / 1000000d); } void record(double value) { @@ -53,7 +52,7 @@ void record(double value) { maxValue = Math.max(maxValue, value); avgValue = (avgValue * samples + value) / (samples + 1d); ++samples; - if (value > maxThreshold) { + if (value > maxAllowed) { avgValueExceeding = (avgValueExceeding * samplesExceeding + value) / (samplesExceeding + 1d); ++samplesExceeding; } @@ -64,22 +63,24 @@ public String toString() { DecimalFormat formatter = new DecimalFormat("###,###.#"); return name + ": " - + samples - + " averaging " - + formatter.format(avgValue) - + " [" - + formatter.format(minValue) - + " - " - + formatter.format(maxValue) - + "] over " - + samples - + " samples" - + (samplesExceeding > 0 - ? ". " - + samplesExceeding - + " violations averaging " - + formatter.format(avgValueExceeding) - : "") + + (samples > 0 + ? formatter.format(samples) + + " samples averaging " + + formatter.format(avgValue) + + " [" + + formatter.format(minValue) + + " - " + + formatter.format(maxValue) + + "] over " + + samples + + " samples" + + (samplesExceeding > 0 + ? ". " + + samplesExceeding + + " violations averaging " + + formatter.format(avgValueExceeding) + : "") + : "No samples") + (interrupted > 0 ? ". Interrupted " + interrupted + " times" : ""); diff --git a/src/main/java/bwapi/PerformanceMetrics.java b/src/main/java/bwapi/PerformanceMetrics.java index 41901392..8e00c1fc 100644 --- a/src/main/java/bwapi/PerformanceMetrics.java +++ b/src/main/java/bwapi/PerformanceMetrics.java @@ -58,20 +58,21 @@ public class PerformanceMetrics { public PerformanceMetric botIdle; public PerformanceMetrics(BWClientConfiguration configuration) { - timersEnabled = configuration.diagnosePerformance; + timersEnabled = configuration.collectPerformanceMetrics; final int frameDurationBufferMs = 5; final int sideEffectsBufferMs = 1; final int realTimeFrameMs = 42; totalFrameDuration = new PerformanceMetric("Total frame duration", configuration.asyncFrameDurationMs + frameDurationBufferMs, timersEnabled); copyingToBuffer = new PerformanceMetric("Time copying to buffer", 5, timersEnabled); intentionallyBlocking = new PerformanceMetric("Intentionally blocking", 0, timersEnabled); - frameBufferSize = new PerformanceMetric("Frame buffer size", 0, timersEnabled); + frameBufferSize = new PerformanceMetric("Frames buffered", 0, timersEnabled); flushSideEffects = new PerformanceMetric("Flush side effects", sideEffectsBufferMs , timersEnabled); botResponse = new PerformanceMetric("Bot Responses", configuration.asyncFrameDurationMs, timersEnabled); bwapiResponse = new PerformanceMetric("BWAPI Responses", realTimeFrameMs, timersEnabled); botIdle = new PerformanceMetric("Bot idle", Long.MAX_VALUE, timersEnabled); } + @Override public String toString() { return "Performance metrics:" + "\n" + totalFrameDuration.toString() From edddd2b6a4df85242d508d71351d51948873efb1 Mon Sep 17 00:00:00 2001 From: dgant Date: Mon, 10 Aug 2020 03:16:01 -0400 Subject: [PATCH 18/56] Fixed condition for waiting for bot to finish (instead of returning control immediately always). Improved some metrics --- src/main/java/bwapi/BWClient.java | 30 ++++++++++++------- .../java/bwapi/BWClientConfiguration.java | 27 +++++++---------- src/main/java/bwapi/BotWrapper.java | 18 +++++++---- src/main/java/bwapi/FrameBuffer.java | 11 ++++--- src/main/java/bwapi/PerformanceMetric.java | 6 +--- src/main/java/bwapi/PerformanceMetrics.java | 23 +++++--------- 6 files changed, 58 insertions(+), 57 deletions(-) diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index 15a39d80..ccb9e424 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -8,6 +8,7 @@ public class BWClient { private final BWEventListener eventListener; private BotWrapper botWrapper; + private Client client; private PerformanceMetrics performanceMetrics; public BWClient(final BWEventListener eventListener) { @@ -31,6 +32,13 @@ public PerformanceMetrics getPerformanceMetrics() { return performanceMetrics; } + /** + * Number of frames + */ + public int framesBehind() { + return botWrapper == null ? 0 : client.clientData().gameData().getFrameCount() - getGame().getFrameCount(); + } + public void startGame() { BWClientConfiguration configuration = new BWClientConfiguration(); startGame(configuration); @@ -57,7 +65,7 @@ public void startGame(BWClientConfiguration configuration) { configuration.validate(); botWrapper = new BotWrapper(configuration, eventListener); - Client client = new Client(configuration); + client = new Client(configuration); client.reconnect(); do { @@ -73,16 +81,16 @@ public void startGame(BWClientConfiguration configuration) { } } while (liveGameData.isInGame()) { - performanceMetrics.totalFrameDuration.time(() -> { - botWrapper.onFrame(); - performanceMetrics.flushSideEffects.time(() -> { - getGame().sideEffects.flushTo(liveGameData); - }); - client.update(); - if (!client.isConnected()) { - client.reconnect(); - } - }); + if (liveGameData.getFrameCount() > 0 || ! configuration.unlimitedFrameZero) { + performanceMetrics.totalFrameDuration.startTiming(); + } + botWrapper.onFrame(); + performanceMetrics.flushSideEffects.time(() -> getGame().sideEffects.flushTo(liveGameData)); + client.update(); + if (!client.isConnected()) { + client.reconnect(); + } + performanceMetrics.totalFrameDuration.stopTiming(); } botWrapper.endGame(); } while (configuration.autoContinue); diff --git a/src/main/java/bwapi/BWClientConfiguration.java b/src/main/java/bwapi/BWClientConfiguration.java index 30632e8e..9328c17c 100644 --- a/src/main/java/bwapi/BWClientConfiguration.java +++ b/src/main/java/bwapi/BWClientConfiguration.java @@ -15,6 +15,16 @@ public class BWClientConfiguration { */ public boolean autoContinue = false; + /** + * 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, + * as the bot has no prior access to BWAPI or game information. + * + * This flag indicates that taking arbitrarily long on frame zero is acceptable. + * Performance metrics omit the frame as an outlier. + * Asynchronous operation will block until the bot's event handlers are complete. + */ + public boolean unlimitedFrameZero = true; + /** * Runs the bot in asynchronous mode. Asynchronous mode helps attempt to ensure that the bot adheres to real-time performance constraints. * @@ -32,7 +42,7 @@ public class BWClientConfiguration { * If JBWAPI detects that this much time (in nanoseconds) has passed since a bot's event handlers began, returns control back to BWAPI. * Real-time human play typically uses the "fastest" game speed, which has 42.86ms (42,860ns) between frames. */ - public int asyncFrameDurationMs = 40000; + public int asyncFrameDurationMs = 40; /** * The maximum number of frames to buffer while waiting on a bot. @@ -40,21 +50,6 @@ public class BWClientConfiguration { */ public int asyncFrameBufferSize = 10; - /** - * 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, - * as the bot has no prior access to BWAPI or game information. - * - * This flag causes JBWAPI to wait for the bot's event handlers to return on the first frame of the game, even if operating in asynchronous mode, - * respecting the time bots are typically allowed on frame 0. - */ - public boolean asyncWaitOnFrameZero = true; - - /** - * Enables collection of diagnostics. - * When timersEnabled, JBWAPI collects and publishes performance metrics. - */ - public boolean collectPerformanceMetrics = false; - /** * Checks that the configuration is in a valid state. Throws an IllegalArgumentException if it isn't. */ diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java index 74b8ca0d..07a83267 100644 --- a/src/main/java/bwapi/BotWrapper.java +++ b/src/main/java/bwapi/BotWrapper.java @@ -78,12 +78,18 @@ void onFrame() { botThread.setName("JBWAPI Bot"); botThread.start(); } - performanceMetrics.copyingToBuffer.time(frameBuffer::enqueueFrame); + /* + Add a frame to buffer + If buffer is full, it will wait until it has capacity + Wait for empty buffer OR termination condition + */ + boolean isFrameZero = liveClientData.gameData().getFrameCount() == 0; + frameBuffer.enqueueFrame(); + performanceMetrics.bwapiResponse.startTiming(); frameBuffer.lockSize.lock(); try { - while (frameBuffer.empty()) { - performanceMetrics.bwapiResponse.startTiming(); - if (configuration.asyncWaitOnFrameZero && liveClientData.gameData().getFrameCount() == 0) { + while (!frameBuffer.empty()) { + if (configuration.unlimitedFrameZero && isFrameZero) { frameBuffer.conditionSize.await(); } else { long remainingNanos = endNanos - System.nanoTime(); @@ -91,10 +97,10 @@ void onFrame() { frameBuffer.conditionSize.awaitNanos(remainingNanos); } } - performanceMetrics.bwapiResponse.stopTiming(); } catch(InterruptedException ignored) { } finally { frameBuffer.lockSize.unlock(); + performanceMetrics.bwapiResponse.stopTiming(); } } else { handleEvents(); @@ -135,7 +141,7 @@ private Thread createBotThread() { private void handleEvents() { ClientData.GameData gameData = game.clientData().gameData(); - if (gameData.getFrameCount() > 0 || ! configuration.asyncWaitOnFrameZero) { + if (gameData.getFrameCount() > 0 || ! configuration.unlimitedFrameZero) { performanceMetrics.botResponse.startTiming(); } for (int i = 0; i < gameData.getEventCount(); i++) { diff --git a/src/main/java/bwapi/FrameBuffer.java b/src/main/java/bwapi/FrameBuffer.java index 84f5bfad..124d5976 100644 --- a/src/main/java/bwapi/FrameBuffer.java +++ b/src/main/java/bwapi/FrameBuffer.java @@ -119,10 +119,13 @@ void enqueueFrame() { } performanceMetrics.intentionallyBlocking.stopTiming(); } finally { lockSize.unlock(); }; - ByteBuffer dataTarget = dataBuffer.get(indexGame()); - dataSource.rewind(); - dataTarget.rewind(); - dataTarget.put(dataSource); + + performanceMetrics.copyingToBuffer.time(() -> { + ByteBuffer dataTarget = dataBuffer.get(indexGame()); + dataSource.rewind(); + dataTarget.rewind(); + dataTarget.put(dataSource); + }); lockSize.lock(); try { diff --git a/src/main/java/bwapi/PerformanceMetric.java b/src/main/java/bwapi/PerformanceMetric.java index 0f07af89..8061cf61 100644 --- a/src/main/java/bwapi/PerformanceMetric.java +++ b/src/main/java/bwapi/PerformanceMetric.java @@ -5,7 +5,6 @@ public class PerformanceMetric { private final String name; private final long maxAllowed; - public final boolean timerEnabled; public double minValue = Long.MAX_VALUE; public double maxValue = Long.MIN_VALUE; @@ -17,10 +16,9 @@ public class PerformanceMetric { private long timeStarted = 0; - PerformanceMetric(String name, long maxAllowed, boolean timerEnabled) { + PerformanceMetric(String name, long maxAllowed) { this.name = name; this.maxAllowed = maxAllowed; - this.timerEnabled = timerEnabled; } void time(Runnable runnable) { @@ -30,7 +28,6 @@ void time(Runnable runnable) { } void startTiming() { - if (!timerEnabled) return; if (timeStarted > 0) { ++interrupted; } @@ -38,7 +35,6 @@ void startTiming() { } void stopTiming() { - if (!timerEnabled) return; if (timeStarted <= 0) return; // Use nanosecond resolution timer, but record in units of milliseconds. long timeEnded = System.nanoTime(); diff --git a/src/main/java/bwapi/PerformanceMetrics.java b/src/main/java/bwapi/PerformanceMetrics.java index 8e00c1fc..f4266138 100644 --- a/src/main/java/bwapi/PerformanceMetrics.java +++ b/src/main/java/bwapi/PerformanceMetrics.java @@ -45,12 +45,6 @@ public class PerformanceMetrics { */ public PerformanceMetric bwapiResponse; - /** - * Whether performance timers have been enabled in the JBWAPI configuration. - * Timers are off by default to spare performance. - */ - public final boolean timersEnabled; - /** * Time bot spends idle. * Applicable only in asynchronous mode. @@ -58,18 +52,17 @@ public class PerformanceMetrics { public PerformanceMetric botIdle; public PerformanceMetrics(BWClientConfiguration configuration) { - timersEnabled = configuration.collectPerformanceMetrics; final int frameDurationBufferMs = 5; final int sideEffectsBufferMs = 1; final int realTimeFrameMs = 42; - totalFrameDuration = new PerformanceMetric("Total frame duration", configuration.asyncFrameDurationMs + frameDurationBufferMs, timersEnabled); - copyingToBuffer = new PerformanceMetric("Time copying to buffer", 5, timersEnabled); - intentionallyBlocking = new PerformanceMetric("Intentionally blocking", 0, timersEnabled); - frameBufferSize = new PerformanceMetric("Frames buffered", 0, timersEnabled); - flushSideEffects = new PerformanceMetric("Flush side effects", sideEffectsBufferMs , timersEnabled); - botResponse = new PerformanceMetric("Bot Responses", configuration.asyncFrameDurationMs, timersEnabled); - bwapiResponse = new PerformanceMetric("BWAPI Responses", realTimeFrameMs, timersEnabled); - botIdle = new PerformanceMetric("Bot idle", Long.MAX_VALUE, timersEnabled); + totalFrameDuration = new PerformanceMetric("Total frame duration", configuration.asyncFrameDurationMs + frameDurationBufferMs); + copyingToBuffer = new PerformanceMetric("Time copying to buffer", 5); + intentionallyBlocking = new PerformanceMetric("Intentionally blocking", 0); + frameBufferSize = new PerformanceMetric("Frames buffered", 0); + flushSideEffects = new PerformanceMetric("Flush side effects", sideEffectsBufferMs ); + botResponse = new PerformanceMetric("Bot Responses", configuration.asyncFrameDurationMs); + bwapiResponse = new PerformanceMetric("BWAPI Responses", realTimeFrameMs); + botIdle = new PerformanceMetric("Bot idle", Long.MAX_VALUE); } @Override From 24aa1cb5d11f04490bfbdf6adb00990923d9ba7f Mon Sep 17 00:00:00 2001 From: JasperGeurtz Date: Mon, 10 Aug 2020 19:22:40 +0200 Subject: [PATCH 19/56] use memcpy to copy the framebuffer --- src/main/java/bwapi/FrameBuffer.java | 19 +++++-- src/main/java/bwapi/MSVCRT.java | 10 ++++ src/test/java/bwapi/SynchronizationTest.java | 53 ++++++++++++++++++++ 3 files changed, 78 insertions(+), 4 deletions(-) create mode 100644 src/main/java/bwapi/MSVCRT.java create mode 100644 src/test/java/bwapi/SynchronizationTest.java diff --git a/src/main/java/bwapi/FrameBuffer.java b/src/main/java/bwapi/FrameBuffer.java index 124d5976..cec62f1e 100644 --- a/src/main/java/bwapi/FrameBuffer.java +++ b/src/main/java/bwapi/FrameBuffer.java @@ -25,6 +25,7 @@ of this software and associated documentation files (the "Software"), to deal package bwapi; +import sun.nio.ch.DirectBuffer; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.concurrent.locks.Condition; @@ -35,6 +36,7 @@ of this software and associated documentation files (the "Software"), to deal * Circular buffer of game states. */ class FrameBuffer { + private static final int BUFFER_SIZE = ClientData.GameData.SIZE; private ByteBuffer dataSource; private PerformanceMetrics performanceMetrics; @@ -51,7 +53,7 @@ class FrameBuffer { FrameBuffer(int size) { this.size = size; while(dataBuffer.size() < size) { - dataBuffer.add(ByteBuffer.allocateDirect(ClientData.GameData.SIZE)); + dataBuffer.add(ByteBuffer.allocateDirect(BUFFER_SIZE)); } } @@ -122,9 +124,7 @@ void enqueueFrame() { performanceMetrics.copyingToBuffer.time(() -> { ByteBuffer dataTarget = dataBuffer.get(indexGame()); - dataSource.rewind(); - dataTarget.rewind(); - dataTarget.put(dataSource); + copyBuffer(dataSource, dataTarget, BUFFER_SIZE); }); lockSize.lock(); @@ -135,6 +135,17 @@ void enqueueFrame() { } finally { lockWrite.unlock(); } } + private void copyBufferOld(ByteBuffer source, ByteBuffer dest, int size) { + source.rewind(); + dest.rewind(); + dest.put(dataSource); + } + private void copyBuffer(ByteBuffer source, ByteBuffer dest, int size) { + long destAddr = ((DirectBuffer)dest).address(); + long sourceAddr = ((DirectBuffer)source).address(); + MSVCRT.INSTANCE.memcpy(destAddr, sourceAddr, size); + } + /** * Peeks the front-most value in the buffer. */ diff --git a/src/main/java/bwapi/MSVCRT.java b/src/main/java/bwapi/MSVCRT.java new file mode 100644 index 00000000..05a9a774 --- /dev/null +++ b/src/main/java/bwapi/MSVCRT.java @@ -0,0 +1,10 @@ +package bwapi; + +import com.sun.jna.Library; +import com.sun.jna.Native; + +interface MSVCRT extends Library { + MSVCRT INSTANCE = Native.load("msvcrt.dll", MSVCRT.class); + + long memcpy(long dest, long src, int count); +} \ No newline at end of file diff --git a/src/test/java/bwapi/SynchronizationTest.java b/src/test/java/bwapi/SynchronizationTest.java new file mode 100644 index 00000000..0b70acae --- /dev/null +++ b/src/test/java/bwapi/SynchronizationTest.java @@ -0,0 +1,53 @@ +package bwapi; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertNull; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +import net.bytebuddy.implementation.bytecode.Addition; +import org.junit.Before; +import org.junit.Test; +import org.junit.experimental.theories.DataPoints; +import org.junit.experimental.theories.FromDataPoints; +import org.junit.experimental.theories.Theories; +import org.junit.experimental.theories.Theory; +import org.junit.runner.RunWith; +import org.mockito.AdditionalAnswers; +import org.mockito.Mock; +import org.mockito.Mockito; + +public class SynchronizationTest { + + class Environment { + BWClientConfiguration configuration = new BWClientConfiguration(); + BWEventListener listener = mock(BWEventListener.class); + Client client = mock(Client.class); + + Game game; + public Environment() { + try { + game = GameBuilder.createGame(); + } catch (IOException exception) { + throw new RuntimeException(exception); + } + Mockito.doAnswer(answer -> { stepFrame(); return null; }).when(client).update(); + } + + public void stepFrame() { + game.clientData().gameData().setFrameCount(game.clientData().gameData().getFrameCount() + 1); + } + } + + @Test + public void synchronizedRuns() { + Environment environment = new Environment(); + environment.configuration.async = false; + } + +} From 80d37cf3ee259cce7ce2636da8272e85c4d8f882 Mon Sep 17 00:00:00 2001 From: dgant Date: Thu, 13 Aug 2020 13:18:01 -0400 Subject: [PATCH 20/56] [Tests failing] Finished test environment for sync/async tests. Configured tests to run with sufficient memory to allocate framebuffers. --- pom.xml | 2 + src/main/java/bwapi/BWClient.java | 24 +++- src/main/java/bwapi/BotWrapper.java | 42 +++++-- src/main/java/bwapi/FrameBuffer.java | 51 ++++++-- src/main/java/bwapi/Game.java | 1 + src/main/java/bwapi/MSVCRT.java | 17 +++ src/test/java/bwapi/GameBuilder.java | 32 ++++- .../bwapi/SynchronizationEnvironment.java | 113 ++++++++++++++++++ src/test/java/bwapi/SynchronizationTest.java | 92 ++++++++------ 9 files changed, 310 insertions(+), 64 deletions(-) create mode 100644 src/test/java/bwapi/SynchronizationEnvironment.java diff --git a/pom.xml b/pom.xml index dc56d2ca..bb7f29e5 100644 --- a/pom.xml +++ b/pom.xml @@ -63,6 +63,8 @@ all true + -Xms1g + -Xmx1g diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index ccb9e424..091c49e3 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -25,20 +25,25 @@ public Game getGame() { } /** - * Returns JBWAPI performance metrics. - * Metrics will be mostly empty if metrics collection isn't timersEnabled in the bot configuration + * @return JBWAPI performance metrics. */ + @SuppressWarnings("unused") public PerformanceMetrics getPerformanceMetrics() { return performanceMetrics; } /** - * Number of frames + * @return The number of frames between the one exposed to the bot and the most recent received by JBWAPI. + * This tracks the size of the frame buffer except when the game is paused (which results in multiple frames arriving with the same count). */ + @SuppressWarnings("unused") public int framesBehind() { - return botWrapper == null ? 0 : client.clientData().gameData().getFrameCount() - getGame().getFrameCount(); + return botWrapper == null ? 0 : Math.max(0, client.clientData().gameData().getFrameCount() - getGame().getFrameCount()); } + /** + * Start the game with default settings. + */ public void startGame() { BWClientConfiguration configuration = new BWClientConfiguration(); startGame(configuration); @@ -65,7 +70,9 @@ public void startGame(BWClientConfiguration configuration) { configuration.validate(); botWrapper = new BotWrapper(configuration, eventListener); - client = new Client(configuration); + if (client == null) { + client = new Client(configuration); + } client.reconnect(); do { @@ -95,4 +102,11 @@ public void startGame(BWClientConfiguration configuration) { botWrapper.endGame(); } while (configuration.autoContinue); } + + /** + * Provides a Client. Intended for test consumers only. + */ + void setClient(Client client) { + this.client = client; + } } \ No newline at end of file diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java index 07a83267..21fc89e3 100644 --- a/src/main/java/bwapi/BotWrapper.java +++ b/src/main/java/bwapi/BotWrapper.java @@ -26,6 +26,7 @@ of this software and associated documentation files (the "Software"), to deal package bwapi; import java.nio.ByteBuffer; +import java.util.concurrent.locks.ReentrantLock; /** * Manages invocation of bot event handlers @@ -39,6 +40,8 @@ class BotWrapper { private Thread botThread; private boolean gameOver; private PerformanceMetrics performanceMetrics; + private Exception lastBotException; + private ReentrantLock lastBotExceptionLock = new ReentrantLock(); BotWrapper(BWClientConfiguration configuration, BWEventListener eventListener) { this.configuration = configuration; @@ -50,7 +53,9 @@ class BotWrapper { * Resets the BotWrapper for a new game. */ void startNewGame(ByteBuffer dataSource, PerformanceMetrics performanceMetrics) { - frameBuffer.initialize(dataSource, performanceMetrics); + if (configuration.async) { + frameBuffer.initialize(dataSource, performanceMetrics); + } this.performanceMetrics = performanceMetrics; game = new Game(liveClientData); liveClientData.setBuffer(dataSource); @@ -80,7 +85,7 @@ void onFrame() { } /* Add a frame to buffer - If buffer is full, it will wait until it has capacity + If buffer is full, it will wait until it has capacity Wait for empty buffer OR termination condition */ boolean isFrameZero = liveClientData.gameData().getFrameCount() == 0; @@ -89,6 +94,13 @@ void onFrame() { frameBuffer.lockSize.lock(); try { while (!frameBuffer.empty()) { + + // Make bot exceptions fall through to the main thread. + Exception lastBotException = getLastBotException(); + if (lastBotException != null) { + throw new RuntimeException(lastBotException); + } + if (configuration.unlimitedFrameZero && isFrameZero) { frameBuffer.conditionSize.await(); } else { @@ -104,6 +116,9 @@ void onFrame() { } } else { handleEvents(); + if (lastBotException != null) { + throw new RuntimeException(lastBotException); + } } } @@ -118,6 +133,13 @@ void endGame() { } } + Exception getLastBotException() { + lastBotExceptionLock.lock(); + Exception output = lastBotException; + lastBotExceptionLock.unlock(); + return output; + } + private Thread createBotThread() { return new Thread(() -> { while ( ! gameOver) { @@ -144,12 +166,18 @@ private void handleEvents() { if (gameData.getFrameCount() > 0 || ! configuration.unlimitedFrameZero) { performanceMetrics.botResponse.startTiming(); } - for (int i = 0; i < gameData.getEventCount(); i++) { - ClientData.Event event = gameData.getEvents(i); - EventHandler.operation(eventListener, game, event); - if (event.getType() == EventType.MatchEnd) { - gameOver = true; + try { + for (int i = 0; i < gameData.getEventCount(); i++) { + ClientData.Event event = gameData.getEvents(i); + EventHandler.operation(eventListener, game, event); + if (event.getType() == EventType.MatchEnd) { + gameOver = true; + } } + } catch (Exception exception) { + lastBotExceptionLock.lock(); + lastBotException = exception; + lastBotExceptionLock.unlock(); } performanceMetrics.botResponse.stopTiming(); } diff --git a/src/main/java/bwapi/FrameBuffer.java b/src/main/java/bwapi/FrameBuffer.java index cec62f1e..6564fa30 100644 --- a/src/main/java/bwapi/FrameBuffer.java +++ b/src/main/java/bwapi/FrameBuffer.java @@ -25,6 +25,7 @@ of this software and associated documentation files (the "Software"), to deal package bwapi; +import com.sun.jna.Platform; import sun.nio.ch.DirectBuffer; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -44,6 +45,7 @@ class FrameBuffer { private int stepGame = 0; private int stepBot = 0; private ArrayList dataBuffer = new ArrayList<>(); + private final String architecture; // Synchronization locks private final Lock lockWrite = new ReentrantLock(); @@ -53,8 +55,10 @@ class FrameBuffer { FrameBuffer(int size) { this.size = size; while(dataBuffer.size() < size) { + System.out.println("Allocating " + BUFFER_SIZE / 1024 / 1024 + "mb"); dataBuffer.add(ByteBuffer.allocateDirect(BUFFER_SIZE)); } + architecture = System.getProperty("sun.arch.data.model"); } /** @@ -124,7 +128,7 @@ void enqueueFrame() { performanceMetrics.copyingToBuffer.time(() -> { ByteBuffer dataTarget = dataBuffer.get(indexGame()); - copyBuffer(dataSource, dataTarget, BUFFER_SIZE); + copyBuffer(dataSource, dataTarget); }); lockSize.lock(); @@ -135,17 +139,6 @@ void enqueueFrame() { } finally { lockWrite.unlock(); } } - private void copyBufferOld(ByteBuffer source, ByteBuffer dest, int size) { - source.rewind(); - dest.rewind(); - dest.put(dataSource); - } - private void copyBuffer(ByteBuffer source, ByteBuffer dest, int size) { - long destAddr = ((DirectBuffer)dest).address(); - long sourceAddr = ((DirectBuffer)source).address(); - MSVCRT.INSTANCE.memcpy(destAddr, sourceAddr, size); - } - /** * Peeks the front-most value in the buffer. */ @@ -168,4 +161,38 @@ void dequeue() { conditionSize.signalAll(); } finally { lockSize.unlock(); } } + + void copyBuffer(ByteBuffer source, ByteBuffer destination) { + /* + The speed at which we copy data into the frame buffer is a major cost of JBWAPI's asynchronous operation. + Copy times observed in the wild range from 2.6ms - 12ms. + + The normal Java way to execute this copy is via ByteBuffer.put(), which has reasonably good performance characteristics. + Some experiments in 64-bit JRE have shown that using a native memcpy achieves a 35% speedup. + Some experiments in 32-bit JRE show no difference in performance. + + So, speculatively, we attempt to do a native memcpy. + */ + long addressSource = ((DirectBuffer) source).address(); + long addressDestination = ((DirectBuffer) destination).address(); + try { + if (Platform.isWindows()) { + if (Platform.is64Bit()) { + MSVCRT.INSTANCE.memcpy(addressDestination, addressSource, FrameBuffer.BUFFER_SIZE); + return; + } else { + MSVCRT.INSTANCE.memcpy((int) addressDestination, (int) addressSource, FrameBuffer.BUFFER_SIZE); + return; + } + } + } + catch(Exception ignored) {} + + // There's no specific case where we expect to fail above, + // but this is a safe fallback regardless, + // and serves to document the known-good (and cross-platform, for BWAPI 5) way to executing the copy. + source.rewind(); + destination.rewind(); + destination.put(dataSource); + } } diff --git a/src/main/java/bwapi/Game.java b/src/main/java/bwapi/Game.java index be638d5c..8321d437 100644 --- a/src/main/java/bwapi/Game.java +++ b/src/main/java/bwapi/Game.java @@ -142,6 +142,7 @@ private static boolean hasPower(final int x, final int y, final UnitType unitTyp Call this method in EventHander::OnMatchStart */ void init() { + System.out.println("Game.init()"); // TODO: REMOVE! visibleUnits.clear(); final int forceCount = gameData().getForceCount(); diff --git a/src/main/java/bwapi/MSVCRT.java b/src/main/java/bwapi/MSVCRT.java index 05a9a774..28a9b222 100644 --- a/src/main/java/bwapi/MSVCRT.java +++ b/src/main/java/bwapi/MSVCRT.java @@ -3,8 +3,25 @@ import com.sun.jna.Library; import com.sun.jna.Native; +/** + * JNI interface for access the native MSVC implementation of memcpy. + */ interface MSVCRT extends Library { MSVCRT INSTANCE = Native.load("msvcrt.dll", MSVCRT.class); + /** + * 32-bit implementation of memcpy. + * @param dest A 32-bit address of a memory block (likely from a ByteBuffer) to copy to + * @param src A 32-bit address of a memory block (likely from a ByteBuffer) to copy from + * @param count The number of bytes to copy + */ + long memcpy(int dest, int src, int count); + + /** + * 64-bit implementation of memcpy. + * @param dest A 64-bit address of a memory block (likely from a ByteBuffer) to copy to + * @param src A 64-bit address of a memory block (likely from a ByteBuffer) to copy from + * @param count The number of bytes to copy + */ long memcpy(long dest, long src, int count); } \ No newline at end of file diff --git a/src/test/java/bwapi/GameBuilder.java b/src/test/java/bwapi/GameBuilder.java index 577c7019..426df684 100644 --- a/src/test/java/bwapi/GameBuilder.java +++ b/src/test/java/bwapi/GameBuilder.java @@ -10,15 +10,32 @@ public class GameBuilder { + public final static String DEFAULT_MAP_FILE = "(2)Benzene.scx"; + public final static String DEFAULT_BUFFER_PATH = "src/test/resources/" + DEFAULT_MAP_FILE + "_frame0_buffer.bin"; + public static Game createGame() throws IOException { - return createGame("(2)Benzene.scx"); + return createGame(DEFAULT_MAP_FILE); } public static Game createGame(String mapName) throws IOException { - final ByteBuffer buffer = binToBuffer("src/test/resources/" + mapName + "_frame0_buffer.bin"); + final ByteBuffer buffer = binToBuffer(DEFAULT_BUFFER_PATH); return createGame(new Client(buffer)); } + public static Game createGame(Client client) { + final Game game = new Game(client.clientData()); + game.init(); + return game; + } + + public static Game createGameUnchecked() { + try { + return GameBuilder.createGame(); + } catch (IOException exception) { + throw new RuntimeException(exception); + } + } + public static ByteBuffer binToBuffer(String binLocation) throws IOException { final byte[] compressedBytes = Files.readAllBytes(Paths.get(binLocation)); final ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -32,9 +49,12 @@ public static ByteBuffer binToBuffer(String binLocation) throws IOException { return buffer; } - public static Game createGame(Client client) throws IOException { - final Game game = new Game(client.clientData()); - game.init(); - return game; + public static ByteBuffer binToBufferUnchecked(String binLocation) { + try { + return binToBuffer(binLocation); + } catch(IOException exception) { + throw new RuntimeException(exception); + } } + } diff --git a/src/test/java/bwapi/SynchronizationEnvironment.java b/src/test/java/bwapi/SynchronizationEnvironment.java new file mode 100644 index 00000000..5428022f --- /dev/null +++ b/src/test/java/bwapi/SynchronizationEnvironment.java @@ -0,0 +1,113 @@ +package bwapi; + +import java.lang.management.ManagementFactory; +import java.util.HashMap; +import java.util.Map; + +import static com.sun.javafx.fxml.expression.Expression.greaterThan; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hamcrest.MatcherAssert.*; + +public class SynchronizationEnvironment { + BWClientConfiguration configuration; + BWClient bwClient; + private BWEventListener listener; + private Client client; + private int onEndFrame; + private Map onFrames; + + SynchronizationEnvironment() { + configuration = new BWClientConfiguration(); + listener = mock(BWEventListener.class); + client = mock(Client.class); + bwClient = new BWClient(listener); + bwClient.setClient(client); + onEndFrame = -1; + onFrames = new HashMap<>(); + + when(client.mapFile()).thenReturn(GameBuilder.binToBufferUnchecked(GameBuilder.DEFAULT_BUFFER_PATH)); + when(client.clientData()).thenReturn(new ClientData()); + client.clientData().setBuffer(client.mapFile()); + client.clientData().gameData().setFrameCount(-1); + client.clientData().gameData().setIsInGame(false); + + when(client.isConnected()).thenReturn(true); + doAnswer(answer -> { + clientUpdate(); + return null; + }).when(client).update(); + doAnswer(answer -> { + System.out.println("onStart()"); + return null; + }).when(listener).onStart(); + doAnswer(answer -> { + System.out.println("onEnd()"); + return null; + }).when(listener).onEnd(anyBoolean()); + doAnswer(answer -> { + System.out.println("onFrame()"); + if (onFrames.containsKey(liveFrame())) { + onFrames.get(liveFrame()).run(); + } + return null; + }).when(listener).onFrame(); + } + + ClientData.GameData liveGameData() { + return client.clientData().gameData(); + } + + void onFrame(Integer frame, Runnable runnable) { + onFrames.put(frame, runnable); + onEndFrame = Math.max(onEndFrame, frame + 1); + } + + void runGame() { + if (configuration.async) { + final long MEGABYTE = 1024 * 1024; + long memoryFree = Runtime.getRuntime().freeMemory() / MEGABYTE; + long memoryRequired = configuration.asyncFrameBufferSize * ClientData.GameData.SIZE / MEGABYTE; + assertTrue( + "Unit test needs to be run with sufficient memory to allocate frame buffer. Has " + + memoryFree + + "mb of " + + memoryRequired + + "mb required.\n" + + "Current JVM arguments: " + + ManagementFactory.getRuntimeMXBean().getInputArguments(), + memoryFree > memoryRequired); + } + bwClient.startGame(configuration); + } + + private int liveFrame() { + return client.clientData().gameData().getFrameCount(); + } + + private void clientUpdate() { + client.clientData().gameData().setFrameCount(liveFrame() + 1); + System.out.println("clientUpdate() to liveFrame #" + liveFrame()); + if (liveFrame() == 0) { + client.clientData().gameData().setIsInGame(true); + client.clientData().gameData().setEventCount(2); + client.clientData().gameData().getEvents(0).setType(EventType.MatchStart); + client.clientData().gameData().getEvents(1).setType(EventType.MatchFrame); + } else if (liveFrame() < onEndFrame) { + client.clientData().gameData().setIsInGame(true); + client.clientData().gameData().setEventCount(1); + client.clientData().gameData().getEvents(0).setType(EventType.MatchFrame); + } else if (liveFrame() == onEndFrame) { + client.clientData().gameData().setIsInGame(true); + client.clientData().gameData().getEvents(0).setType(EventType.MatchEnd); + } else { + client.clientData().gameData().setIsInGame(false); + client.clientData().gameData().setEventCount(0); + } + } +} diff --git a/src/test/java/bwapi/SynchronizationTest.java b/src/test/java/bwapi/SynchronizationTest.java index 0b70acae..54ab8af5 100644 --- a/src/test/java/bwapi/SynchronizationTest.java +++ b/src/test/java/bwapi/SynchronizationTest.java @@ -1,53 +1,77 @@ package bwapi; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.*; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.IntStream; -import net.bytebuddy.implementation.bytecode.Addition; -import org.junit.Before; +import org.junit.Rule; import org.junit.Test; -import org.junit.experimental.theories.DataPoints; -import org.junit.experimental.theories.FromDataPoints; -import org.junit.experimental.theories.Theories; -import org.junit.experimental.theories.Theory; -import org.junit.runner.RunWith; -import org.mockito.AdditionalAnswers; -import org.mockito.Mock; -import org.mockito.Mockito; +import org.junit.rules.Timeout; public class SynchronizationTest { - class Environment { - BWClientConfiguration configuration = new BWClientConfiguration(); - BWEventListener listener = mock(BWEventListener.class); - Client client = mock(Client.class); - - Game game; - public Environment() { - try { - game = GameBuilder.createGame(); - } catch (IOException exception) { - throw new RuntimeException(exception); - } - Mockito.doAnswer(answer -> { stepFrame(); return null; }).when(client).update(); + private void sleepUnchecked(int milliseconds) { + try { + Thread.sleep(milliseconds); + } catch(InterruptedException exception) { + throw new RuntimeException(exception); } + } - public void stepFrame() { - game.clientData().gameData().setFrameCount(game.clientData().gameData().getFrameCount() + 1); - } + @Test + public void sync_IfException_ThrowException() throws InterruptedException { + SynchronizationEnvironment environment = new SynchronizationEnvironment(); + environment.configuration.async = false; + environment.onFrame(0, () -> { throw new RuntimeException("Simulated bot exception"); }); + assertThrows(RuntimeException.class, environment::runGame); + } + + @Test + public void async_IfException_ThrowException() throws InterruptedException { + // An exception in the bot thread must be re-thrown by the main thread. + SynchronizationEnvironment environment = new SynchronizationEnvironment(); + environment.configuration.async = true; + environment.configuration.asyncFrameBufferSize = 3; + environment.onFrame(0, () -> { throw new RuntimeException("Simulated bot exception"); }); + assertThrows(RuntimeException.class, environment::runGame); } @Test - public void synchronizedRuns() { - Environment environment = new Environment(); + public void sync_IfDelay_ThenNoBuffer() throws InterruptedException { + SynchronizationEnvironment environment = new SynchronizationEnvironment(); environment.configuration.async = false; + environment.configuration.asyncFrameDurationMs = 1; + environment.configuration.asyncFrameBufferSize = 3; + IntStream.range(0, 5).forEach(frame -> { + environment.onFrame(frame, () -> { + sleepUnchecked(5); + assertEquals(0, environment.bwClient.framesBehind()); + assertEquals(frame, environment.bwClient.getGame().getFrameCount()); + assertEquals(frame, environment.liveGameData().getFrameCount()); + }); + }); + + environment.runGame(); } + @Test + public void async_IfDelay_ThenBuffer() throws InterruptedException { + SynchronizationEnvironment environment = new SynchronizationEnvironment(); + environment.configuration.async = true; + environment.configuration.asyncFrameDurationMs = 1; + environment.configuration.asyncFrameBufferSize = 3; + environment.onFrame(0, () -> { + sleepUnchecked(5); + assertEquals(2, environment.bwClient.framesBehind()); + assertEquals(0, environment.bwClient.getGame().getFrameCount()); + assertEquals(2, environment.liveGameData().getFrameCount()); + }); + environment.runGame(); + } } From d8e045a4e9da6a936897e5db9ae381861abcd55d Mon Sep 17 00:00:00 2001 From: dgant Date: Thu, 13 Aug 2020 16:06:58 -0400 Subject: [PATCH 21/56] [Failing tests] Unit tests now terminate even if the bot thread dies due to assertion errors --- src/main/java/bwapi/BotWrapper.java | 51 +++++++++++--------- src/main/java/bwapi/FrameBuffer.java | 3 -- src/test/java/bwapi/SynchronizationTest.java | 8 +-- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java index 21fc89e3..56f79877 100644 --- a/src/main/java/bwapi/BotWrapper.java +++ b/src/main/java/bwapi/BotWrapper.java @@ -40,8 +40,8 @@ class BotWrapper { private Thread botThread; private boolean gameOver; private PerformanceMetrics performanceMetrics; - private Exception lastBotException; - private ReentrantLock lastBotExceptionLock = new ReentrantLock(); + private Throwable lastBotThrow; + private ReentrantLock lastBotThrowLock = new ReentrantLock(); BotWrapper(BWClientConfiguration configuration, BWEventListener eventListener) { this.configuration = configuration; @@ -96,9 +96,9 @@ void onFrame() { while (!frameBuffer.empty()) { // Make bot exceptions fall through to the main thread. - Exception lastBotException = getLastBotException(); - if (lastBotException != null) { - throw new RuntimeException(lastBotException); + Throwable lastThrow = getLastBotThrow(); + if (lastThrow != null) { + throw new RuntimeException(lastThrow); } if (configuration.unlimitedFrameZero && isFrameZero) { @@ -116,9 +116,6 @@ void onFrame() { } } else { handleEvents(); - if (lastBotException != null) { - throw new RuntimeException(lastBotException); - } } } @@ -133,10 +130,10 @@ void endGame() { } } - Exception getLastBotException() { - lastBotExceptionLock.lock(); - Exception output = lastBotException; - lastBotExceptionLock.unlock(); + Throwable getLastBotThrow() { + lastBotThrowLock.lock(); + Throwable output = lastBotThrow; + lastBotThrowLock.unlock(); return output; } @@ -155,8 +152,12 @@ private Thread createBotThread() { } game.clientData().setBuffer(frameBuffer.peek()); performanceMetrics.frameBufferSize.record(frameBuffer.framesBuffered() - 1); - handleEvents(); - frameBuffer.dequeue(); + try { + handleEvents(); + } finally { + // In the case where t + frameBuffer.dequeue(); + } } }); } @@ -166,19 +167,21 @@ private void handleEvents() { if (gameData.getFrameCount() > 0 || ! configuration.unlimitedFrameZero) { performanceMetrics.botResponse.startTiming(); } + // Populate gameOver before invoking event handlers (in case the bot throws) + for (int i = 0; i < gameData.getEventCount(); i++) { + gameOver = gameOver || gameData.getEvents(i).getType() == EventType.MatchEnd; + } try { for (int i = 0; i < gameData.getEventCount(); i++) { - ClientData.Event event = gameData.getEvents(i); - EventHandler.operation(eventListener, game, event); - if (event.getType() == EventType.MatchEnd) { - gameOver = true; - } + EventHandler.operation(eventListener, game, gameData.getEvents(i)); } - } catch (Exception exception) { - lastBotExceptionLock.lock(); - lastBotException = exception; - lastBotExceptionLock.unlock(); + } catch (Throwable throwable) { + lastBotThrowLock.lock(); + lastBotThrow = throwable; + lastBotThrowLock.unlock(); + throw throwable; + } finally { + performanceMetrics.botResponse.stopTiming(); } - performanceMetrics.botResponse.stopTiming(); } } diff --git a/src/main/java/bwapi/FrameBuffer.java b/src/main/java/bwapi/FrameBuffer.java index 6564fa30..3553abd9 100644 --- a/src/main/java/bwapi/FrameBuffer.java +++ b/src/main/java/bwapi/FrameBuffer.java @@ -45,7 +45,6 @@ class FrameBuffer { private int stepGame = 0; private int stepBot = 0; private ArrayList dataBuffer = new ArrayList<>(); - private final String architecture; // Synchronization locks private final Lock lockWrite = new ReentrantLock(); @@ -55,10 +54,8 @@ class FrameBuffer { FrameBuffer(int size) { this.size = size; while(dataBuffer.size() < size) { - System.out.println("Allocating " + BUFFER_SIZE / 1024 / 1024 + "mb"); dataBuffer.add(ByteBuffer.allocateDirect(BUFFER_SIZE)); } - architecture = System.getProperty("sun.arch.data.model"); } /** diff --git a/src/test/java/bwapi/SynchronizationTest.java b/src/test/java/bwapi/SynchronizationTest.java index 54ab8af5..55a76ccb 100644 --- a/src/test/java/bwapi/SynchronizationTest.java +++ b/src/test/java/bwapi/SynchronizationTest.java @@ -66,11 +66,11 @@ public void async_IfDelay_ThenBuffer() throws InterruptedException { environment.configuration.async = true; environment.configuration.asyncFrameDurationMs = 1; environment.configuration.asyncFrameBufferSize = 3; - environment.onFrame(0, () -> { + environment.onFrame(1, () -> { sleepUnchecked(5); - assertEquals(2, environment.bwClient.framesBehind()); - assertEquals(0, environment.bwClient.getGame().getFrameCount()); - assertEquals(2, environment.liveGameData().getFrameCount()); + assertEquals("Bot should be observing an old frame", 1, environment.bwClient.getGame().getFrameCount()); + assertEquals("Bot should be behind the live game", 2, environment.bwClient.framesBehind()); + assertEquals("Client should be as far ahead as the frame buffer allows", 3, environment.liveGameData().getFrameCount()); }); environment.runGame(); } From 4be2706395ec52013f603a4f60907763c448c761 Mon Sep 17 00:00:00 2001 From: dgant Date: Thu, 13 Aug 2020 16:36:45 -0400 Subject: [PATCH 22/56] Restored stepwise logging to monitor asynchronous steps --- src/main/java/bwapi/BWClientConfiguration.java | 11 +++++++++++ src/main/java/bwapi/BotWrapper.java | 16 ++++++++++++++++ src/main/java/bwapi/Game.java | 1 - src/main/java/bwapi/PerformanceMetric.java | 2 ++ src/test/java/bwapi/SynchronizationTest.java | 5 +++-- 5 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/main/java/bwapi/BWClientConfiguration.java b/src/main/java/bwapi/BWClientConfiguration.java index 9328c17c..90323046 100644 --- a/src/main/java/bwapi/BWClientConfiguration.java +++ b/src/main/java/bwapi/BWClientConfiguration.java @@ -50,6 +50,11 @@ public class BWClientConfiguration { */ public int asyncFrameBufferSize = 10; + /** + * Toggles verbose logging, particularly of synchronization steps. + */ + public boolean logVerbosely = false; + /** * Checks that the configuration is in a valid state. Throws an IllegalArgumentException if it isn't. */ @@ -61,4 +66,10 @@ public void validate() { throw new IllegalArgumentException("asyncFrameBufferSize needs to be a positive number (There needs to be at least one frame buffer)."); } } + + public void log(String value) { + if (logVerbosely) { + System.out.println(value); + } + } } \ No newline at end of file diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java index 56f79877..96bfc42e 100644 --- a/src/main/java/bwapi/BotWrapper.java +++ b/src/main/java/bwapi/BotWrapper.java @@ -76,9 +76,11 @@ Game getGame() { */ void onFrame() { if (configuration.async) { + configuration.log("Main: onFrame asynchronous start"); long startNanos = System.nanoTime(); long endNanos = startNanos + configuration.asyncFrameDurationMs * 1000000; if (botThread == null) { + configuration.log("Main: Starting bot thread"); botThread = createBotThread(); botThread.setName("JBWAPI Bot"); botThread.start(); @@ -88,24 +90,29 @@ void onFrame() { If buffer is full, it will wait until it has capacity Wait for empty buffer OR termination condition */ + configuration.log("Main: Enqueuing frame"); boolean isFrameZero = liveClientData.gameData().getFrameCount() == 0; frameBuffer.enqueueFrame(); performanceMetrics.bwapiResponse.startTiming(); frameBuffer.lockSize.lock(); try { while (!frameBuffer.empty()) { + configuration.log("Main: Waiting for empty frame buffer"); // Make bot exceptions fall through to the main thread. Throwable lastThrow = getLastBotThrow(); if (lastThrow != null) { + configuration.log("Main: Rethrowing bot throwable"); throw new RuntimeException(lastThrow); } if (configuration.unlimitedFrameZero && isFrameZero) { + configuration.log("Main: Waiting indefinitely on frame 0"); frameBuffer.conditionSize.await(); } else { long remainingNanos = endNanos - System.nanoTime(); if (remainingNanos <= 0) break; + configuration.log("Main: Waiting " + remainingNanos / 1000000 + "ms for bot"); frameBuffer.conditionSize.awaitNanos(remainingNanos); } } @@ -113,10 +120,14 @@ void onFrame() { } finally { frameBuffer.lockSize.unlock(); performanceMetrics.bwapiResponse.stopTiming(); + configuration.log("Main: onFrame asynchronous end"); } } else { + configuration.log("Main: onFrame synchronous start"); handleEvents(); + configuration.log("Main: onFrame synchronous end"); } + } /** @@ -139,10 +150,13 @@ Throwable getLastBotThrow() { private Thread createBotThread() { return new Thread(() -> { + configuration.log("Bot: Thread started"); while ( ! gameOver) { + configuration.log("Bot: Attempting to handle next frame"); frameBuffer.lockSize.lock(); try { while (frameBuffer.empty()) { + configuration.log("Bot: Waiting for next frame"); performanceMetrics.botIdle.startTiming(); frameBuffer.conditionSize.awaitUninterruptibly(); } @@ -150,9 +164,11 @@ private Thread createBotThread() { } finally { frameBuffer.lockSize.unlock(); } + configuration.log("Bot: Peeking next frame"); game.clientData().setBuffer(frameBuffer.peek()); performanceMetrics.frameBufferSize.record(frameBuffer.framesBuffered() - 1); try { + configuration.log("Bot: Handling frame #" + game.getFrameCount()); handleEvents(); } finally { // In the case where t diff --git a/src/main/java/bwapi/Game.java b/src/main/java/bwapi/Game.java index 8321d437..be638d5c 100644 --- a/src/main/java/bwapi/Game.java +++ b/src/main/java/bwapi/Game.java @@ -142,7 +142,6 @@ private static boolean hasPower(final int x, final int y, final UnitType unitTyp Call this method in EventHander::OnMatchStart */ void init() { - System.out.println("Game.init()"); // TODO: REMOVE! visibleUnits.clear(); final int forceCount = gameData().getForceCount(); diff --git a/src/main/java/bwapi/PerformanceMetric.java b/src/main/java/bwapi/PerformanceMetric.java index 8061cf61..eeb103f6 100644 --- a/src/main/java/bwapi/PerformanceMetric.java +++ b/src/main/java/bwapi/PerformanceMetric.java @@ -8,6 +8,7 @@ public class PerformanceMetric { public double minValue = Long.MAX_VALUE; public double maxValue = Long.MIN_VALUE; + public double lastValue = 0; public double avgValue = 0; public double avgValueExceeding = 0; public int samples = 0; @@ -44,6 +45,7 @@ void stopTiming() { } void record(double value) { + lastValue = value; minValue = Math.min(minValue, value); maxValue = Math.max(maxValue, value); avgValue = (avgValue * samples + value) / (samples + 1d); diff --git a/src/test/java/bwapi/SynchronizationTest.java b/src/test/java/bwapi/SynchronizationTest.java index 55a76ccb..743d70e6 100644 --- a/src/test/java/bwapi/SynchronizationTest.java +++ b/src/test/java/bwapi/SynchronizationTest.java @@ -66,11 +66,12 @@ public void async_IfDelay_ThenBuffer() throws InterruptedException { environment.configuration.async = true; environment.configuration.asyncFrameDurationMs = 1; environment.configuration.asyncFrameBufferSize = 3; + environment.configuration.logVerbosely = true; environment.onFrame(1, () -> { - sleepUnchecked(5); + sleepUnchecked(50); assertEquals("Bot should be observing an old frame", 1, environment.bwClient.getGame().getFrameCount()); - assertEquals("Bot should be behind the live game", 2, environment.bwClient.framesBehind()); assertEquals("Client should be as far ahead as the frame buffer allows", 3, environment.liveGameData().getFrameCount()); + assertEquals("Bot should be behind the live game", 2, environment.bwClient.framesBehind()); }); environment.runGame(); } From c30289b0b4dce45dbe8693ef2ec4e46e04946b7d Mon Sep 17 00:00:00 2001 From: dgant Date: Thu, 13 Aug 2020 17:10:34 -0400 Subject: [PATCH 23/56] Fixed bug in which BotWrapper was looking at bot data to determine the live frame --- src/main/java/bwapi/BotWrapper.java | 17 +++++++++-------- src/main/java/bwapi/ClientData.java | 1 + src/main/java/bwapi/FrameBuffer.java | 10 +++++----- src/main/java/bwapi/Game.java | 5 +++-- src/test/java/bwapi/ClientDataBenchmark.java | 6 ++++-- src/test/java/bwapi/GameBuilder.java | 3 ++- src/test/java/bwapi/GameTest.java | 2 +- .../java/bwapi/SynchronizationEnvironment.java | 8 ++++---- 8 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java index 96bfc42e..47925d9c 100644 --- a/src/main/java/bwapi/BotWrapper.java +++ b/src/main/java/bwapi/BotWrapper.java @@ -52,13 +52,14 @@ class BotWrapper { /** * Resets the BotWrapper for a new game. */ - void startNewGame(ByteBuffer dataSource, PerformanceMetrics performanceMetrics) { + void startNewGame(ByteBuffer liveData, PerformanceMetrics performanceMetrics) { if (configuration.async) { - frameBuffer.initialize(dataSource, performanceMetrics); + frameBuffer.initialize(liveData, performanceMetrics); } this.performanceMetrics = performanceMetrics; - game = new Game(liveClientData); - liveClientData.setBuffer(dataSource); + game = new Game(); + game.clientData().setBuffer(liveData); + liveClientData.setBuffer(liveData); botThread = null; gameOver = false; } @@ -90,8 +91,8 @@ void onFrame() { If buffer is full, it will wait until it has capacity Wait for empty buffer OR termination condition */ - configuration.log("Main: Enqueuing frame"); - boolean isFrameZero = liveClientData.gameData().getFrameCount() == 0; + int frame = liveClientData.gameData().getFrameCount(); + configuration.log("Main: Enqueuing frame #" + frame); frameBuffer.enqueueFrame(); performanceMetrics.bwapiResponse.startTiming(); frameBuffer.lockSize.lock(); @@ -106,8 +107,8 @@ void onFrame() { throw new RuntimeException(lastThrow); } - if (configuration.unlimitedFrameZero && isFrameZero) { - configuration.log("Main: Waiting indefinitely on frame 0"); + if (configuration.unlimitedFrameZero && frame == 0) { + configuration.log("Main: Waiting indefinitely on frame " + frame); frameBuffer.conditionSize.await(); } else { long remainingNanos = endNanos - System.nanoTime(); diff --git a/src/main/java/bwapi/ClientData.java b/src/main/java/bwapi/ClientData.java index f3723572..cf17996f 100644 --- a/src/main/java/bwapi/ClientData.java +++ b/src/main/java/bwapi/ClientData.java @@ -4,6 +4,7 @@ class ClientData { private WrappedBuffer buffer; private GameData gameData; ClientData() { + System.out.println("new ClientData()"); // TODO: REMOVE! gameData = new GameData(0); } GameData gameData() { diff --git a/src/main/java/bwapi/FrameBuffer.java b/src/main/java/bwapi/FrameBuffer.java index 3553abd9..4287f0e3 100644 --- a/src/main/java/bwapi/FrameBuffer.java +++ b/src/main/java/bwapi/FrameBuffer.java @@ -39,7 +39,7 @@ of this software and associated documentation files (the "Software"), to deal class FrameBuffer { private static final int BUFFER_SIZE = ClientData.GameData.SIZE; - private ByteBuffer dataSource; + private ByteBuffer liveData; private PerformanceMetrics performanceMetrics; private int size; private int stepGame = 0; @@ -61,8 +61,8 @@ class FrameBuffer { /** * Resets for a new game */ - void initialize(ByteBuffer dataSource, PerformanceMetrics performanceMetrics) { - this.dataSource = dataSource; + void initialize(ByteBuffer liveData, PerformanceMetrics performanceMetrics) { + this.liveData = liveData; this.performanceMetrics = performanceMetrics; stepGame = 0; stepBot = 0; @@ -125,7 +125,7 @@ void enqueueFrame() { performanceMetrics.copyingToBuffer.time(() -> { ByteBuffer dataTarget = dataBuffer.get(indexGame()); - copyBuffer(dataSource, dataTarget); + copyBuffer(liveData, dataTarget); }); lockSize.lock(); @@ -190,6 +190,6 @@ void copyBuffer(ByteBuffer source, ByteBuffer destination) { // and serves to document the known-good (and cross-platform, for BWAPI 5) way to executing the copy. source.rewind(); destination.rewind(); - destination.put(dataSource); + destination.put(liveData); } } diff --git a/src/main/java/bwapi/Game.java b/src/main/java/bwapi/Game.java index be638d5c..d9dc8ac5 100644 --- a/src/main/java/bwapi/Game.java +++ b/src/main/java/bwapi/Game.java @@ -101,8 +101,8 @@ public class Game { public final SideEffectQueue sideEffects = new SideEffectQueue(); - Game(ClientData clientData) { - this.clientData = clientData; + Game() { + clientData = new ClientData(); } ClientData clientData() { @@ -142,6 +142,7 @@ private static boolean hasPower(final int x, final int y, final UnitType unitTyp Call this method in EventHander::OnMatchStart */ void init() { + System.out.println("Game.init()"); // TODO: REMOVE! visibleUnits.clear(); final int forceCount = gameData().getForceCount(); diff --git a/src/test/java/bwapi/ClientDataBenchmark.java b/src/test/java/bwapi/ClientDataBenchmark.java index 6acd3c77..fd814d04 100644 --- a/src/test/java/bwapi/ClientDataBenchmark.java +++ b/src/test/java/bwapi/ClientDataBenchmark.java @@ -20,7 +20,8 @@ public static class EmptyState { @Setup(Level.Invocation) public void setup() { client = new Client(ByteBuffer.allocateDirect(ClientData.GameData.SIZE)); - game = new Game(client.clientData()); + game = new Game(); + game.clientData().setBuffer(client.mapFile()); strings = buildStrings(); } @@ -36,7 +37,8 @@ public static class FilledWithStrings { public void setup() { client = new Client(ByteBuffer.allocateDirect(ClientData.GameData.SIZE)); data = client.clientData().gameData(); - game = new Game(client.clientData()); + game = new Game(); + game.clientData().setBuffer(client.mapFile()); String[] strings = buildStrings(); for (String s : strings) { GameDataUtils.addString(client.clientData().gameData(), s); diff --git a/src/test/java/bwapi/GameBuilder.java b/src/test/java/bwapi/GameBuilder.java index 426df684..63886fa0 100644 --- a/src/test/java/bwapi/GameBuilder.java +++ b/src/test/java/bwapi/GameBuilder.java @@ -23,7 +23,8 @@ public static Game createGame(String mapName) throws IOException { } public static Game createGame(Client client) { - final Game game = new Game(client.clientData()); + final Game game = new Game(); + game.clientData().setBuffer(client.mapFile()); game.init(); return game; } diff --git a/src/test/java/bwapi/GameTest.java b/src/test/java/bwapi/GameTest.java index 4633b37c..568a4b92 100644 --- a/src/test/java/bwapi/GameTest.java +++ b/src/test/java/bwapi/GameTest.java @@ -21,7 +21,7 @@ public class GameTest { private List allUnits = new ArrayList<>(); - private Game sut = new Game(mock(ClientData.class)) { + private Game sut = new Game() { @Override public List getAllUnits() { return allUnits; diff --git a/src/test/java/bwapi/SynchronizationEnvironment.java b/src/test/java/bwapi/SynchronizationEnvironment.java index 5428022f..06ba9529 100644 --- a/src/test/java/bwapi/SynchronizationEnvironment.java +++ b/src/test/java/bwapi/SynchronizationEnvironment.java @@ -43,15 +43,15 @@ public class SynchronizationEnvironment { return null; }).when(client).update(); doAnswer(answer -> { - System.out.println("onStart()"); + System.out.println("Test: onStart()"); return null; }).when(listener).onStart(); doAnswer(answer -> { - System.out.println("onEnd()"); + System.out.println("Test: onEnd()"); return null; }).when(listener).onEnd(anyBoolean()); doAnswer(answer -> { - System.out.println("onFrame()"); + System.out.println("Test: onFrame()"); if (onFrames.containsKey(liveFrame())) { onFrames.get(liveFrame()).run(); } @@ -92,7 +92,7 @@ private int liveFrame() { private void clientUpdate() { client.clientData().gameData().setFrameCount(liveFrame() + 1); - System.out.println("clientUpdate() to liveFrame #" + liveFrame()); + System.out.println("Test: clientUpdate() to liveFrame #" + liveFrame()); if (liveFrame() == 0) { client.clientData().gameData().setIsInGame(true); client.clientData().gameData().setEventCount(2); From 1970dd5400572e0babd20ac7e624695debdbfd2c Mon Sep 17 00:00:00 2001 From: dgant Date: Thu, 13 Aug 2020 17:20:38 -0400 Subject: [PATCH 24/56] Removed console noise caused by bot thread dying (as expected) from exception test --- src/main/java/bwapi/BotWrapper.java | 66 ++++++++++--------- src/main/java/bwapi/ClientData.java | 1 - src/main/java/bwapi/Game.java | 1 - .../bwapi/SynchronizationEnvironment.java | 8 +-- src/test/java/bwapi/SynchronizationTest.java | 9 ++- 5 files changed, 44 insertions(+), 41 deletions(-) diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java index 47925d9c..dd5fbeb5 100644 --- a/src/main/java/bwapi/BotWrapper.java +++ b/src/main/java/bwapi/BotWrapper.java @@ -151,28 +151,41 @@ Throwable getLastBotThrow() { private Thread createBotThread() { return new Thread(() -> { - configuration.log("Bot: Thread started"); - while ( ! gameOver) { - configuration.log("Bot: Attempting to handle next frame"); - frameBuffer.lockSize.lock(); - try { - while (frameBuffer.empty()) { - configuration.log("Bot: Waiting for next frame"); - performanceMetrics.botIdle.startTiming(); - frameBuffer.conditionSize.awaitUninterruptibly(); + try { + configuration.log("Bot: Thread started"); + while (!gameOver) { + + configuration.log("Bot: Attempting to handle next frame"); + frameBuffer.lockSize.lock(); + try { + while (frameBuffer.empty()) { + configuration.log("Bot: Waiting for next frame"); + performanceMetrics.botIdle.startTiming(); + frameBuffer.conditionSize.awaitUninterruptibly(); + } + performanceMetrics.botIdle.stopTiming(); + } finally { + frameBuffer.lockSize.unlock(); } - performanceMetrics.botIdle.stopTiming(); - } finally { - frameBuffer.lockSize.unlock(); - } - configuration.log("Bot: Peeking next frame"); - game.clientData().setBuffer(frameBuffer.peek()); - performanceMetrics.frameBufferSize.record(frameBuffer.framesBuffered() - 1); - try { + + configuration.log("Bot: Peeking next frame"); + game.clientData().setBuffer(frameBuffer.peek()); + performanceMetrics.frameBufferSize.record(frameBuffer.framesBuffered() - 1); + configuration.log("Bot: Handling frame #" + game.getFrameCount()); handleEvents(); - } finally { - // In the case where t + frameBuffer.dequeue(); + } + } catch (Throwable throwable) { + // Record the throw, + // Then allow the thread to terminate silently. + // The main thread will look for the stored throw. + lastBotThrowLock.lock(); + lastBotThrow = throwable; + lastBotThrowLock.unlock(); + + // Awaken any threads waiting on bot progress + while (!frameBuffer.empty()) { frameBuffer.dequeue(); } } @@ -184,21 +197,14 @@ private void handleEvents() { if (gameData.getFrameCount() > 0 || ! configuration.unlimitedFrameZero) { performanceMetrics.botResponse.startTiming(); } + // Populate gameOver before invoking event handlers (in case the bot throws) for (int i = 0; i < gameData.getEventCount(); i++) { gameOver = gameOver || gameData.getEvents(i).getType() == EventType.MatchEnd; } - try { - for (int i = 0; i < gameData.getEventCount(); i++) { - EventHandler.operation(eventListener, game, gameData.getEvents(i)); - } - } catch (Throwable throwable) { - lastBotThrowLock.lock(); - lastBotThrow = throwable; - lastBotThrowLock.unlock(); - throw throwable; - } finally { - performanceMetrics.botResponse.stopTiming(); + for (int i = 0; i < gameData.getEventCount(); i++) { + EventHandler.operation(eventListener, game, gameData.getEvents(i)); } + performanceMetrics.botResponse.stopTiming(); } } diff --git a/src/main/java/bwapi/ClientData.java b/src/main/java/bwapi/ClientData.java index cf17996f..f3723572 100644 --- a/src/main/java/bwapi/ClientData.java +++ b/src/main/java/bwapi/ClientData.java @@ -4,7 +4,6 @@ class ClientData { private WrappedBuffer buffer; private GameData gameData; ClientData() { - System.out.println("new ClientData()"); // TODO: REMOVE! gameData = new GameData(0); } GameData gameData() { diff --git a/src/main/java/bwapi/Game.java b/src/main/java/bwapi/Game.java index d9dc8ac5..1562fb97 100644 --- a/src/main/java/bwapi/Game.java +++ b/src/main/java/bwapi/Game.java @@ -142,7 +142,6 @@ private static boolean hasPower(final int x, final int y, final UnitType unitTyp Call this method in EventHander::OnMatchStart */ void init() { - System.out.println("Game.init()"); // TODO: REMOVE! visibleUnits.clear(); final int forceCount = gameData().getForceCount(); diff --git a/src/test/java/bwapi/SynchronizationEnvironment.java b/src/test/java/bwapi/SynchronizationEnvironment.java index 06ba9529..f1a64c60 100644 --- a/src/test/java/bwapi/SynchronizationEnvironment.java +++ b/src/test/java/bwapi/SynchronizationEnvironment.java @@ -43,15 +43,15 @@ public class SynchronizationEnvironment { return null; }).when(client).update(); doAnswer(answer -> { - System.out.println("Test: onStart()"); + configuration.log("Test: onStart()"); return null; }).when(listener).onStart(); doAnswer(answer -> { - System.out.println("Test: onEnd()"); + configuration.log("Test: onEnd()"); return null; }).when(listener).onEnd(anyBoolean()); doAnswer(answer -> { - System.out.println("Test: onFrame()"); + configuration.log("Test: onFrame()"); if (onFrames.containsKey(liveFrame())) { onFrames.get(liveFrame()).run(); } @@ -92,7 +92,7 @@ private int liveFrame() { private void clientUpdate() { client.clientData().gameData().setFrameCount(liveFrame() + 1); - System.out.println("Test: clientUpdate() to liveFrame #" + liveFrame()); + configuration.log("Test: clientUpdate() to liveFrame #" + liveFrame()); if (liveFrame() == 0) { client.clientData().gameData().setIsInGame(true); client.clientData().gameData().setEventCount(2); diff --git a/src/test/java/bwapi/SynchronizationTest.java b/src/test/java/bwapi/SynchronizationTest.java index 743d70e6..c5596620 100644 --- a/src/test/java/bwapi/SynchronizationTest.java +++ b/src/test/java/bwapi/SynchronizationTest.java @@ -65,13 +65,12 @@ public void async_IfDelay_ThenBuffer() throws InterruptedException { SynchronizationEnvironment environment = new SynchronizationEnvironment(); environment.configuration.async = true; environment.configuration.asyncFrameDurationMs = 1; - environment.configuration.asyncFrameBufferSize = 3; - environment.configuration.logVerbosely = true; + environment.configuration.asyncFrameBufferSize = 4; environment.onFrame(1, () -> { - sleepUnchecked(50); + sleepUnchecked(5); assertEquals("Bot should be observing an old frame", 1, environment.bwClient.getGame().getFrameCount()); - assertEquals("Client should be as far ahead as the frame buffer allows", 3, environment.liveGameData().getFrameCount()); - assertEquals("Bot should be behind the live game", 2, environment.bwClient.framesBehind()); + assertEquals("Client should be as far ahead as the frame buffer allows", 4, environment.liveGameData().getFrameCount()); + assertEquals("Bot should be behind the live game", 3, environment.bwClient.framesBehind()); }); environment.runGame(); } From 20f9d005877bf65fa6730fb48b541ea9cc0fadee Mon Sep 17 00:00:00 2001 From: dgant Date: Fri, 14 Aug 2020 14:32:28 -0400 Subject: [PATCH 25/56] All tests passing. Stubbed out performance metrics tests and added bot frame performance test --- src/main/java/bwapi/BotWrapper.java | 15 +- src/main/java/bwapi/PerformanceMetric.java | 36 ++++ .../bwapi/SynchronizationEnvironment.java | 10 +- src/test/java/bwapi/SynchronizationTest.java | 187 ++++++++++++++++-- 4 files changed, 227 insertions(+), 21 deletions(-) diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java index dd5fbeb5..d93eff16 100644 --- a/src/main/java/bwapi/BotWrapper.java +++ b/src/main/java/bwapi/BotWrapper.java @@ -194,17 +194,18 @@ private Thread createBotThread() { private void handleEvents() { ClientData.GameData gameData = game.clientData().gameData(); - if (gameData.getFrameCount() > 0 || ! configuration.unlimitedFrameZero) { - performanceMetrics.botResponse.startTiming(); - } // Populate gameOver before invoking event handlers (in case the bot throws) for (int i = 0; i < gameData.getEventCount(); i++) { gameOver = gameOver || gameData.getEvents(i).getType() == EventType.MatchEnd; } - for (int i = 0; i < gameData.getEventCount(); i++) { - EventHandler.operation(eventListener, game, gameData.getEvents(i)); - } - performanceMetrics.botResponse.stopTiming(); + + performanceMetrics.botResponse.timeIf( + ! gameOver && (gameData.getFrameCount() > 0 || ! configuration.unlimitedFrameZero), + () -> { + for (int i = 0; i < gameData.getEventCount(); i++) { + EventHandler.operation(eventListener, game, gameData.getEvents(i)); + } + }); } } diff --git a/src/main/java/bwapi/PerformanceMetric.java b/src/main/java/bwapi/PerformanceMetric.java index eeb103f6..ed1d76bc 100644 --- a/src/main/java/bwapi/PerformanceMetric.java +++ b/src/main/java/bwapi/PerformanceMetric.java @@ -2,6 +2,9 @@ import java.text.DecimalFormat; +/** + * Aggregates labeled time series data. + */ public class PerformanceMetric { private final String name; private final long maxAllowed; @@ -22,12 +25,33 @@ public class PerformanceMetric { this.maxAllowed = maxAllowed; } + /** + * Records the duration of a function call. + * @param runnable The function to time + */ void time(Runnable runnable) { startTiming(); runnable.run(); stopTiming(); } + /** + * Convenience method; calls a function; but only records the duration if a condition is met + * @param condition Whether to record the function call duration + * @param runnable The function to call + */ + void timeIf(boolean condition, Runnable runnable) { + if (condition) { + time(runnable); + } else { + runnable.run(); + } + } + + /** + * Manually start timing. + * The next call to stopTiming() will record the duration in fractional milliseconds. + */ void startTiming() { if (timeStarted > 0) { ++interrupted; @@ -35,6 +59,11 @@ void startTiming() { timeStarted = System.nanoTime(); } + + /** + * Manually stop timing. + * If paired with a previous call to startTiming(), records the measured time between the calls in fractional milliseconds. + */ void stopTiming() { if (timeStarted <= 0) return; // Use nanosecond resolution timer, but record in units of milliseconds. @@ -44,6 +73,9 @@ void stopTiming() { record(timeDiff / 1000000d); } + /** + * Manually records a specific value. + */ void record(double value) { lastValue = value; minValue = Math.min(minValue, value); @@ -54,8 +86,12 @@ void record(double value) { avgValueExceeding = (avgValueExceeding * samplesExceeding + value) / (samplesExceeding + 1d); ++samplesExceeding; } + System.out.println(name + " #" + samples + " = " + value); // TODO: REMOVE } + /** + * @return A pretty-printed description of the recorded values. + */ @Override public String toString() { DecimalFormat formatter = new DecimalFormat("###,###.#"); diff --git a/src/test/java/bwapi/SynchronizationEnvironment.java b/src/test/java/bwapi/SynchronizationEnvironment.java index f1a64c60..764345e9 100644 --- a/src/test/java/bwapi/SynchronizationEnvironment.java +++ b/src/test/java/bwapi/SynchronizationEnvironment.java @@ -63,12 +63,20 @@ ClientData.GameData liveGameData() { return client.clientData().gameData(); } + PerformanceMetrics metrics() { + return bwClient.getPerformanceMetrics(); + } + void onFrame(Integer frame, Runnable runnable) { onFrames.put(frame, runnable); - onEndFrame = Math.max(onEndFrame, frame + 1); } void runGame() { + runGame(10); + } + + void runGame(int onEndFrame) { + this.onEndFrame = onEndFrame; if (configuration.async) { final long MEGABYTE = 1024 * 1024; long memoryFree = Runtime.getRuntime().freeMemory() / MEGABYTE; diff --git a/src/test/java/bwapi/SynchronizationTest.java b/src/test/java/bwapi/SynchronizationTest.java index c5596620..772e789a 100644 --- a/src/test/java/bwapi/SynchronizationTest.java +++ b/src/test/java/bwapi/SynchronizationTest.java @@ -1,18 +1,12 @@ package bwapi; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThrows; -import static org.mockito.Mockito.*; +import org.junit.Test; -import java.util.HashMap; -import java.util.Map; import java.util.stream.IntStream; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.Timeout; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; public class SynchronizationTest { @@ -24,6 +18,26 @@ private void sleepUnchecked(int milliseconds) { } } + private String describeApproximateExpectation(double expected, double actual, double margin) { + return "Expected " + expected + " == " + actual + " +/- " + margin; + } + + private boolean measureApproximateEquality(double expected, double actual, double margin) { + return expected + margin >= actual && expected - margin <= actual; + } + + private void assertWithin(double expected, double actual, double margin) { + assertTrue( + describeApproximateExpectation(expected, actual, margin), + measureApproximateEquality(expected, actual, margin)); + } + + private void assertWithin(String message, double expected, double actual, double margin) { + assertTrue( + message + ": " + describeApproximateExpectation(expected, actual, margin), + measureApproximateEquality(expected, actual, margin)); + } + @Test public void sync_IfException_ThrowException() throws InterruptedException { SynchronizationEnvironment environment = new SynchronizationEnvironment(); @@ -48,6 +62,7 @@ public void sync_IfDelay_ThenNoBuffer() throws InterruptedException { environment.configuration.async = false; environment.configuration.asyncFrameDurationMs = 1; environment.configuration.asyncFrameBufferSize = 3; + IntStream.range(0, 5).forEach(frame -> { environment.onFrame(frame, () -> { sleepUnchecked(5); @@ -61,17 +76,163 @@ public void sync_IfDelay_ThenNoBuffer() throws InterruptedException { } @Test - public void async_IfDelay_ThenBuffer() throws InterruptedException { + public void async_IfBotDelay_ThenClientBuffers() throws InterruptedException { SynchronizationEnvironment environment = new SynchronizationEnvironment(); environment.configuration.async = true; - environment.configuration.asyncFrameDurationMs = 1; + environment.configuration.asyncFrameDurationMs = 10; environment.configuration.asyncFrameBufferSize = 4; + environment.onFrame(1, () -> { - sleepUnchecked(5); + sleepUnchecked(40); assertEquals("Bot should be observing an old frame", 1, environment.bwClient.getGame().getFrameCount()); assertEquals("Client should be as far ahead as the frame buffer allows", 4, environment.liveGameData().getFrameCount()); assertEquals("Bot should be behind the live game", 3, environment.bwClient.framesBehind()); }); + + environment.onFrame(6, () -> { // Maybe it should be possible to demand that these assertions pass a frame earlier? + assertEquals("Bot should be observing the live frame", 6, environment.bwClient.getGame().getFrameCount()); + assertEquals("Client should not be ahead of the bot", 6, environment.liveGameData().getFrameCount()); + assertEquals("Bot should not be behind the live game", 0, environment.bwClient.framesBehind()); + }); + + environment.runGame(); + } + + @Test + public void async_IfBotDelay_ThenClientStalls() throws InterruptedException { + SynchronizationEnvironment environment = new SynchronizationEnvironment(); + environment.configuration.async = true; + environment.configuration.asyncFrameDurationMs = 50; + environment.configuration.asyncFrameBufferSize = 5; + + environment.onFrame(1, () -> { + sleepUnchecked(125); + assertEquals("3: Bot should be observing an old frame", 1, environment.bwClient.getGame().getFrameCount()); + assertEquals("3: Client should have progressed as slowly as possible", 3, environment.liveGameData().getFrameCount()); + assertEquals("3: Bot should be behind the live game by as little as possible", 2, environment.bwClient.framesBehind()); + sleepUnchecked(50); + assertEquals("4: Bot should be observing an old frame", 1, environment.bwClient.getGame().getFrameCount()); + assertEquals("4: Client should have progressed as slowly as possible", 4, environment.liveGameData().getFrameCount()); + assertEquals("4: Bot should be behind the live game by as little as possible", 3, environment.bwClient.framesBehind()); + sleepUnchecked(50); + assertEquals("5: Bot should be observing an old frame", 1, environment.bwClient.getGame().getFrameCount()); + assertEquals("5: Client should have progressed as slowly as possible", 5, environment.liveGameData().getFrameCount()); + assertEquals("5: Bot should be behind the live game by as little as possible", 4, environment.bwClient.framesBehind()); + }); + environment.runGame(); } + + @Test + public void async_IfFrameZeroWaitsEnabled_ThenAllowInfiniteTime() throws InterruptedException { + SynchronizationEnvironment environment = new SynchronizationEnvironment(); + environment.configuration.async = true; + environment.configuration.unlimitedFrameZero = true; + environment.configuration.asyncFrameDurationMs = 5; + environment.configuration.asyncFrameBufferSize = 2; + + environment.onFrame(0, () -> { + sleepUnchecked(50); + assertEquals("Bot should still be on frame zero", 0, environment.bwClient.getGame().getFrameCount()); + assertEquals("Client should still be on frame zero", 0, environment.liveGameData().getFrameCount()); + assertEquals("Bot should not be behind the live game", 0, environment.bwClient.framesBehind()); + }); + + environment.runGame(); + } + + @Test + public void async_IfFrameZeroWaitsDisabled_ThenClientBuffers() throws InterruptedException { + SynchronizationEnvironment environment = new SynchronizationEnvironment(); + environment.configuration.async = true; + environment.configuration.unlimitedFrameZero = false; + environment.configuration.asyncFrameDurationMs = 5; + environment.configuration.asyncFrameBufferSize = 2; + + environment.onFrame(0, () -> { + sleepUnchecked(50); + assertEquals("Bot should still be on frame zero", 0, environment.bwClient.getGame().getFrameCount()); + assertEquals("Client should have advanced to the next frame", 2, environment.liveGameData().getFrameCount()); + assertEquals("Bot should be behind the live game", 2, environment.bwClient.framesBehind()); + }); + + environment.runGame(); + } + + @Test + public void async_MeasurePerformance_TotalFrameDuration() { + + } + + @Test + public void async_MeasurePerformance_CopyingToBuffer() { + + } + + @Test + public void async_MeasurePerformance_IntentionallyBlocking() { + + } + + @Test + public void async_MeasurePerformance_FrameBufferSize() { + + } + + @Test + public void async_MeasurePerformance_FlushSideEffects() { + + } + + /** + * Number of milliseconds of leeway to give in performance metrics. + * Increase if tests are flaky due to variance in execution speed. + */ + private final static long MS_MARGIN = 10; + + @Test + public void async_MeasurePerformance_BotResponse() { + SynchronizationEnvironment environment = new SynchronizationEnvironment(); + + // Frame zero appears to take an extra 60ms, so let's disable timing for it + // (and also verify that we omit frame zero from performance metrics) + environment.configuration.unlimitedFrameZero = true; + + environment.onFrame(1, () -> { + sleepUnchecked(100); + }); + environment.onFrame(2, () -> { + assertWithin("2: Bot response average", 100, environment.metrics().botResponse.avgValue, MS_MARGIN); + assertWithin("2: Bot response minimum", 100, environment.metrics().botResponse.minValue, MS_MARGIN); + assertWithin("2: Bot response maximum", 100, environment.metrics().botResponse.maxValue, MS_MARGIN); + assertWithin("2: Bot response previous", 100, environment.metrics().botResponse.lastValue, MS_MARGIN); + sleepUnchecked(300); + }); + environment.onFrame(3, () -> { + assertWithin("3: Bot response average", 200, environment.metrics().botResponse.avgValue, MS_MARGIN); + assertWithin("3: Bot response minimum", 100, environment.metrics().botResponse.minValue, MS_MARGIN); + assertWithin("3: Bot response maximum", 300, environment.metrics().botResponse.maxValue, MS_MARGIN); + assertWithin("3: Bot response previous", 300, environment.metrics().botResponse.lastValue, MS_MARGIN); + sleepUnchecked(200); + }); + + environment.runGame(4); + + assertWithin("Final: Bot response average", 200, environment.metrics().botResponse.avgValue, MS_MARGIN); + assertWithin("Final: Bot response minimum", 100, environment.metrics().botResponse.minValue, MS_MARGIN); + assertWithin("Final: Bot response maximum", 300, environment.metrics().botResponse.maxValue, MS_MARGIN); + assertWithin("Final: Bot response previous", 200, environment.metrics().botResponse.lastValue, MS_MARGIN); + } + + @Test + public void async_MeasurePerformance_BwapiResponse() { + + } + + @Test + public void async_MeasurePerformance_BotIdle() { + + } + + } From af4c98623ca82a26edbfea092c65d2ad0ce1e0d9 Mon Sep 17 00:00:00 2001 From: dgant Date: Sat, 15 Aug 2020 16:32:56 -0400 Subject: [PATCH 26/56] Fixing performance metrics. Fixed framebuffer thinking it's full one frame too soon. --- .../java/bwapi/BWClientConfiguration.java | 18 +++--- src/main/java/bwapi/BotWrapper.java | 25 +++++--- src/main/java/bwapi/FrameBuffer.java | 10 +++- src/main/java/bwapi/PerformanceMetric.java | 2 +- src/main/java/bwapi/PerformanceMetrics.java | 16 ++++-- .../bwapi/SynchronizationEnvironment.java | 5 +- src/test/java/bwapi/SynchronizationTest.java | 57 ++++++++++++++----- 7 files changed, 92 insertions(+), 41 deletions(-) diff --git a/src/main/java/bwapi/BWClientConfiguration.java b/src/main/java/bwapi/BWClientConfiguration.java index 90323046..c67d0149 100644 --- a/src/main/java/bwapi/BWClientConfiguration.java +++ b/src/main/java/bwapi/BWClientConfiguration.java @@ -25,6 +25,14 @@ public class BWClientConfiguration { */ public boolean unlimitedFrameZero = true; + /** + * The maximum amount of time the bot is supposed to spend on a single frame. + * 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. + * In synchronous mode, JBWAPI is not empowered to prevent the bot to exceed this amount, but will record overruns in performance metrics. + * Real-time human play typically uses the "fastest" game speed, which has 42.86ms (42,860ns) between frames. + */ + public int maxFrameDurationMs = 40; + /** * Runs the bot in asynchronous mode. Asynchronous mode helps attempt to ensure that the bot adheres to real-time performance constraints. * @@ -38,12 +46,6 @@ public class BWClientConfiguration { */ public boolean async = false; - /** - * If JBWAPI detects that this much time (in nanoseconds) has passed since a bot's event handlers began, returns control back to BWAPI. - * Real-time human play typically uses the "fastest" game speed, which has 42.86ms (42,860ns) between frames. - */ - public int asyncFrameDurationMs = 40; - /** * The maximum number of frames to buffer while waiting on a bot. * Each frame buffered adds about 33 megabytes to JBWAPI's memory footprint. @@ -59,8 +61,8 @@ public class BWClientConfiguration { * Checks that the configuration is in a valid state. Throws an IllegalArgumentException if it isn't. */ public void validate() { - if (async && asyncFrameDurationMs < 0) { - throw new IllegalArgumentException("asyncFrameDurationMs needs to be a non-negative number (it's how long JBWAPI waits for a bot response before returning control to BWAPI)."); + if (async && maxFrameDurationMs < 0) { + 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)."); } if (async && asyncFrameBufferSize < 1) { throw new IllegalArgumentException("asyncFrameBufferSize needs to be a positive number (There needs to be at least one frame buffer)."); diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java index d93eff16..943b699f 100644 --- a/src/main/java/bwapi/BotWrapper.java +++ b/src/main/java/bwapi/BotWrapper.java @@ -46,7 +46,7 @@ class BotWrapper { BotWrapper(BWClientConfiguration configuration, BWEventListener eventListener) { this.configuration = configuration; this.eventListener = eventListener; - frameBuffer = configuration.async ? new FrameBuffer(configuration.asyncFrameBufferSize) : null; + frameBuffer = configuration.async ? new FrameBuffer(configuration) : null; } /** @@ -79,7 +79,7 @@ void onFrame() { if (configuration.async) { configuration.log("Main: onFrame asynchronous start"); long startNanos = System.nanoTime(); - long endNanos = startNanos + configuration.asyncFrameDurationMs * 1000000; + long endNanos = startNanos + configuration.maxFrameDurationMs * 1000000; if (botThread == null) { configuration.log("Main: Starting bot thread"); botThread = createBotThread(); @@ -98,7 +98,6 @@ void onFrame() { frameBuffer.lockSize.lock(); try { while (!frameBuffer.empty()) { - configuration.log("Main: Waiting for empty frame buffer"); // Make bot exceptions fall through to the main thread. Throwable lastThrow = getLastBotThrow(); @@ -108,12 +107,15 @@ void onFrame() { } if (configuration.unlimitedFrameZero && frame == 0) { - configuration.log("Main: Waiting indefinitely on frame " + frame); + configuration.log("Main: Waiting indefinitely on frame #" + frame); frameBuffer.conditionSize.await(); } else { long remainingNanos = endNanos - System.nanoTime(); - if (remainingNanos <= 0) break; - configuration.log("Main: Waiting " + remainingNanos / 1000000 + "ms for bot"); + if (remainingNanos <= 0) { + configuration.log("Main: Out of time in frame #" + frame); + break; + } + configuration.log("Main: Waiting " + remainingNanos / 1000000 + "ms for bot on frame #" + frame); frameBuffer.conditionSize.awaitNanos(remainingNanos); } } @@ -155,11 +157,11 @@ private Thread createBotThread() { configuration.log("Bot: Thread started"); while (!gameOver) { - configuration.log("Bot: Attempting to handle next frame"); + configuration.log("Bot: Ready for another frame"); frameBuffer.lockSize.lock(); try { while (frameBuffer.empty()) { - configuration.log("Bot: Waiting for next frame"); + configuration.log("Bot: Waiting for a frame"); performanceMetrics.botIdle.startTiming(); frameBuffer.conditionSize.awaitUninterruptibly(); } @@ -170,10 +172,11 @@ private Thread createBotThread() { configuration.log("Bot: Peeking next frame"); game.clientData().setBuffer(frameBuffer.peek()); - performanceMetrics.frameBufferSize.record(frameBuffer.framesBuffered() - 1); configuration.log("Bot: Handling frame #" + game.getFrameCount()); handleEvents(); + + configuration.log("Bot: Events done. Dequeuing frame #" + game.getFrameCount()); frameBuffer.dequeue(); } } catch (Throwable throwable) { @@ -200,6 +203,10 @@ private void handleEvents() { gameOver = gameOver || gameData.getEvents(i).getType() == EventType.MatchEnd; } + if (configuration.async) { + performanceMetrics.framesBehind.record(frameBuffer.framesBuffered() - 1); + } + performanceMetrics.botResponse.timeIf( ! gameOver && (gameData.getFrameCount() > 0 || ! configuration.unlimitedFrameZero), () -> { diff --git a/src/main/java/bwapi/FrameBuffer.java b/src/main/java/bwapi/FrameBuffer.java index 4287f0e3..c20c49f3 100644 --- a/src/main/java/bwapi/FrameBuffer.java +++ b/src/main/java/bwapi/FrameBuffer.java @@ -41,6 +41,7 @@ class FrameBuffer { private ByteBuffer liveData; private PerformanceMetrics performanceMetrics; + private BWClientConfiguration configuration; private int size; private int stepGame = 0; private int stepBot = 0; @@ -51,8 +52,9 @@ class FrameBuffer { final Lock lockSize = new ReentrantLock(); final Condition conditionSize = lockSize.newCondition(); - FrameBuffer(int size) { - this.size = size; + FrameBuffer(BWClientConfiguration configuration) { + this.size = configuration.asyncFrameBufferSize; + this.configuration = configuration; while(dataBuffer.size() < size) { dataBuffer.add(ByteBuffer.allocateDirect(BUFFER_SIZE)); } @@ -94,7 +96,7 @@ boolean empty() { boolean full() { lockSize.lock(); try { - return framesBuffered() >= size - 1; + return framesBuffered() >= size; } finally { lockSize.unlock(); } @@ -117,6 +119,7 @@ void enqueueFrame() { lockSize.lock(); try { while (full()) { + configuration.log("Main: Waiting for frame buffer capacity"); performanceMetrics.intentionallyBlocking.startTiming(); conditionSize.awaitUninterruptibly(); } @@ -130,6 +133,7 @@ void enqueueFrame() { lockSize.lock(); try { + performanceMetrics.frameBufferSize.record(framesBuffered()); ++stepGame; conditionSize.signalAll(); } finally { lockSize.unlock(); } diff --git a/src/main/java/bwapi/PerformanceMetric.java b/src/main/java/bwapi/PerformanceMetric.java index ed1d76bc..2fd4c00d 100644 --- a/src/main/java/bwapi/PerformanceMetric.java +++ b/src/main/java/bwapi/PerformanceMetric.java @@ -86,7 +86,7 @@ void record(double value) { avgValueExceeding = (avgValueExceeding * samplesExceeding + value) / (samplesExceeding + 1d); ++samplesExceeding; } - System.out.println(name + " #" + samples + " = " + value); // TODO: REMOVE + System.out.println("Metric: " + name + " (n=" + samples + "): " + value); // TODO: REMOVE } /** diff --git a/src/main/java/bwapi/PerformanceMetrics.java b/src/main/java/bwapi/PerformanceMetrics.java index f4266138..e441e837 100644 --- a/src/main/java/bwapi/PerformanceMetrics.java +++ b/src/main/java/bwapi/PerformanceMetrics.java @@ -25,11 +25,17 @@ public class PerformanceMetrics { public PerformanceMetric intentionallyBlocking; /** - * Number of frames backed up in the frame buffer, after releasing each frame. + * Number of frames backed up in the frame buffer, after enqueuing each frame (and not including the newest frame). * Applicable only in asynchronous mode. */ public PerformanceMetric frameBufferSize; + /** + * Number of frames behind real-time the bot is at the time it handles events. + * Applicable only in asynchronous mode. + */ + public PerformanceMetric framesBehind; + /** * Time spent applying bot commands to the live frame. */ @@ -55,13 +61,14 @@ public PerformanceMetrics(BWClientConfiguration configuration) { final int frameDurationBufferMs = 5; final int sideEffectsBufferMs = 1; final int realTimeFrameMs = 42; - totalFrameDuration = new PerformanceMetric("Total frame duration", configuration.asyncFrameDurationMs + frameDurationBufferMs); + totalFrameDuration = new PerformanceMetric("Total frame duration", configuration.maxFrameDurationMs + frameDurationBufferMs); copyingToBuffer = new PerformanceMetric("Time copying to buffer", 5); intentionallyBlocking = new PerformanceMetric("Intentionally blocking", 0); frameBufferSize = new PerformanceMetric("Frames buffered", 0); + framesBehind = new PerformanceMetric("Frames behind", 0); flushSideEffects = new PerformanceMetric("Flush side effects", sideEffectsBufferMs ); - botResponse = new PerformanceMetric("Bot Responses", configuration.asyncFrameDurationMs); - bwapiResponse = new PerformanceMetric("BWAPI Responses", realTimeFrameMs); + botResponse = new PerformanceMetric("Bot responses", configuration.maxFrameDurationMs); + bwapiResponse = new PerformanceMetric("BWAPI responses", realTimeFrameMs); botIdle = new PerformanceMetric("Bot idle", Long.MAX_VALUE); } @@ -72,6 +79,7 @@ public String toString() { + "\n" + copyingToBuffer.toString() + "\n" + intentionallyBlocking.toString() + "\n" + frameBufferSize.toString() + + "\n" + framesBehind.toString() + "\n" + flushSideEffects.toString() + "\n" + botResponse.toString() + "\n" + bwapiResponse.toString(); diff --git a/src/test/java/bwapi/SynchronizationEnvironment.java b/src/test/java/bwapi/SynchronizationEnvironment.java index 764345e9..875df110 100644 --- a/src/test/java/bwapi/SynchronizationEnvironment.java +++ b/src/test/java/bwapi/SynchronizationEnvironment.java @@ -52,8 +52,9 @@ public class SynchronizationEnvironment { }).when(listener).onEnd(anyBoolean()); doAnswer(answer -> { configuration.log("Test: onFrame()"); - if (onFrames.containsKey(liveFrame())) { - onFrames.get(liveFrame()).run(); + int botFrame = bwClient.getGame().getFrameCount(); + if (onFrames.containsKey(botFrame)) { + onFrames.get(botFrame).run(); } return null; }).when(listener).onFrame(); diff --git a/src/test/java/bwapi/SynchronizationTest.java b/src/test/java/bwapi/SynchronizationTest.java index 772e789a..2f3d30cc 100644 --- a/src/test/java/bwapi/SynchronizationTest.java +++ b/src/test/java/bwapi/SynchronizationTest.java @@ -39,7 +39,7 @@ private void assertWithin(String message, double expected, double actual, double } @Test - public void sync_IfException_ThrowException() throws InterruptedException { + public void sync_IfException_ThrowException() { SynchronizationEnvironment environment = new SynchronizationEnvironment(); environment.configuration.async = false; environment.onFrame(0, () -> { throw new RuntimeException("Simulated bot exception"); }); @@ -47,7 +47,7 @@ public void sync_IfException_ThrowException() throws InterruptedException { } @Test - public void async_IfException_ThrowException() throws InterruptedException { + public void async_IfException_ThrowException() { // An exception in the bot thread must be re-thrown by the main thread. SynchronizationEnvironment environment = new SynchronizationEnvironment(); environment.configuration.async = true; @@ -57,10 +57,10 @@ public void async_IfException_ThrowException() throws InterruptedException { } @Test - public void sync_IfDelay_ThenNoBuffer() throws InterruptedException { + public void sync_IfDelay_ThenNoBuffer() { SynchronizationEnvironment environment = new SynchronizationEnvironment(); environment.configuration.async = false; - environment.configuration.asyncFrameDurationMs = 1; + environment.configuration.maxFrameDurationMs = 1; environment.configuration.asyncFrameBufferSize = 3; IntStream.range(0, 5).forEach(frame -> { @@ -76,10 +76,10 @@ public void sync_IfDelay_ThenNoBuffer() throws InterruptedException { } @Test - public void async_IfBotDelay_ThenClientBuffers() throws InterruptedException { + public void async_IfBotDelay_ThenClientBuffers() { SynchronizationEnvironment environment = new SynchronizationEnvironment(); environment.configuration.async = true; - environment.configuration.asyncFrameDurationMs = 10; + environment.configuration.maxFrameDurationMs = 10; environment.configuration.asyncFrameBufferSize = 4; environment.onFrame(1, () -> { @@ -99,10 +99,10 @@ public void async_IfBotDelay_ThenClientBuffers() throws InterruptedException { } @Test - public void async_IfBotDelay_ThenClientStalls() throws InterruptedException { + public void async_IfBotDelay_ThenClientStalls() { SynchronizationEnvironment environment = new SynchronizationEnvironment(); environment.configuration.async = true; - environment.configuration.asyncFrameDurationMs = 50; + environment.configuration.maxFrameDurationMs = 50; environment.configuration.asyncFrameBufferSize = 5; environment.onFrame(1, () -> { @@ -124,11 +124,11 @@ public void async_IfBotDelay_ThenClientStalls() throws InterruptedException { } @Test - public void async_IfFrameZeroWaitsEnabled_ThenAllowInfiniteTime() throws InterruptedException { + public void async_IfFrameZeroWaitsEnabled_ThenAllowInfiniteTime() { SynchronizationEnvironment environment = new SynchronizationEnvironment(); environment.configuration.async = true; environment.configuration.unlimitedFrameZero = true; - environment.configuration.asyncFrameDurationMs = 5; + environment.configuration.maxFrameDurationMs = 5; environment.configuration.asyncFrameBufferSize = 2; environment.onFrame(0, () -> { @@ -142,11 +142,11 @@ public void async_IfFrameZeroWaitsEnabled_ThenAllowInfiniteTime() throws Interru } @Test - public void async_IfFrameZeroWaitsDisabled_ThenClientBuffers() throws InterruptedException { + public void async_IfFrameZeroWaitsDisabled_ThenClientBuffers() { SynchronizationEnvironment environment = new SynchronizationEnvironment(); environment.configuration.async = true; environment.configuration.unlimitedFrameZero = false; - environment.configuration.asyncFrameDurationMs = 5; + environment.configuration.maxFrameDurationMs = 5; environment.configuration.asyncFrameBufferSize = 2; environment.onFrame(0, () -> { @@ -175,8 +175,37 @@ public void async_MeasurePerformance_IntentionallyBlocking() { } @Test - public void async_MeasurePerformance_FrameBufferSize() { + public void async_MeasurePerformance_FrameBufferSizeAndFramesBehind() { + SynchronizationEnvironment environment = new SynchronizationEnvironment(); + environment.configuration.async = true; + environment.configuration.unlimitedFrameZero = true; + environment.configuration.asyncFrameBufferSize = 3; + environment.configuration.maxFrameDurationMs = 20; + environment.configuration.logVerbosely = true; + + environment.onFrame(5, () -> { + assertWithin("5: Frame buffer average", 0, environment.metrics().frameBufferSize.avgValue, 0.1); + assertWithin("5: Frame buffer minimum", 0, environment.metrics().frameBufferSize.minValue, 0.1); + assertWithin("5: Frame buffer maximum", 0, environment.metrics().frameBufferSize.maxValue, 0.1); + assertWithin("5: Frame buffer previous", 0, environment.metrics().frameBufferSize.lastValue, 0.1); + assertWithin("5: Frames behind average", 0, environment.metrics().framesBehind.avgValue, 0.1); + assertWithin("5: Frames behind minimum", 0, environment.metrics().framesBehind.minValue, 0.1); + assertWithin("5: Frames behind maximum", 0, environment.metrics().framesBehind.maxValue, 0.1); + assertWithin("5: Frames behind previous", 0, environment.metrics().framesBehind.lastValue, 0.1); + sleepUnchecked(200); + }); + environment.onFrame(6, () -> { + assertWithin("6: Frame buffer average", 1 / 6.0 + 2 / 7.0, environment.metrics().frameBufferSize.avgValue, 0.1); + assertWithin("6: Frame buffer minimum", 0, environment.metrics().frameBufferSize.minValue, 0.1); + assertWithin("6: Frame buffer maximum", 2, environment.metrics().frameBufferSize.maxValue, 0.1); + assertWithin("6: Frame buffer previous", 2, environment.metrics().frameBufferSize.lastValue, 0.1); + assertWithin("6: Frames behind average", 1 / 6.0, environment.metrics().framesBehind.avgValue, 0.1); + assertWithin("6: Frames behind minimum", 0, environment.metrics().framesBehind.minValue, 0.1); + assertWithin("6: Frames behind maximum", 1, environment.metrics().framesBehind.maxValue, 0.1); + assertWithin("6: Frames behind previous", 1, environment.metrics().framesBehind.lastValue, 0.1); + }); + environment.runGame(8); } @Test @@ -191,7 +220,7 @@ public void async_MeasurePerformance_FlushSideEffects() { private final static long MS_MARGIN = 10; @Test - public void async_MeasurePerformance_BotResponse() { + public void MeasurePerformance_BotResponse() { SynchronizationEnvironment environment = new SynchronizationEnvironment(); // Frame zero appears to take an extra 60ms, so let's disable timing for it From 0e35168251eecf462d00f991f3e90f96f23c77b0 Mon Sep 17 00:00:00 2001 From: dgant Date: Sun, 16 Aug 2020 01:41:17 -0400 Subject: [PATCH 27/56] Finished asynchronous unit tests; fixed miscellaneous performance measurements --- src/main/java/bwapi/BWClient.java | 4 +- src/main/java/bwapi/BotWrapper.java | 24 ++--- src/main/java/bwapi/PerformanceMetrics.java | 12 ++- .../bwapi/SynchronizationEnvironment.java | 13 ++- src/test/java/bwapi/SynchronizationTest.java | 101 ++++++++++++------ 5 files changed, 102 insertions(+), 52 deletions(-) diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index 091c49e3..f063d5c6 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -93,11 +93,11 @@ public void startGame(BWClientConfiguration configuration) { } botWrapper.onFrame(); performanceMetrics.flushSideEffects.time(() -> getGame().sideEffects.flushTo(liveGameData)); - client.update(); + performanceMetrics.totalFrameDuration.stopTiming(); + performanceMetrics.bwapiResponse.time(client::update); if (!client.isConnected()) { client.reconnect(); } - performanceMetrics.totalFrameDuration.stopTiming(); } botWrapper.endGame(); } while (configuration.autoContinue); diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java index 943b699f..c2536036 100644 --- a/src/main/java/bwapi/BotWrapper.java +++ b/src/main/java/bwapi/BotWrapper.java @@ -90,11 +90,10 @@ void onFrame() { Add a frame to buffer If buffer is full, it will wait until it has capacity Wait for empty buffer OR termination condition - */ + */ int frame = liveClientData.gameData().getFrameCount(); configuration.log("Main: Enqueuing frame #" + frame); frameBuffer.enqueueFrame(); - performanceMetrics.bwapiResponse.startTiming(); frameBuffer.lockSize.lock(); try { while (!frameBuffer.empty()) { @@ -122,7 +121,6 @@ void onFrame() { } catch(InterruptedException ignored) { } finally { frameBuffer.lockSize.unlock(); - performanceMetrics.bwapiResponse.stopTiming(); configuration.log("Main: onFrame asynchronous end"); } } else { @@ -158,17 +156,17 @@ private Thread createBotThread() { while (!gameOver) { configuration.log("Bot: Ready for another frame"); - frameBuffer.lockSize.lock(); - try { - while (frameBuffer.empty()) { - configuration.log("Bot: Waiting for a frame"); - performanceMetrics.botIdle.startTiming(); - frameBuffer.conditionSize.awaitUninterruptibly(); + performanceMetrics.botIdle.time(() -> { + frameBuffer.lockSize.lock(); + try { + while (frameBuffer.empty()) { + configuration.log("Bot: Waiting for a frame"); + frameBuffer.conditionSize.awaitUninterruptibly(); + } + } finally { + frameBuffer.lockSize.unlock(); } - performanceMetrics.botIdle.stopTiming(); - } finally { - frameBuffer.lockSize.unlock(); - } + }); configuration.log("Bot: Peeking next frame"); game.clientData().setBuffer(frameBuffer.peek()); diff --git a/src/main/java/bwapi/PerformanceMetrics.java b/src/main/java/bwapi/PerformanceMetrics.java index e441e837..150e3664 100644 --- a/src/main/java/bwapi/PerformanceMetrics.java +++ b/src/main/java/bwapi/PerformanceMetrics.java @@ -57,12 +57,19 @@ public class PerformanceMetrics { */ public PerformanceMetric botIdle; + private BWClientConfiguration configuration; + public PerformanceMetrics(BWClientConfiguration configuration) { + this.configuration = configuration; + reset(); + } + + void reset() { final int frameDurationBufferMs = 5; final int sideEffectsBufferMs = 1; final int realTimeFrameMs = 42; totalFrameDuration = new PerformanceMetric("Total frame duration", configuration.maxFrameDurationMs + frameDurationBufferMs); - copyingToBuffer = new PerformanceMetric("Time copying to buffer", 5); + copyingToBuffer = new PerformanceMetric("Time copying to buffer", 15); intentionallyBlocking = new PerformanceMetric("Intentionally blocking", 0); frameBufferSize = new PerformanceMetric("Frames buffered", 0); framesBehind = new PerformanceMetric("Frames behind", 0); @@ -82,6 +89,7 @@ public String toString() { + "\n" + framesBehind.toString() + "\n" + flushSideEffects.toString() + "\n" + botResponse.toString() - + "\n" + bwapiResponse.toString(); + + "\n" + bwapiResponse.toString() + + "\n" + botIdle.toString(); } } diff --git a/src/test/java/bwapi/SynchronizationEnvironment.java b/src/test/java/bwapi/SynchronizationEnvironment.java index 875df110..476d6cce 100644 --- a/src/test/java/bwapi/SynchronizationEnvironment.java +++ b/src/test/java/bwapi/SynchronizationEnvironment.java @@ -4,22 +4,19 @@ import java.util.HashMap; import java.util.Map; -import static com.sun.javafx.fxml.expression.Expression.greaterThan; -import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import org.hamcrest.MatcherAssert.*; - public class SynchronizationEnvironment { BWClientConfiguration configuration; BWClient bwClient; private BWEventListener listener; private Client client; private int onEndFrame; + private long bwapiDelayMs; private Map onFrames; SynchronizationEnvironment() { @@ -29,6 +26,7 @@ public class SynchronizationEnvironment { bwClient = new BWClient(listener); bwClient.setClient(client); onEndFrame = -1; + bwapiDelayMs = 0; onFrames = new HashMap<>(); when(client.mapFile()).thenReturn(GameBuilder.binToBufferUnchecked(GameBuilder.DEFAULT_BUFFER_PATH)); @@ -72,6 +70,10 @@ void onFrame(Integer frame, Runnable runnable) { onFrames.put(frame, runnable); } + void setBwapiDelayMs(long milliseconds) { + bwapiDelayMs = milliseconds; + } + void runGame() { runGame(10); } @@ -99,7 +101,8 @@ private int liveFrame() { return client.clientData().gameData().getFrameCount(); } - private void clientUpdate() { + private void clientUpdate() throws InterruptedException{ + Thread.sleep(bwapiDelayMs); client.clientData().gameData().setFrameCount(liveFrame() + 1); configuration.log("Test: clientUpdate() to liveFrame #" + liveFrame()); if (liveFrame() == 0) { diff --git a/src/test/java/bwapi/SynchronizationTest.java b/src/test/java/bwapi/SynchronizationTest.java index 2f3d30cc..ab6349ba 100644 --- a/src/test/java/bwapi/SynchronizationTest.java +++ b/src/test/java/bwapi/SynchronizationTest.java @@ -10,7 +10,7 @@ public class SynchronizationTest { - private void sleepUnchecked(int milliseconds) { + private void sleepUnchecked(long milliseconds) { try { Thread.sleep(milliseconds); } catch(InterruptedException exception) { @@ -26,12 +26,6 @@ private boolean measureApproximateEquality(double expected, double actual, doubl return expected + margin >= actual && expected - margin <= actual; } - private void assertWithin(double expected, double actual, double margin) { - assertTrue( - describeApproximateExpectation(expected, actual, margin), - measureApproximateEquality(expected, actual, margin)); - } - private void assertWithin(String message, double expected, double actual, double margin) { assertTrue( message + ": " + describeApproximateExpectation(expected, actual, margin), @@ -83,10 +77,10 @@ public void async_IfBotDelay_ThenClientBuffers() { environment.configuration.asyncFrameBufferSize = 4; environment.onFrame(1, () -> { - sleepUnchecked(40); + sleepUnchecked(50); assertEquals("Bot should be observing an old frame", 1, environment.bwClient.getGame().getFrameCount()); - assertEquals("Client should be as far ahead as the frame buffer allows", 4, environment.liveGameData().getFrameCount()); - assertEquals("Bot should be behind the live game", 3, environment.bwClient.framesBehind()); + assertEquals("Client should be as far ahead as the frame buffer allows", 5, environment.liveGameData().getFrameCount()); + assertEquals("Bot should be behind the live game", 4, environment.bwClient.framesBehind()); }); environment.onFrame(6, () -> { // Maybe it should be possible to demand that these assertions pass a frame earlier? @@ -161,17 +155,36 @@ public void async_IfFrameZeroWaitsDisabled_ThenClientBuffers() { @Test public void async_MeasurePerformance_TotalFrameDuration() { - + final int frames = 10; + final int frameSleep = 20; + SynchronizationEnvironment environment = new SynchronizationEnvironment(); + environment.configuration.async = true; + environment.configuration.unlimitedFrameZero = true; + environment.configuration.maxFrameDurationMs = frameSleep + 20; + IntStream.range(0, frames).forEach(i -> environment.onFrame(i, () -> { + sleepUnchecked(frameSleep); + })); + environment.runGame(frames); + + // Assume copying accounts for almost all the frame time except what the bot uses + double meanCopy = environment.metrics().copyingToBuffer.avgValue; + assertWithin("Total frame duration: Average", environment.metrics().totalFrameDuration.avgValue, meanCopy + frameSleep, MS_MARGIN); } @Test public void async_MeasurePerformance_CopyingToBuffer() { - - } - - @Test - public void async_MeasurePerformance_IntentionallyBlocking() { - + // Somewhat lazy test; just verify that we're getting sane values + SynchronizationEnvironment environment = new SynchronizationEnvironment(); + environment.configuration.async = true; + environment.runGame(20); + final double minObserved = 2; + final double maxObserved = 15; + final double meanObserved = (minObserved + maxObserved) / 2; + final double rangeObserved = (maxObserved - minObserved) / 2; + assertWithin("Copy to buffer: minimum", environment.metrics().copyingToBuffer.minValue, meanObserved, rangeObserved); + assertWithin("Copy to buffer: maximum", environment.metrics().copyingToBuffer.maxValue, meanObserved, rangeObserved); + assertWithin("Copy to buffer: average", environment.metrics().copyingToBuffer.avgValue, meanObserved, rangeObserved); + assertWithin("Copy to buffer: previous", environment.metrics().copyingToBuffer.lastValue, meanObserved, rangeObserved); } @Test @@ -181,8 +194,7 @@ public void async_MeasurePerformance_FrameBufferSizeAndFramesBehind() { environment.configuration.unlimitedFrameZero = true; environment.configuration.asyncFrameBufferSize = 3; environment.configuration.maxFrameDurationMs = 20; - environment.configuration.logVerbosely = true; - + environment.onFrame(5, () -> { assertWithin("5: Frame buffer average", 0, environment.metrics().frameBufferSize.avgValue, 0.1); assertWithin("5: Frame buffer minimum", 0, environment.metrics().frameBufferSize.minValue, 0.1); @@ -208,13 +220,8 @@ public void async_MeasurePerformance_FrameBufferSizeAndFramesBehind() { environment.runGame(8); } - @Test - public void async_MeasurePerformance_FlushSideEffects() { - - } - /** - * Number of milliseconds of leeway to give in performance metrics. + * Number of milliseconds of leeway to give in potentially noisy performance metrics. * Increase if tests are flaky due to variance in execution speed. */ private final static long MS_MARGIN = 10; @@ -254,14 +261,48 @@ public void MeasurePerformance_BotResponse() { } @Test - public void async_MeasurePerformance_BwapiResponse() { - + public void MeasurePerformance_BwapiResponse() { + final long bwapiDelayMs = 50; + SynchronizationEnvironment environment = new SynchronizationEnvironment(); + environment.setBwapiDelayMs(bwapiDelayMs); + environment.runGame(); + System.out.println(environment.metrics()); + assertWithin("BWAPI Response: Average", environment.metrics().bwapiResponse.avgValue, bwapiDelayMs, MS_MARGIN); } @Test - public void async_MeasurePerformance_BotIdle() { - + public void MeasurePerformance_BotIdle() { + final long bwapiDelayMs = 10; + final int frames = 10; + SynchronizationEnvironment environment = new SynchronizationEnvironment(); + environment.configuration.async = true; + environment.configuration.asyncFrameBufferSize = 3; + environment.configuration.unlimitedFrameZero = true; + environment.setBwapiDelayMs(bwapiDelayMs); + environment.runGame(frames); + double expected = environment.metrics().copyingToBuffer.avgValue + bwapiDelayMs; + assertWithin("Bot Idle: Average", environment.metrics().botIdle.avgValue, expected, MS_MARGIN); } - + @Test + public void async_MeasurePerformance_IntentionallyBlocking() { + SynchronizationEnvironment environment = new SynchronizationEnvironment(); + environment.configuration.async = true; + environment.configuration.unlimitedFrameZero = true; + environment.configuration.asyncFrameBufferSize = 2; + environment.configuration.maxFrameDurationMs = 20; + final int frameDelayMs = 100; + environment.onFrame(1, () -> { + sleepUnchecked(100); + }); + environment.onFrame(2, () -> { + assertWithin( + "2: Intentionally blocking previous", + environment.metrics().intentionallyBlocking.lastValue, + frameDelayMs - environment.configuration.asyncFrameBufferSize * environment.configuration.maxFrameDurationMs, + MS_MARGIN); + sleepUnchecked(100); + }); + environment.runGame(3); + } } From 93a9791380df44b0699d3e39e956506e2a162b0a Mon Sep 17 00:00:00 2001 From: dgant Date: Sun, 16 Aug 2020 02:09:57 -0400 Subject: [PATCH 28/56] Cleanup --- src/main/java/bwapi/BWClient.java | 9 +++++-- src/main/java/bwapi/BotWrapper.java | 25 ------------------- src/main/java/bwapi/Bullet.java | 4 +-- src/main/java/bwapi/CommandTemp.java | 2 +- src/main/java/bwapi/FrameBuffer.java | 25 ------------------- src/main/java/bwapi/Game.java | 24 +++++++++--------- src/main/java/bwapi/PerformanceMetrics.java | 2 +- src/main/java/bwapi/Unit.java | 12 ++++----- src/test/java/bwapi/GameBuilder.java | 13 +++------- src/test/java/bwapi/GameStateDumper.java | 4 +-- .../bwapi/SynchronizationEnvironment.java | 5 +++- 11 files changed, 37 insertions(+), 88 deletions(-) diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index f063d5c6..27841f81 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -27,7 +27,6 @@ public Game getGame() { /** * @return JBWAPI performance metrics. */ - @SuppressWarnings("unused") public PerformanceMetrics getPerformanceMetrics() { return performanceMetrics; } @@ -36,11 +35,17 @@ public PerformanceMetrics getPerformanceMetrics() { * @return The number of frames between the one exposed to the bot and the most recent received by JBWAPI. * This tracks the size of the frame buffer except when the game is paused (which results in multiple frames arriving with the same count). */ - @SuppressWarnings("unused") public int framesBehind() { return botWrapper == null ? 0 : Math.max(0, client.clientData().gameData().getFrameCount() - getGame().getFrameCount()); } + /** + * For internal test use. + */ + Client getClient() { + return client; + } + /** * Start the game with default settings. */ diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java index c2536036..bc30094b 100644 --- a/src/main/java/bwapi/BotWrapper.java +++ b/src/main/java/bwapi/BotWrapper.java @@ -1,28 +1,3 @@ -/* -MIT License - -Copyright (c) 2018 Hannes Bredberg -Modified work Copyright (c) 2018 Jasper - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - package bwapi; import java.nio.ByteBuffer; diff --git a/src/main/java/bwapi/Bullet.java b/src/main/java/bwapi/Bullet.java index 460f8094..76612209 100644 --- a/src/main/java/bwapi/Bullet.java +++ b/src/main/java/bwapi/Bullet.java @@ -21,7 +21,7 @@ * represents a new Bullet. *

* If {@link Flag#CompleteMapInformation} is disabled, then a {@link Bullet} is accessible if and only if - * it is visible. Otherwise if {@link Flag#CompleteMapInformation} is timersEnabled, then all Bullets + * it is visible. Otherwise if {@link Flag#CompleteMapInformation} is enabled, then all Bullets * in the game are accessible. * * @see Game#getBullets @@ -54,7 +54,7 @@ public int getID() { * return value will be false regardless of the Bullet's true existence. This is because * absolutely no state information on invisible enemy bullets is made available to the AI. *

- * If {@link Flag#CompleteMapInformation} is timersEnabled, then this function is accurate for all + * If {@link Flag#CompleteMapInformation} is enabled, then this function is accurate for all * {@link Bullet} information. * * @return true if the bullet exists or is visible, false if the bullet was destroyed or has gone out of scope. diff --git a/src/main/java/bwapi/CommandTemp.java b/src/main/java/bwapi/CommandTemp.java index b2fa6fc2..11141319 100644 --- a/src/main/java/bwapi/CommandTemp.java +++ b/src/main/java/bwapi/CommandTemp.java @@ -2,7 +2,7 @@ /** * Latency Compensation: - * Only need to implement LatCom for current frame, the server updates the next frame already if latcom is timersEnabled. + * Only need to implement LatCom for current frame, the server updates the next frame already if latcom is enabled. * Use Caches for all internal state that might be affected by latcom, and add the (current) frame, to let Player & Unit * check if they need to use the cached/latcom version of the value or the from server (or a combination of both) *

diff --git a/src/main/java/bwapi/FrameBuffer.java b/src/main/java/bwapi/FrameBuffer.java index c20c49f3..232e2774 100644 --- a/src/main/java/bwapi/FrameBuffer.java +++ b/src/main/java/bwapi/FrameBuffer.java @@ -1,28 +1,3 @@ -/* -MIT License - -Copyright (c) 2018 Hannes Bredberg -Modified work Copyright (c) 2018 Jasper - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - package bwapi; import com.sun.jna.Platform; diff --git a/src/main/java/bwapi/Game.java b/src/main/java/bwapi/Game.java index 1562fb97..89eacdde 100644 --- a/src/main/java/bwapi/Game.java +++ b/src/main/java/bwapi/Game.java @@ -329,7 +329,7 @@ public List getPlayers() { /** * Retrieves the set of all accessible units. - * If {@link Flag#CompleteMapInformation} is timersEnabled, then the set also includes units that are not + * If {@link Flag#CompleteMapInformation} is enabled, then the set also includes units that are not * visible to the player. *

* Units that are inside refineries are not included in this set. @@ -624,13 +624,13 @@ public void pingMinimap(final Position p) { } /** - * Checks if the state of the given flag is timersEnabled or not. + * Checks if the state of the given flag is enabled or not. *

- * Flags may only be timersEnabled at the start of the match during the {@link BWEventListener#onStart} + * Flags may only be enabled at the start of the match during the {@link BWEventListener#onStart} * callback. * * @param flag The {@link Flag} entry describing the flag's effects on BWAPI. - * @return true if the given flag is timersEnabled, false if the flag is disabled. + * @return true if the given flag is enabled, false if the flag is disabled. * @see Flag */ public boolean isFlagEnabled(final Flag flag) { @@ -640,7 +640,7 @@ public boolean isFlagEnabled(final Flag flag) { /** * Enables the state of a given flag. *

- * Flags may only be timersEnabled at the start of the match during the {@link BWEventListener#onStart} + * Flags may only be enabled at the start of the match during the {@link BWEventListener#onStart} * callback. * * @param flag The {@link Flag} entry describing the flag's effects on BWAPI. @@ -1640,7 +1640,7 @@ public boolean issueCommand(final Collection units, final UnitCommand comm /** * Retrieves the set of units that are currently selected by the user outside of - * BWAPI. This function requires that{@link Flag#UserInput} be timersEnabled. + * BWAPI. This function requires that{@link Flag#UserInput} be enabled. * * @return A List containing the user's selected units. If {@link Flag#UserInput} is disabled, * then this set is always empty. @@ -2163,7 +2163,7 @@ public boolean isDebug() { /** * Checks the state of latency compensation. * - * @return true if latency compensation is timersEnabled, false if it is disabled. + * @return true if latency compensation is enabled, false if it is disabled. * @see #setLatCom */ public boolean isLatComEnabled() { @@ -2174,9 +2174,9 @@ public boolean isLatComEnabled() { * Changes the state of latency compensation. Latency compensation * modifies the state of BWAPI's representation of units to reflect the implications of * issuing a command immediately after the command was performed, instead of waiting - * consecutive frames for the results. Latency compensation is timersEnabled by default. + * consecutive frames for the results. Latency compensation is enabled by default. * - * @param isEnabled Set whether the latency compensation feature will be timersEnabled (true) or disabled (false). + * @param isEnabled Set whether the latency compensation feature will be enabled (true) or disabled (false). * @see #isLatComEnabled */ public void setLatCom(final boolean isEnabled) { @@ -2278,11 +2278,11 @@ public boolean setVision(Player player, boolean enabled) { } /** - * Checks if the GUI is timersEnabled. + * Checks if the GUI is enabled. *

* The GUI includes all drawing functions of BWAPI, as well as screen updates from Broodwar. * - * @return true if the GUI is timersEnabled, and everything is visible, false if the GUI is disabled and drawing + * @return true if the GUI is enabled, and everything is visible, false if the GUI is disabled and drawing * functions are rejected * @see #setGUI */ @@ -2341,7 +2341,7 @@ public boolean setMap(final String mapFileName) { /** * Sets the state of the fog of war when watching a replay. * - * @param reveal The state of the reveal all flag. If false, all fog of war will be timersEnabled. If true, + * @param reveal The state of the reveal all flag. If false, all fog of war will be enabled. If true, * then the fog of war will be revealed. It is true by default. */ public boolean setRevealAll(boolean reveal) { diff --git a/src/main/java/bwapi/PerformanceMetrics.java b/src/main/java/bwapi/PerformanceMetrics.java index 150e3664..4ec80276 100644 --- a/src/main/java/bwapi/PerformanceMetrics.java +++ b/src/main/java/bwapi/PerformanceMetrics.java @@ -1,7 +1,7 @@ package bwapi; /** - * When performance diagnostics are timersEnabled, collects various performance metrics. + * Collects various performance metrics. */ public class PerformanceMetrics { diff --git a/src/main/java/bwapi/Unit.java b/src/main/java/bwapi/Unit.java index c13d8c2a..6d491ae9 100644 --- a/src/main/java/bwapi/Unit.java +++ b/src/main/java/bwapi/Unit.java @@ -19,8 +19,8 @@ * becoming invalid). *

* Every Unit in the game is either accessible or inaccessible. To determine if an AI can access - * a particular unit, BWAPI checks to see if {@link Flag#CompleteMapInformation} is timersEnabled. So there - * are two cases to consider - either the flag is timersEnabled, or it is disabled: + * a particular unit, BWAPI checks to see if {@link Flag#CompleteMapInformation} is enabled. So there + * are two cases to consider - either the flag is enabled, or it is disabled: *

* If {@link Flag#CompleteMapInformation} is disabled, then a unit is accessible if and only if it is visible. *

@@ -31,7 +31,7 @@ * AI must watch for {@link BWEventListener#onUnitDestroy} messages from BWAPI, which is only called for visible units * which get destroyed. *

- * If {@link Flag#CompleteMapInformation} is timersEnabled, then all units that exist in the game are accessible, and + * If {@link Flag#CompleteMapInformation} is enabled, then all units that exist in the game are accessible, and * {@link Unit#exists} is accurate for all units. Similarly {@link BWEventListener#onUnitDestroy} messages are generated for all * units that get destroyed, not just visible ones. *

@@ -128,7 +128,7 @@ public boolean exists() { /** * Retrieves the unit identifier for this unit as seen in replay data. *

- * This is only available if {@link Flag#CompleteMapInformation} is timersEnabled. + * This is only available if {@link Flag#CompleteMapInformation} is enabled. * * @return An integer containing the replay unit identifier. * @see #getID @@ -1882,7 +1882,7 @@ public boolean isResearching() { /** * Checks if this unit has been selected in the user interface. This - * function is only available if the flag Flag#UserInput is timersEnabled. + * function is only available if the flag Flag#UserInput is enabled. * * @return true if this unit is currently selected, and false if this unit is not selected * @see Game#getSelectedUnits @@ -2046,7 +2046,7 @@ public boolean isVisible() { * @param player The player to check visibility for. If this parameter is omitted, then the BWAPI player obtained from {@link Game#self()} will be used. * @return true if this unit is visible to the specified player, and false if it is not. *

- * If the {@link Flag#CompleteMapInformation} flag is timersEnabled, existing units hidden by the + * If the {@link Flag#CompleteMapInformation} flag is enabled, existing units hidden by the * fog of war will be accessible, but isVisible will still return false. * @see #exists */ diff --git a/src/test/java/bwapi/GameBuilder.java b/src/test/java/bwapi/GameBuilder.java index 63886fa0..a9dd77ce 100644 --- a/src/test/java/bwapi/GameBuilder.java +++ b/src/test/java/bwapi/GameBuilder.java @@ -10,15 +10,16 @@ public class GameBuilder { + private final static String RESOURCES = "src/test/resources/"; public final static String DEFAULT_MAP_FILE = "(2)Benzene.scx"; - public final static String DEFAULT_BUFFER_PATH = "src/test/resources/" + DEFAULT_MAP_FILE + "_frame0_buffer.bin"; + public final static String DEFAULT_BUFFER_PATH = RESOURCES + DEFAULT_MAP_FILE + "_frame0_buffer.bin"; public static Game createGame() throws IOException { return createGame(DEFAULT_MAP_FILE); } public static Game createGame(String mapName) throws IOException { - final ByteBuffer buffer = binToBuffer(DEFAULT_BUFFER_PATH); + final ByteBuffer buffer = binToBuffer(RESOURCES + mapName + "_frame0_buffer.bin"); return createGame(new Client(buffer)); } @@ -29,14 +30,6 @@ public static Game createGame(Client client) { return game; } - public static Game createGameUnchecked() { - try { - return GameBuilder.createGame(); - } catch (IOException exception) { - throw new RuntimeException(exception); - } - } - public static ByteBuffer binToBuffer(String binLocation) throws IOException { final byte[] compressedBytes = Files.readAllBytes(Paths.get(binLocation)); final ByteArrayOutputStream out = new ByteArrayOutputStream(); diff --git a/src/test/java/bwapi/GameStateDumper.java b/src/test/java/bwapi/GameStateDumper.java index e8f5b9ae..84f7c7b2 100644 --- a/src/test/java/bwapi/GameStateDumper.java +++ b/src/test/java/bwapi/GameStateDumper.java @@ -36,9 +36,7 @@ public void onStart() { } private void dumpBuffer(String name) throws IOException { - // TODO - //ByteBuffer buf = game.getClient().clientData().buffer.getBuffer(); - ByteBuffer buf = null; + ByteBuffer buf = client.getClient().mapFile(); buf.rewind(); byte[] bytearr = new byte[buf.remaining()]; buf.get(bytearr); diff --git a/src/test/java/bwapi/SynchronizationEnvironment.java b/src/test/java/bwapi/SynchronizationEnvironment.java index 476d6cce..4385cdce 100644 --- a/src/test/java/bwapi/SynchronizationEnvironment.java +++ b/src/test/java/bwapi/SynchronizationEnvironment.java @@ -10,7 +10,10 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -public class SynchronizationEnvironment { +/** + * Mocks BWAPI and a bot listener, for synchronization tests. + */ +class SynchronizationEnvironment { BWClientConfiguration configuration; BWClient bwClient; private BWEventListener listener; From cb5a3fdcfbadf86b59d33377bf0e90082f2d4e51 Mon Sep 17 00:00:00 2001 From: dgant Date: Sun, 16 Aug 2020 02:31:56 -0400 Subject: [PATCH 29/56] Fixed unit tests --- src/main/java/bwapi/BWClient.java | 13 +++++++------ src/main/java/bwapi/PerformanceMetric.java | 1 - src/test/java/bwapi/GameBuilder.java | 6 +----- src/test/java/bwapi/GameTest.java | 11 +++++++---- 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index 27841f81..38b4ee13 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -93,14 +93,15 @@ public void startGame(BWClientConfiguration configuration) { } } while (liveGameData.isInGame()) { - if (liveGameData.getFrameCount() > 0 || ! configuration.unlimitedFrameZero) { - performanceMetrics.totalFrameDuration.startTiming(); - } - botWrapper.onFrame(); - performanceMetrics.flushSideEffects.time(() -> getGame().sideEffects.flushTo(liveGameData)); - performanceMetrics.totalFrameDuration.stopTiming(); + performanceMetrics.totalFrameDuration.timeIf( + liveGameData.getFrameCount() > 0 || ! configuration.unlimitedFrameZero, + () -> { + botWrapper.onFrame(); + performanceMetrics.flushSideEffects.time(() -> getGame().sideEffects.flushTo(liveGameData)); + }); performanceMetrics.bwapiResponse.time(client::update); if (!client.isConnected()) { + System.out.println("Reconnecting..."); client.reconnect(); } } diff --git a/src/main/java/bwapi/PerformanceMetric.java b/src/main/java/bwapi/PerformanceMetric.java index 2fd4c00d..01956b32 100644 --- a/src/main/java/bwapi/PerformanceMetric.java +++ b/src/main/java/bwapi/PerformanceMetric.java @@ -86,7 +86,6 @@ void record(double value) { avgValueExceeding = (avgValueExceeding * samplesExceeding + value) / (samplesExceeding + 1d); ++samplesExceeding; } - System.out.println("Metric: " + name + " (n=" + samples + "): " + value); // TODO: REMOVE } /** diff --git a/src/test/java/bwapi/GameBuilder.java b/src/test/java/bwapi/GameBuilder.java index a9dd77ce..f03d9e73 100644 --- a/src/test/java/bwapi/GameBuilder.java +++ b/src/test/java/bwapi/GameBuilder.java @@ -20,12 +20,8 @@ public static Game createGame() throws IOException { public static Game createGame(String mapName) throws IOException { final ByteBuffer buffer = binToBuffer(RESOURCES + mapName + "_frame0_buffer.bin"); - return createGame(new Client(buffer)); - } - - public static Game createGame(Client client) { final Game game = new Game(); - game.clientData().setBuffer(client.mapFile()); + game.clientData().setBuffer(buffer); game.init(); return game; } diff --git a/src/test/java/bwapi/GameTest.java b/src/test/java/bwapi/GameTest.java index 568a4b92..844e6156 100644 --- a/src/test/java/bwapi/GameTest.java +++ b/src/test/java/bwapi/GameTest.java @@ -89,13 +89,16 @@ public void shouldNotFindNonOverlappingUnits( @Test public void ifReplaySelfAndEnemyShouldBeNull() throws IOException { - ByteBuffer buffer = GameBuilder.binToBuffer("src/test/resources/" + "(2)Benzene.scx" + "_frame0_buffer.bin"); + ByteBuffer buffer = GameBuilder.binToBuffer(GameBuilder.DEFAULT_BUFFER_PATH); - Client client = new Client(buffer); // modify the buffer to fake a replay - client.clientData().gameData().setIsReplay(true); + ClientData clientData = new ClientData(); + clientData.setBuffer(buffer); + clientData.gameData().setIsReplay(true); - Game game = GameBuilder.createGame(client); + Game game = new Game(); + game.clientData().setBuffer(buffer); + game.init(); assertThat(game.isReplay()); assertNull(game.self()); From 45201088f37f5238f582a3b1f151c83e1d777e48 Mon Sep 17 00:00:00 2001 From: dgant Date: Sun, 16 Aug 2020 02:59:15 -0400 Subject: [PATCH 30/56] Minor tweaks --- src/main/java/bwapi/ClientData.java | 2 +- src/main/java/bwapi/FrameBuffer.java | 1 - src/main/java/bwapi/Unit.java | 2 +- src/test/java/DumpToClient.java | 2 +- src/test/java/bwapi/ClientDataBenchmark.java | 6 ++---- 5 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/main/java/bwapi/ClientData.java b/src/main/java/bwapi/ClientData.java index f3723572..2dca5ac7 100644 --- a/src/main/java/bwapi/ClientData.java +++ b/src/main/java/bwapi/ClientData.java @@ -1,6 +1,6 @@ package bwapi; import java.nio.ByteBuffer; -class ClientData { +final class ClientData { private WrappedBuffer buffer; private GameData gameData; ClientData() { diff --git a/src/main/java/bwapi/FrameBuffer.java b/src/main/java/bwapi/FrameBuffer.java index 232e2774..a8c0f581 100644 --- a/src/main/java/bwapi/FrameBuffer.java +++ b/src/main/java/bwapi/FrameBuffer.java @@ -22,7 +22,6 @@ class FrameBuffer { private int stepBot = 0; private ArrayList dataBuffer = new ArrayList<>(); - // Synchronization locks private final Lock lockWrite = new ReentrantLock(); final Lock lockSize = new ReentrantLock(); final Condition conditionSize = lockSize.newCondition(); diff --git a/src/main/java/bwapi/Unit.java b/src/main/java/bwapi/Unit.java index 6d491ae9..0ac4cb1b 100644 --- a/src/main/java/bwapi/Unit.java +++ b/src/main/java/bwapi/Unit.java @@ -1019,7 +1019,7 @@ public Order getOrder() { /** * Retrieves the secondary Order that the unit is assigned. Secondary - * orders are step in the background as a sub-order. An example would be {@link Order#TrainFighter}, + * orders are run in the background as a sub-order. An example would be {@link Order#TrainFighter}, * because a @Carrier can move and train fighters at the same time. * * @return The secondary {@link Order} that the unit is executing. diff --git a/src/test/java/DumpToClient.java b/src/test/java/DumpToClient.java index 191f023f..90d72377 100644 --- a/src/test/java/DumpToClient.java +++ b/src/test/java/DumpToClient.java @@ -91,7 +91,7 @@ public static void main(String[] args) throws IOException { try (PrintWriter out = new PrintWriter(sw)) { out.println("package bwapi;"); out.println("import java.nio.ByteBuffer;"); - out.println("class ClientData {"); + out.println("final class ClientData {"); out.println(" private WrappedBuffer buffer;"); out.println(" private GameData gameData;"); out.println(" ClientData() {"); diff --git a/src/test/java/bwapi/ClientDataBenchmark.java b/src/test/java/bwapi/ClientDataBenchmark.java index fd814d04..ec19d2dd 100644 --- a/src/test/java/bwapi/ClientDataBenchmark.java +++ b/src/test/java/bwapi/ClientDataBenchmark.java @@ -19,9 +19,8 @@ public static class EmptyState { @Setup(Level.Invocation) public void setup() { - client = new Client(ByteBuffer.allocateDirect(ClientData.GameData.SIZE)); game = new Game(); - game.clientData().setBuffer(client.mapFile()); + game.clientData().setBuffer(ByteBuffer.allocateDirect(ClientData.GameData.SIZE)); strings = buildStrings(); } @@ -35,10 +34,9 @@ public static class FilledWithStrings { @Setup(Level.Invocation) public void setup() { - client = new Client(ByteBuffer.allocateDirect(ClientData.GameData.SIZE)); data = client.clientData().gameData(); game = new Game(); - game.clientData().setBuffer(client.mapFile()); + game.clientData().setBuffer(ByteBuffer.allocateDirect(ClientData.GameData.SIZE)); String[] strings = buildStrings(); for (String s : strings) { GameDataUtils.addString(client.clientData().gameData(), s); From 879f6cf7b2f35c251d4221275cfa8367536dd29c Mon Sep 17 00:00:00 2001 From: dgant Date: Sat, 19 Sep 2020 20:47:42 -0400 Subject: [PATCH 31/56] Clarified thresholds for performance metrics --- src/main/java/bwapi/PerformanceMetric.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/bwapi/PerformanceMetric.java b/src/main/java/bwapi/PerformanceMetric.java index 01956b32..f0f9278f 100644 --- a/src/main/java/bwapi/PerformanceMetric.java +++ b/src/main/java/bwapi/PerformanceMetric.java @@ -110,7 +110,9 @@ public String toString() { + (samplesExceeding > 0 ? ". " + samplesExceeding - + " violations averaging " + + " values over " + + maxAllowed + + " averaging " + formatter.format(avgValueExceeding) : "") : "No samples") From 8bb4f8054c7c618c0a96ad692776a0f434efead4 Mon Sep 17 00:00:00 2001 From: dgant Date: Mon, 21 Sep 2020 16:11:27 -0400 Subject: [PATCH 32/56] Implemented unsafe asynchronous mode, allowing the bot to read directly from live game data while the frame buffer is being populated. There's some additional cruft lying around and there's no guarantee that this will actually work in practice, but the test case is passing. --- src/main/java/bwapi/BWClient.java | 17 ++- .../java/bwapi/BWClientConfiguration.java | 15 ++- src/main/java/bwapi/BotWrapper.java | 110 +++++++++++++----- src/main/java/bwapi/FrameBuffer.java | 25 ++-- src/main/java/bwapi/PerformanceMetrics.java | 51 ++++++-- .../bwapi/SynchronizationEnvironment.java | 2 +- src/test/java/bwapi/SynchronizationTest.java | 44 ++++--- 7 files changed, 201 insertions(+), 63 deletions(-) diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index 38b4ee13..29524e9d 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -93,12 +93,25 @@ public void startGame(BWClientConfiguration configuration) { } } while (liveGameData.isInGame()) { + boolean timeFrame = liveGameData.getFrameCount() > 0 || ! configuration.unlimitedFrameZero; + performanceMetrics.frameDuration5.timeIf(timeFrame, () -> + performanceMetrics.frameDuration10.timeIf(timeFrame, () -> + performanceMetrics.frameDuration15.timeIf(timeFrame, () -> + performanceMetrics.frameDuration20.timeIf(timeFrame, () -> + performanceMetrics.frameDuration25.timeIf(timeFrame, () -> + performanceMetrics.frameDuration30.timeIf(timeFrame, () -> + performanceMetrics.frameDuration35.timeIf(timeFrame, () -> + performanceMetrics.frameDuration40.timeIf(timeFrame, () -> + performanceMetrics.frameDuration45.timeIf(timeFrame, () -> + performanceMetrics.frameDuration50.timeIf(timeFrame, () -> + performanceMetrics.frameDuration55.timeIf(timeFrame, () -> performanceMetrics.totalFrameDuration.timeIf( - liveGameData.getFrameCount() > 0 || ! configuration.unlimitedFrameZero, + timeFrame, () -> { botWrapper.onFrame(); performanceMetrics.flushSideEffects.time(() -> getGame().sideEffects.flushTo(liveGameData)); - }); + }) + ))))))))))); performanceMetrics.bwapiResponse.time(client::update); if (!client.isConnected()) { System.out.println("Reconnecting..."); diff --git a/src/main/java/bwapi/BWClientConfiguration.java b/src/main/java/bwapi/BWClientConfiguration.java index c67d0149..19092197 100644 --- a/src/main/java/bwapi/BWClientConfiguration.java +++ b/src/main/java/bwapi/BWClientConfiguration.java @@ -50,7 +50,16 @@ public class BWClientConfiguration { * The maximum number of frames to buffer while waiting on a bot. * Each frame buffered adds about 33 megabytes to JBWAPI's memory footprint. */ - public int asyncFrameBufferSize = 10; + public int asyncFrameBufferCapacity = 10; + + /** + * Enables thread-unsafe async mode. + * In this mode, the bot is allowed to read directly from shared memory until shared memory has been copied into the frame buffer, + * at wihch point the bot switches to using the frame buffer. + * This should enhance performance by allowing the bot to act while the frame is copied, but poses unidentified risk due to + * the non-thread-safe switc from shared memory reads to frame buffer reads. + */ + public boolean asyncUnsafe = false; /** * Toggles verbose logging, particularly of synchronization steps. @@ -64,8 +73,8 @@ public void validate() { if (async && maxFrameDurationMs < 0) { 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)."); } - if (async && asyncFrameBufferSize < 1) { - throw new IllegalArgumentException("asyncFrameBufferSize needs to be a positive number (There needs to be at least one frame buffer)."); + if (async && asyncFrameBufferCapacity < 1) { + throw new IllegalArgumentException("asyncFrameBufferCapacity needs to be a positive number (There needs to be at least one frame buffer)."); } } diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java index bc30094b..15c11afe 100644 --- a/src/main/java/bwapi/BotWrapper.java +++ b/src/main/java/bwapi/BotWrapper.java @@ -11,12 +11,15 @@ class BotWrapper { private final BWClientConfiguration configuration; private final BWEventListener eventListener; private final FrameBuffer frameBuffer; - private Game game; + private ByteBuffer liveData; + private Game botGame; private Thread botThread; private boolean gameOver; private PerformanceMetrics performanceMetrics; private Throwable lastBotThrow; private ReentrantLock lastBotThrowLock = new ReentrantLock(); + private ReentrantLock unsafeReadReadyLock = new ReentrantLock(); + private boolean unsafeReadReady = false; BotWrapper(BWClientConfiguration configuration, BWEventListener eventListener) { this.configuration = configuration; @@ -25,16 +28,17 @@ class BotWrapper { } /** - * Resets the BotWrapper for a new game. + * Resets the BotWrapper for a new botGame. */ void startNewGame(ByteBuffer liveData, PerformanceMetrics performanceMetrics) { if (configuration.async) { frameBuffer.initialize(liveData, performanceMetrics); } this.performanceMetrics = performanceMetrics; - game = new Game(); - game.clientData().setBuffer(liveData); + botGame = new Game(); + botGame.clientData().setBuffer(liveData); liveClientData.setBuffer(liveData); + this.liveData = liveData; botThread = null; gameOver = false; } @@ -44,7 +48,25 @@ void startNewGame(ByteBuffer liveData, PerformanceMetrics performanceMetrics) { * In asynchronous mode this Game object may point at a copy of a previous frame. */ Game getGame() { - return game; + return botGame; + } + + private boolean isUnsafeReadReady() { + unsafeReadReadyLock.lock(); + try { return unsafeReadReady; } + finally { unsafeReadReadyLock.unlock(); } + } + + private void setUnsafeReadReady(boolean value) { + unsafeReadReadyLock.lock(); + try { unsafeReadReady = value; } + finally { unsafeReadReadyLock.unlock(); } + frameBuffer.lockSize.lock(); + try { + frameBuffer.conditionSize.signalAll(); + } finally { + frameBuffer.lockSize.unlock(); + } } /** @@ -61,17 +83,44 @@ void onFrame() { botThread.setName("JBWAPI Bot"); botThread.start(); } - /* - Add a frame to buffer - If buffer is full, it will wait until it has capacity - Wait for empty buffer OR termination condition - */ + + // Unsafe mode: + // If the frame buffer is empty (meaning the bot must be idle) + // allow the bot to read directly from shared memory while we copy it over + if (configuration.asyncUnsafe) { + frameBuffer.lockSize.lock(); + try { + if (frameBuffer.empty()) { + configuration.log("Main: Putting bot on live data"); + botGame.clientData().setBuffer(liveData); + setUnsafeReadReady(true); + } else { + setUnsafeReadReady(false); + } + } finally { + frameBuffer.lockSize.unlock(); + } + } + + // Add a frame to buffer + // If buffer is full, will wait until it has capacity. + // Then wait for the buffer to empty or to run out of time in the frame. int frame = liveClientData.gameData().getFrameCount(); configuration.log("Main: Enqueuing frame #" + frame); frameBuffer.enqueueFrame(); + configuration.log("Main: Enqueued frame #" + frame); frameBuffer.lockSize.lock(); try { while (!frameBuffer.empty()) { + // Unsafe mode: Move the bot off of live data onto the frame buffer + // This is the unsafe step! + // We don't synchronize on calls which access the buffer + // (to avoid tens of thousands of synchronized calls per frame) + // so there's no guarantee of safety here. + if (configuration.asyncUnsafe && frameBuffer.size() == 1) { + configuration.log("Main: Weaning bot off live data"); + botGame.clientData().setBuffer(frameBuffer.peek()); + } // Make bot exceptions fall through to the main thread. Throwable lastThrow = getLastBotThrow(); @@ -130,26 +179,35 @@ private Thread createBotThread() { configuration.log("Bot: Thread started"); while (!gameOver) { + boolean doUnsafeRead = false; configuration.log("Bot: Ready for another frame"); - performanceMetrics.botIdle.time(() -> { - frameBuffer.lockSize.lock(); - try { - while (frameBuffer.empty()) { - configuration.log("Bot: Waiting for a frame"); - frameBuffer.conditionSize.awaitUninterruptibly(); - } - } finally { - frameBuffer.lockSize.unlock(); + performanceMetrics.botIdle.startTiming(); + frameBuffer.lockSize.lock(); + try { + doUnsafeRead = isUnsafeReadReady(); + while ( ! doUnsafeRead && frameBuffer.empty()) { + configuration.log("Bot: Waiting for a frame"); + frameBuffer.conditionSize.awaitUninterruptibly(); + doUnsafeRead = isUnsafeReadReady(); } - }); + } finally { + frameBuffer.lockSize.unlock(); + } + performanceMetrics.botIdle.stopTiming(); - configuration.log("Bot: Peeking next frame"); - game.clientData().setBuffer(frameBuffer.peek()); + if (doUnsafeRead) { + configuration.log("Bot: Reading live frame"); + setUnsafeReadReady(false); + // TODO: Maybe we should point it at live data from here? + } else { + configuration.log("Bot: Peeking next frame from buffer"); + botGame.clientData().setBuffer(frameBuffer.peek()); + } - configuration.log("Bot: Handling frame #" + game.getFrameCount()); + configuration.log("Bot: Handling events on frame #" + botGame.getFrameCount()); handleEvents(); - configuration.log("Bot: Events done. Dequeuing frame #" + game.getFrameCount()); + configuration.log("Bot: Events handled. Dequeuing frame #" + botGame.getFrameCount()); frameBuffer.dequeue(); } } catch (Throwable throwable) { @@ -169,7 +227,7 @@ private Thread createBotThread() { } private void handleEvents() { - ClientData.GameData gameData = game.clientData().gameData(); + ClientData.GameData gameData = botGame.clientData().gameData(); // Populate gameOver before invoking event handlers (in case the bot throws) for (int i = 0; i < gameData.getEventCount(); i++) { @@ -184,7 +242,7 @@ private void handleEvents() { ! gameOver && (gameData.getFrameCount() > 0 || ! configuration.unlimitedFrameZero), () -> { for (int i = 0; i < gameData.getEventCount(); i++) { - EventHandler.operation(eventListener, game, gameData.getEvents(i)); + EventHandler.operation(eventListener, botGame, gameData.getEvents(i)); } }); } diff --git a/src/main/java/bwapi/FrameBuffer.java b/src/main/java/bwapi/FrameBuffer.java index a8c0f581..2726fad2 100644 --- a/src/main/java/bwapi/FrameBuffer.java +++ b/src/main/java/bwapi/FrameBuffer.java @@ -17,7 +17,7 @@ class FrameBuffer { private ByteBuffer liveData; private PerformanceMetrics performanceMetrics; private BWClientConfiguration configuration; - private int size; + private int capacity; private int stepGame = 0; private int stepBot = 0; private ArrayList dataBuffer = new ArrayList<>(); @@ -27,9 +27,9 @@ class FrameBuffer { final Condition conditionSize = lockSize.newCondition(); FrameBuffer(BWClientConfiguration configuration) { - this.size = configuration.asyncFrameBufferSize; + this.capacity = configuration.asyncFrameBufferCapacity; this.configuration = configuration; - while(dataBuffer.size() < size) { + while(dataBuffer.size() < capacity) { dataBuffer.add(ByteBuffer.allocateDirect(BUFFER_SIZE)); } } @@ -52,17 +52,24 @@ synchronized int framesBuffered() { } /** - * @return Whether the frame buffer is empty and has no frames available for the bot to consume. + * @return Number of frames currently stored in the buffer */ - boolean empty() { + int size() { lockSize.lock(); try { - return framesBuffered() <= 0; + return framesBuffered(); } finally { lockSize.unlock(); } } + /** + * @return Whether the frame buffer is empty and has no frames available for the bot to consume. + */ + boolean empty() { + return size() <= 0; + } + /** * @return Whether the frame buffer is full and can not buffer any additional frames. * When the frame buffer is full, JBWAPI must wait for the bot to complete a frame before returning control to StarCraft. @@ -70,18 +77,18 @@ boolean empty() { boolean full() { lockSize.lock(); try { - return framesBuffered() >= size; + return framesBuffered() >= capacity; } finally { lockSize.unlock(); } } private int indexGame() { - return stepGame % size; + return stepGame % capacity; } private int indexBot() { - return stepBot % size; + return stepBot % capacity; } /** diff --git a/src/main/java/bwapi/PerformanceMetrics.java b/src/main/java/bwapi/PerformanceMetrics.java index 4ec80276..a97d9b4f 100644 --- a/src/main/java/bwapi/PerformanceMetrics.java +++ b/src/main/java/bwapi/PerformanceMetrics.java @@ -57,6 +57,18 @@ public class PerformanceMetrics { */ public PerformanceMetric botIdle; + public PerformanceMetric frameDuration5; + public PerformanceMetric frameDuration10; + public PerformanceMetric frameDuration15; + public PerformanceMetric frameDuration20; + public PerformanceMetric frameDuration25; + public PerformanceMetric frameDuration30; + public PerformanceMetric frameDuration35; + public PerformanceMetric frameDuration40; + public PerformanceMetric frameDuration45; + public PerformanceMetric frameDuration50; + public PerformanceMetric frameDuration55; + private BWClientConfiguration configuration; public PerformanceMetrics(BWClientConfiguration configuration) { @@ -65,18 +77,28 @@ public PerformanceMetrics(BWClientConfiguration configuration) { } void reset() { - final int frameDurationBufferMs = 5; final int sideEffectsBufferMs = 1; final int realTimeFrameMs = 42; - totalFrameDuration = new PerformanceMetric("Total frame duration", configuration.maxFrameDurationMs + frameDurationBufferMs); - copyingToBuffer = new PerformanceMetric("Time copying to buffer", 15); - intentionallyBlocking = new PerformanceMetric("Intentionally blocking", 0); + totalFrameDuration = new PerformanceMetric("JBWAPI frame duration", configuration.maxFrameDurationMs + 5); + copyingToBuffer = new PerformanceMetric("Time copying to buffer", configuration.maxFrameDurationMs + 5); + intentionallyBlocking = new PerformanceMetric("Blocking with full buffer", 0); frameBufferSize = new PerformanceMetric("Frames buffered", 0); - framesBehind = new PerformanceMetric("Frames behind", 0); - flushSideEffects = new PerformanceMetric("Flush side effects", sideEffectsBufferMs ); - botResponse = new PerformanceMetric("Bot responses", configuration.maxFrameDurationMs); - bwapiResponse = new PerformanceMetric("BWAPI responses", realTimeFrameMs); + framesBehind = new PerformanceMetric("Frames behind real-time", 0); + flushSideEffects = new PerformanceMetric("Flushing side effects", sideEffectsBufferMs ); + botResponse = new PerformanceMetric("Bot event handlers", configuration.maxFrameDurationMs); + bwapiResponse = new PerformanceMetric("Responses from BWAPI", realTimeFrameMs); botIdle = new PerformanceMetric("Bot idle", Long.MAX_VALUE); + frameDuration5 = new PerformanceMetric("JBWAPI frame @ 5ms", 5); + frameDuration10 = new PerformanceMetric("JBWAPI frame @ 10ms", 10); + frameDuration15 = new PerformanceMetric("JBWAPI frame @ 15ms", 15); + frameDuration20 = new PerformanceMetric("JBWAPI frame @ 20ms", 20); + frameDuration25 = new PerformanceMetric("JBWAPI frame @ 25ms", 25); + frameDuration30 = new PerformanceMetric("JBWAPI frame @ 30ms", 30); + frameDuration35 = new PerformanceMetric("JBWAPI frame @ 35ms", 35); + frameDuration40 = new PerformanceMetric("JBWAPI frame @ 40ms", 40); + frameDuration45 = new PerformanceMetric("JBWAPI frame @ 45ms", 45); + frameDuration50 = new PerformanceMetric("JBWAPI frame @ 50ms", 50); + frameDuration55 = new PerformanceMetric("JBWAPI frame @ 55ms", 55); } @Override @@ -90,6 +112,17 @@ public String toString() { + "\n" + flushSideEffects.toString() + "\n" + botResponse.toString() + "\n" + bwapiResponse.toString() - + "\n" + botIdle.toString(); + + "\n" + botIdle.toString() + + "\n" + frameDuration5.toString() + + "\n" + frameDuration10.toString() + + "\n" + frameDuration15.toString() + + "\n" + frameDuration20.toString() + + "\n" + frameDuration25.toString() + + "\n" + frameDuration30.toString() + + "\n" + frameDuration35.toString() + + "\n" + frameDuration40.toString() + + "\n" + frameDuration45.toString() + + "\n" + frameDuration50.toString() + + "\n" + frameDuration55.toString(); } } diff --git a/src/test/java/bwapi/SynchronizationEnvironment.java b/src/test/java/bwapi/SynchronizationEnvironment.java index 4385cdce..cf8063ab 100644 --- a/src/test/java/bwapi/SynchronizationEnvironment.java +++ b/src/test/java/bwapi/SynchronizationEnvironment.java @@ -86,7 +86,7 @@ void runGame(int onEndFrame) { if (configuration.async) { final long MEGABYTE = 1024 * 1024; long memoryFree = Runtime.getRuntime().freeMemory() / MEGABYTE; - long memoryRequired = configuration.asyncFrameBufferSize * ClientData.GameData.SIZE / MEGABYTE; + long memoryRequired = configuration.asyncFrameBufferCapacity * ClientData.GameData.SIZE / MEGABYTE; assertTrue( "Unit test needs to be run with sufficient memory to allocate frame buffer. Has " + memoryFree diff --git a/src/test/java/bwapi/SynchronizationTest.java b/src/test/java/bwapi/SynchronizationTest.java index ab6349ba..a2f6df05 100644 --- a/src/test/java/bwapi/SynchronizationTest.java +++ b/src/test/java/bwapi/SynchronizationTest.java @@ -4,9 +4,7 @@ import java.util.stream.IntStream; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThrows; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; public class SynchronizationTest { @@ -45,7 +43,7 @@ public void async_IfException_ThrowException() { // An exception in the bot thread must be re-thrown by the main thread. SynchronizationEnvironment environment = new SynchronizationEnvironment(); environment.configuration.async = true; - environment.configuration.asyncFrameBufferSize = 3; + environment.configuration.asyncFrameBufferCapacity = 3; environment.onFrame(0, () -> { throw new RuntimeException("Simulated bot exception"); }); assertThrows(RuntimeException.class, environment::runGame); } @@ -55,7 +53,7 @@ public void sync_IfDelay_ThenNoBuffer() { SynchronizationEnvironment environment = new SynchronizationEnvironment(); environment.configuration.async = false; environment.configuration.maxFrameDurationMs = 1; - environment.configuration.asyncFrameBufferSize = 3; + environment.configuration.asyncFrameBufferCapacity = 3; IntStream.range(0, 5).forEach(frame -> { environment.onFrame(frame, () -> { @@ -74,7 +72,7 @@ public void async_IfBotDelay_ThenClientBuffers() { SynchronizationEnvironment environment = new SynchronizationEnvironment(); environment.configuration.async = true; environment.configuration.maxFrameDurationMs = 10; - environment.configuration.asyncFrameBufferSize = 4; + environment.configuration.asyncFrameBufferCapacity = 4; environment.onFrame(1, () -> { sleepUnchecked(50); @@ -97,7 +95,7 @@ public void async_IfBotDelay_ThenClientStalls() { SynchronizationEnvironment environment = new SynchronizationEnvironment(); environment.configuration.async = true; environment.configuration.maxFrameDurationMs = 50; - environment.configuration.asyncFrameBufferSize = 5; + environment.configuration.asyncFrameBufferCapacity = 5; environment.onFrame(1, () -> { sleepUnchecked(125); @@ -117,13 +115,33 @@ public void async_IfBotDelay_ThenClientStalls() { environment.runGame(); } + @Test + public void async_IfUnsafe_ThenBotProceedsImmediately() { + SynchronizationEnvironment environment = new SynchronizationEnvironment(); + environment.configuration.async = true; + environment.configuration.asyncUnsafe = true; + environment.configuration.maxFrameDurationMs = 100; + environment.configuration.logVerbosely = true; + + environment.onFrame(1, () -> { + assertEquals("Bot should be observing frame 1", 1, environment.bwClient.getGame().getFrameCount()); + assertEquals("Live game should be observing frame 1", 1, environment.liveGameData().getFrameCount()); + environment.liveGameData().setFps(12345); + assertEquals("Bot should be observing live game", 12345, environment.bwClient.getGame().getFPS()); + sleepUnchecked(50); + assertFalse("Bot should be observing frame buffer", 12345 == environment.bwClient.getGame().getFPS()); + }); + + environment.runGame(3); + } + @Test public void async_IfFrameZeroWaitsEnabled_ThenAllowInfiniteTime() { SynchronizationEnvironment environment = new SynchronizationEnvironment(); environment.configuration.async = true; environment.configuration.unlimitedFrameZero = true; environment.configuration.maxFrameDurationMs = 5; - environment.configuration.asyncFrameBufferSize = 2; + environment.configuration.asyncFrameBufferCapacity = 2; environment.onFrame(0, () -> { sleepUnchecked(50); @@ -141,7 +159,7 @@ public void async_IfFrameZeroWaitsDisabled_ThenClientBuffers() { environment.configuration.async = true; environment.configuration.unlimitedFrameZero = false; environment.configuration.maxFrameDurationMs = 5; - environment.configuration.asyncFrameBufferSize = 2; + environment.configuration.asyncFrameBufferCapacity = 2; environment.onFrame(0, () -> { sleepUnchecked(50); @@ -192,7 +210,7 @@ public void async_MeasurePerformance_FrameBufferSizeAndFramesBehind() { SynchronizationEnvironment environment = new SynchronizationEnvironment(); environment.configuration.async = true; environment.configuration.unlimitedFrameZero = true; - environment.configuration.asyncFrameBufferSize = 3; + environment.configuration.asyncFrameBufferCapacity = 3; environment.configuration.maxFrameDurationMs = 20; environment.onFrame(5, () -> { @@ -276,7 +294,7 @@ public void MeasurePerformance_BotIdle() { final int frames = 10; SynchronizationEnvironment environment = new SynchronizationEnvironment(); environment.configuration.async = true; - environment.configuration.asyncFrameBufferSize = 3; + environment.configuration.asyncFrameBufferCapacity = 3; environment.configuration.unlimitedFrameZero = true; environment.setBwapiDelayMs(bwapiDelayMs); environment.runGame(frames); @@ -289,7 +307,7 @@ public void async_MeasurePerformance_IntentionallyBlocking() { SynchronizationEnvironment environment = new SynchronizationEnvironment(); environment.configuration.async = true; environment.configuration.unlimitedFrameZero = true; - environment.configuration.asyncFrameBufferSize = 2; + environment.configuration.asyncFrameBufferCapacity = 2; environment.configuration.maxFrameDurationMs = 20; final int frameDelayMs = 100; environment.onFrame(1, () -> { @@ -299,7 +317,7 @@ public void async_MeasurePerformance_IntentionallyBlocking() { assertWithin( "2: Intentionally blocking previous", environment.metrics().intentionallyBlocking.lastValue, - frameDelayMs - environment.configuration.asyncFrameBufferSize * environment.configuration.maxFrameDurationMs, + frameDelayMs - environment.configuration.asyncFrameBufferCapacity * environment.configuration.maxFrameDurationMs, MS_MARGIN); sleepUnchecked(100); }); From 512795b0c6a82086b6d2f1ff6f1d360ab931b881 Mon Sep 17 00:00:00 2001 From: dgant Date: Mon, 21 Sep 2020 22:21:27 -0400 Subject: [PATCH 33/56] Implemented partial frame copies, cutting about 3/4 off the amount of data we need to copy per frame --- src/main/java/bwapi/FrameBuffer.java | 79 +++++++++++++++----- src/test/java/bwapi/SynchronizationTest.java | 2 +- 2 files changed, 61 insertions(+), 20 deletions(-) diff --git a/src/main/java/bwapi/FrameBuffer.java b/src/main/java/bwapi/FrameBuffer.java index 2726fad2..96b9e009 100644 --- a/src/main/java/bwapi/FrameBuffer.java +++ b/src/main/java/bwapi/FrameBuffer.java @@ -107,10 +107,19 @@ void enqueueFrame() { performanceMetrics.intentionallyBlocking.stopTiming(); } finally { lockSize.unlock(); }; - performanceMetrics.copyingToBuffer.time(() -> { - ByteBuffer dataTarget = dataBuffer.get(indexGame()); - copyBuffer(liveData, dataTarget); - }); + // For the first frame of the game, populate all buffers completely + // This is to ensure all buffers have access to immutable data like regions/walkability/buildability + // Afterwards, we want to shorten this process by only copying important and mutable data + if (stepGame == 0) { + for (ByteBuffer frameBuffer : dataBuffer) { + copyBuffer(liveData, frameBuffer, true); + } + } else { + performanceMetrics.copyingToBuffer.time(() -> { + ByteBuffer dataTarget = dataBuffer.get(indexGame()); + copyBuffer(liveData, dataTarget, false); + }); + } lockSize.lock(); try { @@ -144,31 +153,63 @@ void dequeue() { } finally { lockSize.unlock(); } } - void copyBuffer(ByteBuffer source, ByteBuffer destination) { + /** + * + * @param source Address to copy from + * @param destination Address to copy to + * @param size Number of bytes to copy + * @return True if the copy succeeded + */ + private boolean tryMemcpyBuffer(ByteBuffer source, ByteBuffer destination, long offset, int size) { + long addressSource = ((DirectBuffer) source).address() + offset; + long addressDestination = ((DirectBuffer) destination).address() + offset; + try { + if (Platform.isWindows()) { + if (Platform.is64Bit()) { + MSVCRT.INSTANCE.memcpy(addressDestination, addressSource, size); + return true; + } else { + MSVCRT.INSTANCE.memcpy((int) addressDestination, (int) addressSource, size); + return true; + } + } + } + catch(Exception ignored) {} + return false; + } + + void copyBuffer(ByteBuffer source, ByteBuffer destination, boolean copyEverything) { /* The speed at which we copy data into the frame buffer is a major cost of JBWAPI's asynchronous operation. - Copy times observed in the wild range from 2.6ms - 12ms. + Copy times observed in the wild usually range from 2.6ms - 19ms but are prone to large amounts of variance. The normal Java way to execute this copy is via ByteBuffer.put(), which has reasonably good performance characteristics. Some experiments in 64-bit JRE have shown that using a native memcpy achieves a 35% speedup. Some experiments in 32-bit JRE show no difference in performance. So, speculatively, we attempt to do a native memcpy. - */ - long addressSource = ((DirectBuffer) source).address(); - long addressDestination = ((DirectBuffer) destination).address(); - try { - if (Platform.isWindows()) { - if (Platform.is64Bit()) { - MSVCRT.INSTANCE.memcpy(addressDestination, addressSource, FrameBuffer.BUFFER_SIZE); - return; - } else { - MSVCRT.INSTANCE.memcpy((int) addressDestination, (int) addressSource, FrameBuffer.BUFFER_SIZE); - return; - } + */ + + if (copyEverything) { + if (tryMemcpyBuffer(source, destination, 0, FrameBuffer.BUFFER_SIZE)) { + return; + } + } else { + int STATICTILES_START = 3447004; // getGroundHeight, isWalkable, isBuildable + int STATICTILES_END = 4823260; + int REGION_START = 5085404; // getMapTileRegionId, ..., getRegions + int REGION_END = 10586480; + int STRINGSSHAPES_START = 10962632; // getStringCount, ... getShapes + int STRINGSHAPES_END = 32242636; + int UNITFINDER_START = 32962644; + if ( + tryMemcpyBuffer(source, destination, 0, STATICTILES_START) + && tryMemcpyBuffer(source, destination, STATICTILES_END, REGION_START - STATICTILES_END) + && tryMemcpyBuffer(source, destination, REGION_END, STRINGSSHAPES_START - REGION_END) + && tryMemcpyBuffer(source, destination, STRINGSHAPES_END, UNITFINDER_START - STRINGSHAPES_END)) { + return; } } - catch(Exception ignored) {} // There's no specific case where we expect to fail above, // but this is a safe fallback regardless, diff --git a/src/test/java/bwapi/SynchronizationTest.java b/src/test/java/bwapi/SynchronizationTest.java index a2f6df05..7b9d7c62 100644 --- a/src/test/java/bwapi/SynchronizationTest.java +++ b/src/test/java/bwapi/SynchronizationTest.java @@ -195,7 +195,7 @@ public void async_MeasurePerformance_CopyingToBuffer() { SynchronizationEnvironment environment = new SynchronizationEnvironment(); environment.configuration.async = true; environment.runGame(20); - final double minObserved = 2; + final double minObserved = 0.25; final double maxObserved = 15; final double meanObserved = (minObserved + maxObserved) / 2; final double rangeObserved = (maxObserved - minObserved) / 2; From d016e033bc2af878164d32dbe0ff2787178bb5ef Mon Sep 17 00:00:00 2001 From: dgant Date: Tue, 22 Sep 2020 20:17:43 -0400 Subject: [PATCH 34/56] Added some more metrics. Added multiple thresholds to metrics. --- src/main/java/bwapi/BWClient.java | 14 +-- src/main/java/bwapi/BotWrapper.java | 10 +- src/main/java/bwapi/PerformanceMetric.java | 108 ++++++++++++-------- src/main/java/bwapi/PerformanceMetrics.java | 86 ++++++---------- src/main/java/bwapi/Unit.java | 2 +- 5 files changed, 106 insertions(+), 114 deletions(-) diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index 29524e9d..d29098c9 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -94,24 +94,12 @@ public void startGame(BWClientConfiguration configuration) { } while (liveGameData.isInGame()) { boolean timeFrame = liveGameData.getFrameCount() > 0 || ! configuration.unlimitedFrameZero; - performanceMetrics.frameDuration5.timeIf(timeFrame, () -> - performanceMetrics.frameDuration10.timeIf(timeFrame, () -> - performanceMetrics.frameDuration15.timeIf(timeFrame, () -> - performanceMetrics.frameDuration20.timeIf(timeFrame, () -> - performanceMetrics.frameDuration25.timeIf(timeFrame, () -> - performanceMetrics.frameDuration30.timeIf(timeFrame, () -> - performanceMetrics.frameDuration35.timeIf(timeFrame, () -> - performanceMetrics.frameDuration40.timeIf(timeFrame, () -> - performanceMetrics.frameDuration45.timeIf(timeFrame, () -> - performanceMetrics.frameDuration50.timeIf(timeFrame, () -> - performanceMetrics.frameDuration55.timeIf(timeFrame, () -> performanceMetrics.totalFrameDuration.timeIf( timeFrame, () -> { botWrapper.onFrame(); performanceMetrics.flushSideEffects.time(() -> getGame().sideEffects.flushTo(liveGameData)); - }) - ))))))))))); + }); performanceMetrics.bwapiResponse.time(client::update); if (!client.isConnected()) { System.out.println("Reconnecting..."); diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java index 15c11afe..126ff948 100644 --- a/src/main/java/bwapi/BotWrapper.java +++ b/src/main/java/bwapi/BotWrapper.java @@ -108,7 +108,11 @@ void onFrame() { int frame = liveClientData.gameData().getFrameCount(); configuration.log("Main: Enqueuing frame #" + frame); frameBuffer.enqueueFrame(); + configuration.log("Main: Enqueued frame #" + frame); + if (frame > 0) { + performanceMetrics.clientIdle.startTiming(); + } frameBuffer.lockSize.lock(); try { while (!frameBuffer.empty()) { @@ -140,11 +144,14 @@ void onFrame() { } configuration.log("Main: Waiting " + remainingNanos / 1000000 + "ms for bot on frame #" + frame); frameBuffer.conditionSize.awaitNanos(remainingNanos); + long excessNanos = Math.max(0, (System.nanoTime() - endNanos) / 1000000); + performanceMetrics.excessSleep.record(excessNanos); } } } catch(InterruptedException ignored) { } finally { frameBuffer.lockSize.unlock(); + performanceMetrics.clientIdle.stopTiming(); configuration.log("Main: onFrame asynchronous end"); } } else { @@ -152,7 +159,6 @@ void onFrame() { handleEvents(); configuration.log("Main: onFrame synchronous end"); } - } /** @@ -235,7 +241,7 @@ private void handleEvents() { } if (configuration.async) { - performanceMetrics.framesBehind.record(frameBuffer.framesBuffered() - 1); + performanceMetrics.framesBehind.record(Math.max(1, frameBuffer.framesBuffered()) - 1); } performanceMetrics.botResponse.timeIf( diff --git a/src/main/java/bwapi/PerformanceMetric.java b/src/main/java/bwapi/PerformanceMetric.java index f0f9278f..c9828857 100644 --- a/src/main/java/bwapi/PerformanceMetric.java +++ b/src/main/java/bwapi/PerformanceMetric.java @@ -1,28 +1,56 @@ package bwapi; import java.text.DecimalFormat; +import java.util.ArrayList; /** * Aggregates labeled time series data. */ public class PerformanceMetric { - private final String name; - private final long maxAllowed; + class RunningTotal { + int samples = 0; + double mean = 0d; + double min = Long.MAX_VALUE; + double max = Long.MIN_VALUE; + void record(double value) { + min = Math.min(min, value); + max = Math.max(max, value); + mean = (mean * samples + value) / (samples + 1d); + ++samples; + } + } + class Threshold { + double threshold; + RunningTotal runningTotal = new RunningTotal(); + Threshold(double value) { + threshold = value; + } + void record(double value) { + if (value >= threshold) { + runningTotal.record(value); + } + } + public String toString() { + if (runningTotal.samples <= 0) { + return ""; + } + DecimalFormat formatter = new DecimalFormat("###,###.#"); + return "\n>= " + formatter.format(threshold) + ": " + runningTotal.samples + " samples averaging " + formatter.format(runningTotal.mean) + ")."; + } + } - public double minValue = Long.MAX_VALUE; - public double maxValue = Long.MIN_VALUE; - public double lastValue = 0; - public double avgValue = 0; - public double avgValueExceeding = 0; - public int samples = 0; - public int samplesExceeding = 0; + private final String name; public int interrupted = 0; - private long timeStarted = 0; - PerformanceMetric(String name, long maxAllowed) { + RunningTotal runningTotal = new RunningTotal(); + private ArrayList thresholds = new ArrayList<>(); + + PerformanceMetric(String name, double... thresholds) { this.name = name; - this.maxAllowed = maxAllowed; + for (double threshold : thresholds) { + this.thresholds.add(new Threshold(threshold)); + } } /** @@ -77,15 +105,8 @@ void stopTiming() { * Manually records a specific value. */ void record(double value) { - lastValue = value; - minValue = Math.min(minValue, value); - maxValue = Math.max(maxValue, value); - avgValue = (avgValue * samples + value) / (samples + 1d); - ++samples; - if (value > maxAllowed) { - avgValueExceeding = (avgValueExceeding * samplesExceeding + value) / (samplesExceeding + 1d); - ++samplesExceeding; - } + runningTotal.record(value); + thresholds.forEach(threshold -> threshold.record(value)); } /** @@ -93,31 +114,28 @@ void record(double value) { */ @Override public String toString() { + if (runningTotal.samples <= 0) { + return name + ": No samples."; + } DecimalFormat formatter = new DecimalFormat("###,###.#"); - return name + String output = name + ": " - + (samples > 0 - ? formatter.format(samples) - + " samples averaging " - + formatter.format(avgValue) - + " [" - + formatter.format(minValue) - + " - " - + formatter.format(maxValue) - + "] over " - + samples - + " samples" - + (samplesExceeding > 0 - ? ". " - + samplesExceeding - + " values over " - + maxAllowed - + " averaging " - + formatter.format(avgValueExceeding) - : "") - : "No samples") - + (interrupted > 0 - ? ". Interrupted " + interrupted + " times" - : ""); + + formatter.format(runningTotal.samples) + + " samples averaging " + + formatter.format(runningTotal.mean) + + " [" + + formatter.format(runningTotal.min) + + " - " + + formatter.format(runningTotal.max) + + "] over " + + runningTotal.samples + + " samples. "; + for (Threshold threshold : thresholds) { + output += threshold.toString(); + } + if (interrupted > 0) { + output += "\n\tInterrupted " + interrupted + " times"; + } + return output; } } diff --git a/src/main/java/bwapi/PerformanceMetrics.java b/src/main/java/bwapi/PerformanceMetrics.java index a97d9b4f..f22d1298 100644 --- a/src/main/java/bwapi/PerformanceMetrics.java +++ b/src/main/java/bwapi/PerformanceMetrics.java @@ -10,95 +10,84 @@ public class PerformanceMetrics { * Likely to be at least a little bit of an undercount, * given that the tournament module is timing a superset of JBWAPI's execution time. */ - public PerformanceMetric totalFrameDuration; + PerformanceMetric totalFrameDuration; /** * Time spent copying game data from system pipe shared memory to a frame buffer. * Applicable only in asynchronous mode. */ - public PerformanceMetric copyingToBuffer; + PerformanceMetric copyingToBuffer; /** * Time spent intentionally blocking on bot operation due to a full frame buffer. * Applicable only in asynchronous mode. */ - public PerformanceMetric intentionallyBlocking; + PerformanceMetric intentionallyBlocking; /** * Number of frames backed up in the frame buffer, after enqueuing each frame (and not including the newest frame). * Applicable only in asynchronous mode. */ - public PerformanceMetric frameBufferSize; + PerformanceMetric frameBufferSize; /** * Number of frames behind real-time the bot is at the time it handles events. * Applicable only in asynchronous mode. */ - public PerformanceMetric framesBehind; + PerformanceMetric framesBehind; /** * Time spent applying bot commands to the live frame. */ - public PerformanceMetric flushSideEffects; + PerformanceMetric flushSideEffects; /** * Time spent waiting for bot event handlers to complete for a single frame. */ - public PerformanceMetric botResponse; + PerformanceMetric botResponse; /** * Time spent waiting for a response from BWAPI; is likely reflective of the performance of any opponent bots. */ - public PerformanceMetric bwapiResponse; + PerformanceMetric bwapiResponse; /** * Time bot spends idle. * Applicable only in asynchronous mode. */ - public PerformanceMetric botIdle; - - public PerformanceMetric frameDuration5; - public PerformanceMetric frameDuration10; - public PerformanceMetric frameDuration15; - public PerformanceMetric frameDuration20; - public PerformanceMetric frameDuration25; - public PerformanceMetric frameDuration30; - public PerformanceMetric frameDuration35; - public PerformanceMetric frameDuration40; - public PerformanceMetric frameDuration45; - public PerformanceMetric frameDuration50; - public PerformanceMetric frameDuration55; + PerformanceMetric botIdle; + + /** + * Time the main thread spends idle, waiting for the bot to finish processing frames. + * Applicable only in asynchronous mode. + */ + PerformanceMetric clientIdle; + + /** + * Time the main thread spends oversleeping its timeout target, potentially causing overtime frames. + * Applicable only in asynchronous mode. + */ + PerformanceMetric excessSleep; private BWClientConfiguration configuration; - public PerformanceMetrics(BWClientConfiguration configuration) { + PerformanceMetrics(BWClientConfiguration configuration) { this.configuration = configuration; reset(); } void reset() { - final int sideEffectsBufferMs = 1; - final int realTimeFrameMs = 42; - totalFrameDuration = new PerformanceMetric("JBWAPI frame duration", configuration.maxFrameDurationMs + 5); - copyingToBuffer = new PerformanceMetric("Time copying to buffer", configuration.maxFrameDurationMs + 5); + totalFrameDuration = new PerformanceMetric("JBWAPI frame duration", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); + copyingToBuffer = new PerformanceMetric("Time copying to buffer", 5, 10, 15, 20, 25, 30); intentionallyBlocking = new PerformanceMetric("Blocking with full buffer", 0); - frameBufferSize = new PerformanceMetric("Frames buffered", 0); - framesBehind = new PerformanceMetric("Frames behind real-time", 0); - flushSideEffects = new PerformanceMetric("Flushing side effects", sideEffectsBufferMs ); - botResponse = new PerformanceMetric("Bot event handlers", configuration.maxFrameDurationMs); - bwapiResponse = new PerformanceMetric("Responses from BWAPI", realTimeFrameMs); + frameBufferSize = new PerformanceMetric("Frames buffered", 0, 1); + framesBehind = new PerformanceMetric("Frames behind real-time", 0, 1); + flushSideEffects = new PerformanceMetric("Flushing side effects", 1, 3, 5); + botResponse = new PerformanceMetric("Bot event handlers", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); + bwapiResponse = new PerformanceMetric("Responses from BWAPI", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); botIdle = new PerformanceMetric("Bot idle", Long.MAX_VALUE); - frameDuration5 = new PerformanceMetric("JBWAPI frame @ 5ms", 5); - frameDuration10 = new PerformanceMetric("JBWAPI frame @ 10ms", 10); - frameDuration15 = new PerformanceMetric("JBWAPI frame @ 15ms", 15); - frameDuration20 = new PerformanceMetric("JBWAPI frame @ 20ms", 20); - frameDuration25 = new PerformanceMetric("JBWAPI frame @ 25ms", 25); - frameDuration30 = new PerformanceMetric("JBWAPI frame @ 30ms", 30); - frameDuration35 = new PerformanceMetric("JBWAPI frame @ 35ms", 35); - frameDuration40 = new PerformanceMetric("JBWAPI frame @ 40ms", 40); - frameDuration45 = new PerformanceMetric("JBWAPI frame @ 45ms", 45); - frameDuration50 = new PerformanceMetric("JBWAPI frame @ 50ms", 50); - frameDuration55 = new PerformanceMetric("JBWAPI frame @ 55ms", 55); + clientIdle = new PerformanceMetric("Client idling", configuration.maxFrameDurationMs); + excessSleep = new PerformanceMetric("Excess sleep", 1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); } @Override @@ -113,16 +102,7 @@ public String toString() { + "\n" + botResponse.toString() + "\n" + bwapiResponse.toString() + "\n" + botIdle.toString() - + "\n" + frameDuration5.toString() - + "\n" + frameDuration10.toString() - + "\n" + frameDuration15.toString() - + "\n" + frameDuration20.toString() - + "\n" + frameDuration25.toString() - + "\n" + frameDuration30.toString() - + "\n" + frameDuration35.toString() - + "\n" + frameDuration40.toString() - + "\n" + frameDuration45.toString() - + "\n" + frameDuration50.toString() - + "\n" + frameDuration55.toString(); + + "\n" + clientIdle.toString() + + "\n" + excessSleep.toString(); } } diff --git a/src/main/java/bwapi/Unit.java b/src/main/java/bwapi/Unit.java index 0ac4cb1b..a7b77596 100644 --- a/src/main/java/bwapi/Unit.java +++ b/src/main/java/bwapi/Unit.java @@ -115,7 +115,7 @@ public int getID() { *

* In the event that this function returns false, there are two cases to consider: * 1. You own the unit. This means the unit is dead. - * 2. Another player owns the unit. This could either mean that you don't have access + * 2. Another player owns the unit. This could either runningTotal that you don't have access * to the unit or that the unit has died. You can specifically identify dead units * by polling onUnitDestroy. * @see #isVisible From 71dbc8dd46fea3e6cb5550302483e7aed243c1ba Mon Sep 17 00:00:00 2001 From: dgant Date: Wed, 23 Sep 2020 16:02:59 -0400 Subject: [PATCH 35/56] Trying various tactics to resolve timeouts. Added a timer resolution hack which should force Windows to use the lowest resolution (16ms) for GetTickCount() (though it appeared to be doing that in all environments anyway). Reduced thread priority for JBWAPI + bot threads to increase likelihood that BWAPI receives priority sooner. --- src/main/java/bwapi/BWClient.java | 25 +++++++++++ src/main/java/bwapi/BotWrapper.java | 2 + src/main/java/bwapi/Client.java | 3 ++ src/main/java/bwapi/MSVCRT.java | 2 +- src/main/java/bwapi/PerformanceMetric.java | 4 +- src/main/java/bwapi/PerformanceMetrics.java | 45 ++++++++++++++----- .../java/bwapi/TimerResolutionThread.java | 34 ++++++++++++++ 7 files changed, 101 insertions(+), 14 deletions(-) create mode 100644 src/main/java/bwapi/TimerResolutionThread.java diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index d29098c9..91a4299b 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -1,5 +1,7 @@ package bwapi; +import com.sun.jna.platform.win32.Kernel32; + import java.util.Objects; /** @@ -10,10 +12,12 @@ public class BWClient { private BotWrapper botWrapper; private Client client; private PerformanceMetrics performanceMetrics; + private TimerResolutionThread timerResolutionThread; public BWClient(final BWEventListener eventListener) { Objects.requireNonNull(eventListener); this.eventListener = eventListener; + this.timerResolutionThread = new TimerResolutionThread(); } /** @@ -75,6 +79,10 @@ public void startGame(BWClientConfiguration configuration) { configuration.validate(); botWrapper = new BotWrapper(configuration, eventListener); + // Use reduced priority to encourage Windows to give priority to StarCraft.exe/BWAPI. + // If BWAPI doesn't get priority, it may not detect completion of a frame on our end in timely fashion. + Thread.currentThread().setPriority(Thread.NORM_PRIORITY); + if (client == null) { client = new Client(configuration); } @@ -94,12 +102,29 @@ public void startGame(BWClientConfiguration configuration) { } while (liveGameData.isInGame()) { boolean timeFrame = liveGameData.getFrameCount() > 0 || ! configuration.unlimitedFrameZero; + + long ticksBefore = Kernel32.INSTANCE.GetTickCount(); performanceMetrics.totalFrameDuration.timeIf( timeFrame, () -> { botWrapper.onFrame(); performanceMetrics.flushSideEffects.time(() -> getGame().sideEffects.flushTo(liveGameData)); }); + long ticksAfter = Kernel32.INSTANCE.GetTickCount(); + if (timeFrame) { + long deltaTicks = ticksAfter - ticksBefore; + long deltaMillis = (long) performanceMetrics.totalFrameDuration.runningTotal.last; + long delta = deltaMillis - deltaTicks; + if (Math.abs(delta) > 1000) { + System.out.println("Got weird tick delta: " + ticksAfter + ", " + ticksBefore + ", " + deltaTicks + ", " + deltaMillis + ", " + delta); + performanceMetrics.weirdTimeDelta.record(1); + } else if (delta > 0) { + performanceMetrics.positiveTimeDelta.record(delta); + } else if (delta < 0) { + performanceMetrics.negativeTimeDelta.record(-delta); + } + } + performanceMetrics.bwapiResponse.time(client::update); if (!client.isConnected()) { System.out.println("Reconnecting..."); diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java index 126ff948..a9269b01 100644 --- a/src/main/java/bwapi/BotWrapper.java +++ b/src/main/java/bwapi/BotWrapper.java @@ -81,6 +81,8 @@ void onFrame() { configuration.log("Main: Starting bot thread"); botThread = createBotThread(); botThread.setName("JBWAPI Bot"); + // Reduced priority helps ensure that StarCraft.exe/BWAPI pick up on our frame completion in timely fashion + botThread.setPriority(Thread.MIN_PRIORITY); botThread.start(); } diff --git a/src/main/java/bwapi/Client.java b/src/main/java/bwapi/Client.java index 05784a9b..d819197d 100644 --- a/src/main/java/bwapi/Client.java +++ b/src/main/java/bwapi/Client.java @@ -247,6 +247,9 @@ void update() { while (code != 2) { try { code = pipeObjectHandle.readByte(); + if (code != 2) { + Thread.sleep(0, 1); + } } catch (Exception e) { System.err.println("failed, disconnecting"); diff --git a/src/main/java/bwapi/MSVCRT.java b/src/main/java/bwapi/MSVCRT.java index 28a9b222..e7b1c55b 100644 --- a/src/main/java/bwapi/MSVCRT.java +++ b/src/main/java/bwapi/MSVCRT.java @@ -4,7 +4,7 @@ import com.sun.jna.Native; /** - * JNI interface for access the native MSVC implementation of memcpy. + * JNI interface for accessing native MSVC code. */ interface MSVCRT extends Library { MSVCRT INSTANCE = Native.load("msvcrt.dll", MSVCRT.class); diff --git a/src/main/java/bwapi/PerformanceMetric.java b/src/main/java/bwapi/PerformanceMetric.java index c9828857..a4aac0ee 100644 --- a/src/main/java/bwapi/PerformanceMetric.java +++ b/src/main/java/bwapi/PerformanceMetric.java @@ -9,10 +9,12 @@ public class PerformanceMetric { class RunningTotal { int samples = 0; + double last = 0d; double mean = 0d; double min = Long.MAX_VALUE; double max = Long.MIN_VALUE; void record(double value) { + last = value; min = Math.min(min, value); max = Math.max(max, value); mean = (mean * samples + value) / (samples + 1d); @@ -35,7 +37,7 @@ public String toString() { return ""; } DecimalFormat formatter = new DecimalFormat("###,###.#"); - return "\n>= " + formatter.format(threshold) + ": " + runningTotal.samples + " samples averaging " + formatter.format(runningTotal.mean) + ")."; + return "\n>= " + formatter.format(threshold) + ": " + runningTotal.samples + " samples averaging " + formatter.format(runningTotal.mean); } } diff --git a/src/main/java/bwapi/PerformanceMetrics.java b/src/main/java/bwapi/PerformanceMetrics.java index f22d1298..ffd00f75 100644 --- a/src/main/java/bwapi/PerformanceMetrics.java +++ b/src/main/java/bwapi/PerformanceMetrics.java @@ -69,6 +69,21 @@ public class PerformanceMetrics { */ PerformanceMetric excessSleep; + /** + * Instances of System.nanoTime() measuring a longer frame duration with respect to WinAPI's GetTickCount. + */ + PerformanceMetric positiveTimeDelta; + + /** + * Instances of System.nanoTime() measuring a shorter frame duration with respect to WinAPI's GetTickCount. + */ + PerformanceMetric negativeTimeDelta; + + /** + * When Kernel32.INSTANCE.GetTickCount() returns at least one inexplicable value + */ + PerformanceMetric weirdTimeDelta; + private BWClientConfiguration configuration; PerformanceMetrics(BWClientConfiguration configuration) { @@ -76,7 +91,7 @@ public class PerformanceMetrics { reset(); } - void reset() { + public void reset() { totalFrameDuration = new PerformanceMetric("JBWAPI frame duration", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); copyingToBuffer = new PerformanceMetric("Time copying to buffer", 5, 10, 15, 20, 25, 30); intentionallyBlocking = new PerformanceMetric("Blocking with full buffer", 0); @@ -88,21 +103,27 @@ void reset() { botIdle = new PerformanceMetric("Bot idle", Long.MAX_VALUE); clientIdle = new PerformanceMetric("Client idling", configuration.maxFrameDurationMs); excessSleep = new PerformanceMetric("Excess sleep", 1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); + positiveTimeDelta = new PerformanceMetric("Positive timer delta", 1, 2, 3, 4, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); + negativeTimeDelta = new PerformanceMetric("Negative timer delta", 1, 2, 3, 4, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); + weirdTimeDelta = new PerformanceMetric("Weird timer delta"); } @Override public String toString() { return "Performance metrics:" - + "\n" + totalFrameDuration.toString() - + "\n" + copyingToBuffer.toString() - + "\n" + intentionallyBlocking.toString() - + "\n" + frameBufferSize.toString() - + "\n" + framesBehind.toString() - + "\n" + flushSideEffects.toString() - + "\n" + botResponse.toString() - + "\n" + bwapiResponse.toString() - + "\n" + botIdle.toString() - + "\n" + clientIdle.toString() - + "\n" + excessSleep.toString(); + + "\n" + totalFrameDuration.toString() + + "\n" + copyingToBuffer.toString() + + "\n" + intentionallyBlocking.toString() + + "\n" + frameBufferSize.toString() + + "\n" + framesBehind.toString() + + "\n" + flushSideEffects.toString() + + "\n" + botResponse.toString() + + "\n" + bwapiResponse.toString() + + "\n" + botIdle.toString() + + "\n" + clientIdle.toString() + + "\n" + excessSleep.toString() + + "\n" + positiveTimeDelta.toString() + + "\n" + negativeTimeDelta.toString() + + "\n" + weirdTimeDelta.toString(); } } diff --git a/src/main/java/bwapi/TimerResolutionThread.java b/src/main/java/bwapi/TimerResolutionThread.java new file mode 100644 index 00000000..3d42a7e8 --- /dev/null +++ b/src/main/java/bwapi/TimerResolutionThread.java @@ -0,0 +1,34 @@ +package bwapi; + +/** + * See https://hazelcast.com/blog/locksupport-parknanos-under-the-hood-and-the-curious-case-of-parking-part-ii-windows/ + * + * "Is this it? Just a single flag and my JVM will use high-resolution timers? + * Well, it turns out it’s not that simple. + * This flag has been reported to be broken since 2006! + * However, the very same bug report suggests a very interesting workaround:" + * + * “Do not use ForceTimeHighResolution but instead, + * at the start of the application create and start + * a daemon Thread that simply sleeps for a very long time + * (that isn’t a multiple of 10ms) + * as this will set the timer period to be 1ms for the duration of that sleep + * – which in this case is the lifetime of the VM" + * + * "What the R$#@#@ have I just read? + * Let me rephrase it: + * “If you want a high-resolution timer then just start a new thread and let it sleep forever”. + * That’s simply hilarious! + */ +class TimerResolutionThread { + TimerResolutionThread() { + Thread t = new Thread(() -> { + try { + Thread.sleep(Long.MAX_VALUE); + } catch (InterruptedException e) { // a delicious interrupt, omm, omm + } + }); + t.setDaemon(true); + t.start(); + } +} From b735923ddd0f4e5a4c221f6e0fb1544651d2261d Mon Sep 17 00:00:00 2001 From: dgant Date: Wed, 23 Sep 2020 23:15:01 -0400 Subject: [PATCH 36/56] Removed unnecessary allocations in Game API. --- src/main/java/bwapi/BWClient.java | 4 +- src/main/java/bwapi/BotWrapper.java | 2 +- src/main/java/bwapi/Client.java | 6 +- src/main/java/bwapi/Game.java | 159 +++++++++++++--------------- 4 files changed, 83 insertions(+), 88 deletions(-) diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index 91a4299b..3d4ab9cc 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -17,7 +17,7 @@ public class BWClient { public BWClient(final BWEventListener eventListener) { Objects.requireNonNull(eventListener); this.eventListener = eventListener; - this.timerResolutionThread = new TimerResolutionThread(); + //this.timerResolutionThread = new TimerResolutionThread(); } /** @@ -81,7 +81,7 @@ public void startGame(BWClientConfiguration configuration) { // Use reduced priority to encourage Windows to give priority to StarCraft.exe/BWAPI. // If BWAPI doesn't get priority, it may not detect completion of a frame on our end in timely fashion. - Thread.currentThread().setPriority(Thread.NORM_PRIORITY); + Thread.currentThread().setPriority(4); if (client == null) { client = new Client(configuration); diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java index a9269b01..6c6080ae 100644 --- a/src/main/java/bwapi/BotWrapper.java +++ b/src/main/java/bwapi/BotWrapper.java @@ -82,7 +82,7 @@ void onFrame() { botThread = createBotThread(); botThread.setName("JBWAPI Bot"); // Reduced priority helps ensure that StarCraft.exe/BWAPI pick up on our frame completion in timely fashion - botThread.setPriority(Thread.MIN_PRIORITY); + botThread.setPriority(3); botThread.start(); } diff --git a/src/main/java/bwapi/Client.java b/src/main/java/bwapi/Client.java index d819197d..c925dc2b 100644 --- a/src/main/java/bwapi/Client.java +++ b/src/main/java/bwapi/Client.java @@ -247,9 +247,13 @@ void update() { while (code != 2) { try { code = pipeObjectHandle.readByte(); + /* if (code != 2) { - Thread.sleep(0, 1); + try { + Thread.sleep(0, 1); + } catch (InterruptedException ignored) {} } + */ } catch (Exception e) { System.err.println("failed, disconnecting"); diff --git a/src/main/java/bwapi/Game.java b/src/main/java/bwapi/Game.java index 89eacdde..a7a2a9b6 100644 --- a/src/main/java/bwapi/Game.java +++ b/src/main/java/bwapi/Game.java @@ -1,8 +1,6 @@ package bwapi; -import bwapi.ClientData.Command; import bwapi.ClientData.GameData; -import bwapi.ClientData.Shape; import java.util.*; import java.util.stream.Collectors; @@ -18,7 +16,7 @@ * game state information from Starcraft Broodwar. Game state information includes all units, * resources, players, forces, bullets, terrain, fog of war, regions, etc. */ -public class Game { +public final class Game { private static final int[][] damageRatio = { // Ind, Sml, Med, Lrg, Non, Unk {0, 0, 0, 0, 0, 0}, // Independent @@ -99,7 +97,7 @@ public class Game { private Text.Size textSize = Text.Size.Default; private boolean latcom = true; - public final SideEffectQueue sideEffects = new SideEffectQueue(); + final SideEffectQueue sideEffects = new SideEffectQueue(); Game() { clientData = new ClientData(); @@ -565,7 +563,7 @@ public Position getMousePosition() { * and false if it was not. Returns false always if {@link Flag#UserInput} is disabled. * @see MouseButton */ - public boolean getMouseState(final MouseButton button) { + final public boolean getMouseState(final MouseButton button) { return gameData().getMouseState(button.id); } @@ -577,7 +575,7 @@ public boolean getMouseState(final MouseButton button) { * and false if it was not. Returns false always if {@link Flag#UserInput} is disabled. * @see Key */ - public boolean getKeyState(final Key key) { + final public boolean getKeyState(final Key key) { return gameData().getKeyState(key.id); } @@ -633,7 +631,7 @@ public void pingMinimap(final Position p) { * @return true if the given flag is enabled, false if the flag is disabled. * @see Flag */ - public boolean isFlagEnabled(final Flag flag) { + final public boolean isFlagEnabled(final Flag flag) { return gameData().getFlags(flag.id); } @@ -864,11 +862,11 @@ public String mapHash() { * @param walkY The y coordinate of the mini-tile, in mini-tile units (8 pixels). * @return true if the mini-tile is walkable and false if it is impassable for ground units. */ - public boolean isWalkable(final int walkX, final int walkY) { + final public boolean isWalkable(final int walkX, final int walkY) { return isWalkable(new WalkPosition(walkX, walkY)); } - public boolean isWalkable(final WalkPosition position) { + final public boolean isWalkable(final WalkPosition position) { if (!position.isValid(this)) { return false; } @@ -900,7 +898,7 @@ public int getGroundHeight(final TilePosition position) { return groundHeight[position.x][position.y]; } - public boolean isBuildable(final int tileX, final int tileY) { + final public boolean isBuildable(final int tileX, final int tileY) { return isBuildable(tileX, tileY, false); } @@ -916,19 +914,24 @@ public boolean isBuildable(final int tileX, final int tileY) { * If includeBuildings was provided, then it will return false if a structure is currently * occupying the tile. */ - public boolean isBuildable(final int tileX, final int tileY, final boolean includeBuildings) { - return isBuildable(new TilePosition(tileX, tileY), includeBuildings); + final public boolean isBuildable(final int tileX, final int tileY, final boolean includeBuildings) { + return isValidTile(tileX, tileY) && buildable[tileX][tileY] && (!includeBuildings || !gameData().isOccupied(tileX, tileY)); } - public boolean isBuildable(final TilePosition position) { + final public boolean isBuildable(final TilePosition position) { return isBuildable(position, false); } - public boolean isBuildable(final TilePosition position, final boolean includeBuildings) { - if (!position.isValid(this)) { - return false; - } - return buildable[position.x][position.y] && (!includeBuildings || !gameData().isOccupied(position.x, position.y)); + boolean isValidPosition(final int x, final int y) { + return x >= 0 && y >= 0 && x < mapPixelWidth && y < mapPixelHeight; + } + + boolean isValidTile(final int x, final int y) { + return x >= 0 && y >= 0 && x < mapWidth && y < mapHeight; + } + + final public boolean isBuildable(final TilePosition position, final boolean includeBuildings) { + return isBuildable(position.x, position.y, includeBuildings); } /** @@ -940,15 +943,12 @@ public boolean isBuildable(final TilePosition position, final boolean includeBui * the value is true. If the given tile is concealed by the fog of war, then this value will * be false. */ - public boolean isVisible(final int tileX, final int tileY) { - return isVisible(new TilePosition(tileX, tileY)); + final public boolean isVisible(final int tileX, final int tileY) { + return isValidTile(tileX, tileY) && gameData().isVisible(tileX, tileY); } - public boolean isVisible(final TilePosition position) { - if (!position.isValid(this)) { - return false; - } - return gameData().isVisible(position.x, position.y); + final public boolean isVisible(final TilePosition position) { + return isVisible(position.x, position.y); } /** @@ -961,15 +961,12 @@ public boolean isVisible(final TilePosition position) { * @return true if the player has explored the given tile position (partially revealed fog), false if the tile position was never explored (completely black fog). * @see #isVisible */ - public boolean isExplored(final int tileX, final int tileY) { - return isExplored(new TilePosition(tileX, tileY)); + final public boolean isExplored(final int tileX, final int tileY) { + return isValidTile(tileX, tileY) && gameData().isExplored(tileX, tileY); } - public boolean isExplored(final TilePosition position) { - if (!position.isValid(this)) { - return false; - } - return gameData().isExplored(position.x, position.y); + final public boolean isExplored(final TilePosition position) { + return isExplored(position.x, position.y); } /** @@ -979,18 +976,15 @@ public boolean isExplored(final TilePosition position) { * @param tileY The y tile coordinate to check. * @return true if the given tile has creep on it, false if the given tile does not have creep, or if it is concealed by the fog of war. */ - public boolean hasCreep(final int tileX, final int tileY) { - return hasCreep(new TilePosition(tileX, tileY)); + final public boolean hasCreep(final int tileX, final int tileY) { + return isValidTile(tileX, tileY) && gameData().getHasCreep(tileX, tileY); } - public boolean hasCreep(final TilePosition position) { - if (!position.isValid(this)) { - return false; - } - return gameData().getHasCreep(position.x, position.y); + final public boolean hasCreep(final TilePosition position) { + return hasCreep(position.x, position.y); } - public boolean hasPowerPrecise(final int x, final int y) { + final public boolean hasPowerPrecise(final int x, final int y) { return hasPowerPrecise(new Position(x, y)); } @@ -1003,41 +997,38 @@ public boolean hasPowerPrecise(final int x, final int y) { * @param unitType Checks if the given {@link UnitType} requires power or not. If ommitted, then it will assume that the position requires power for any unit type. * @return true if the type at the given position will have power, false if the type at the given position will be unpowered. */ - public boolean hasPowerPrecise(final int x, final int y, final UnitType unitType) { - return hasPowerPrecise(new Position(x, y), unitType); + final public boolean hasPowerPrecise(final int x, final int y, final UnitType unitType) { + return isValidPosition(x, y) && hasPower(x, y, unitType, self().getUnits().stream().filter(u -> u.getType() == Protoss_Pylon).collect(Collectors.toList())); } - public boolean hasPowerPrecise(final Position position) { - return hasPowerPrecise(position, UnitType.None); + final public boolean hasPowerPrecise(final Position position) { + return hasPowerPrecise(position.x, position.y, UnitType.None); } - public boolean hasPowerPrecise(final Position position, final UnitType unitType) { - if (!position.isValid(this)) { - return false; - } - return hasPower(position.x, position.y, unitType, self().getUnits().stream().filter(u -> u.getType() == Protoss_Pylon).collect(Collectors.toList())); + final public boolean hasPowerPrecise(final Position position, final UnitType unitType) { + return hasPowerPrecise(position.x, position.y, unitType); } - public boolean hasPower(final int tileX, final int tileY) { + final public boolean hasPower(final int tileX, final int tileY) { return hasPower(new TilePosition(tileX, tileY)); } - public boolean hasPower(final int tileX, final int tileY, final UnitType unitType) { + final public boolean hasPower(final int tileX, final int tileY, final UnitType unitType) { return hasPower(new TilePosition(tileX, tileY), unitType); } - public boolean hasPower(final TilePosition position) { + final public boolean hasPower(final TilePosition position) { return hasPower(position.x, position.y, UnitType.None); } - public boolean hasPower(final TilePosition position, final UnitType unitType) { + final public boolean hasPower(final TilePosition position, final UnitType unitType) { if (unitType.id >= 0 && unitType.id < UnitType.None.id) { return hasPowerPrecise(position.x * 32 + unitType.tileWidth() * 16, position.y * 32 + unitType.tileHeight() * 16, unitType); } return hasPowerPrecise(position.x * 32, position.y * 32, UnitType.None); } - public boolean hasPower(final int tileX, final int tileY, final int tileWidth, final int tileHeight) { + final public boolean hasPower(final int tileX, final int tileY, final int tileWidth, final int tileHeight) { return hasPower(tileX, tileY, tileWidth, tileHeight, UnitType.Unknown); } @@ -1050,23 +1041,23 @@ public boolean hasPower(final int tileX, final int tileY, final int tileWidth, f * @param unitType Checks if the given UnitType will be powered if placed at the given tile position. If omitted, then only the immediate tile position is checked for power, and the function will assume that the location requires power for any unit type. * @return true if the type at the given tile position will receive power, false if the type will be unpowered at the given tile position. */ - public boolean hasPower(final int tileX, final int tileY, final int tileWidth, final int tileHeight, final UnitType unitType) { + final public boolean hasPower(final int tileX, final int tileY, final int tileWidth, final int tileHeight, final UnitType unitType) { return hasPowerPrecise(tileX * 32 + tileWidth * 16, tileY * 32 + tileHeight * 16, unitType); } - public boolean hasPower(final TilePosition position, final int tileWidth, final int tileHeight) { + final public boolean hasPower(final TilePosition position, final int tileWidth, final int tileHeight) { return hasPower(position.x, position.y, tileWidth, tileHeight); } - public boolean hasPower(final TilePosition position, final int tileWidth, final int tileHeight, final UnitType unitType) { + final public boolean hasPower(final TilePosition position, final int tileWidth, final int tileHeight, final UnitType unitType) { return hasPower(position.x, position.y, tileWidth, tileHeight, unitType); } - public boolean canBuildHere(final TilePosition position, final UnitType type, final Unit builder) { + final public boolean canBuildHere(final TilePosition position, final UnitType type, final Unit builder) { return canBuildHere(position, type, builder, false); } - public boolean canBuildHere(final TilePosition position, final UnitType type) { + final public boolean canBuildHere(final TilePosition position, final UnitType type) { return canBuildHere(position, type, null); } @@ -1090,7 +1081,7 @@ public boolean canBuildHere(final TilePosition position, final UnitType type) { * @return true indicating that the structure can be placed at the given tile position, and * false if something may be obstructing the build location. */ - public boolean canBuildHere(final TilePosition position, final UnitType type, final Unit builder, final boolean checkExplored) { + final public boolean canBuildHere(final TilePosition position, final UnitType type, final Unit builder, final boolean checkExplored) { // lt = left top, rb = right bottom final TilePosition lt = builder != null && type.isAddon() ? position.add(new TilePosition(4, 1)) : // addon build offset @@ -1205,7 +1196,7 @@ public boolean canBuildHere(final TilePosition position, final UnitType type, fi return true; } - public boolean canMake(final UnitType type) { + final public boolean canMake(final UnitType type) { return canMake(type, null); } @@ -1220,7 +1211,7 @@ public boolean canMake(final UnitType type) { * only true if builder can make the type. Otherwise it will return false, indicating * that the unit type can not be made. */ - public boolean canMake(final UnitType type, final Unit builder) { + final public boolean canMake(final UnitType type, final Unit builder) { final Player pSelf = self(); // Error checking if (pSelf == null) { @@ -1330,11 +1321,11 @@ public boolean canMake(final UnitType type, final Unit builder) { (builder.getAddon() != null && builder.getAddon().getType() == addon); } - public boolean canResearch(final TechType type, final Unit unit) { + final public boolean canResearch(final TechType type, final Unit unit) { return canResearch(type, unit, true); } - public boolean canResearch(final TechType type) { + final public boolean canResearch(final TechType type) { return canResearch(type, null); } @@ -1350,7 +1341,7 @@ public boolean canResearch(final TechType type) { * only true if unit can research the type. Otherwise it will return false, indicating * that the technology can not be researched. */ - public boolean canResearch(final TechType type, final Unit unit, final boolean checkCanIssueCommandType) { + final public boolean canResearch(final TechType type, final Unit unit, final boolean checkCanIssueCommandType) { final Player self = self(); // Error checking if (self == null) { @@ -1394,11 +1385,11 @@ public boolean canResearch(final TechType type, final Unit unit, final boolean c } - public boolean canUpgrade(final UpgradeType type, final Unit unit) { + final public boolean canUpgrade(final UpgradeType type, final Unit unit) { return canUpgrade(type, unit, true); } - public boolean canUpgrade(final UpgradeType type) { + final public boolean canUpgrade(final UpgradeType type) { return canUpgrade(type, null); } @@ -1414,7 +1405,7 @@ public boolean canUpgrade(final UpgradeType type) { * only true if unit can upgrade the type. Otherwise it will return false, indicating * that the upgrade can not be upgraded. */ - public boolean canUpgrade(final UpgradeType type, final Unit unit, final boolean checkCanIssueCommandType) { + final public boolean canUpgrade(final UpgradeType type, final Unit unit, final boolean checkCanIssueCommandType) { final Player self = self(); if (self == null) { return false; @@ -1518,7 +1509,7 @@ public void sendTextEx(final boolean toAllies, final String string, final Text.. * * @return true if the client is in a game, and false if it is not. */ - public boolean isInGame() { + final public boolean isInGame() { return gameData().isInGame(); } @@ -1528,7 +1519,7 @@ public boolean isInGame() { * @return true if the client is in a multiplayer game, and false if it is a single player * game, a replay, or some other state. */ - public boolean isMultiplayer() { + final public boolean isMultiplayer() { return multiplayer; } @@ -1538,7 +1529,7 @@ public boolean isMultiplayer() { * * @return true if the client is in a multiplayer Battle.net game and false if it is not. */ - public boolean isBattleNet() { + final public boolean isBattleNet() { return battleNet; } @@ -1550,7 +1541,7 @@ public boolean isBattleNet() { * @see #pauseGame * @see #resumeGame */ - public boolean isPaused() { + final public boolean isPaused() { return gameData().isPaused(); } @@ -1559,7 +1550,7 @@ public boolean isPaused() { * * @return true if the client is watching a replay and false otherwise */ - public boolean isReplay() { + final public boolean isReplay() { return replay; } @@ -1632,7 +1623,7 @@ public void setLocalSpeed(final int speed) { * @return true if any one of the units in the List were capable of executing the * command, and false if none of the units were capable of executing the command. */ - public boolean issueCommand(final Collection units, final UnitCommand command) { + final public boolean issueCommand(final Collection units, final UnitCommand command) { return units.stream() .map(u -> u.issueCommand(command)) .reduce(false, (a, b) -> a | b); @@ -2156,7 +2147,7 @@ public int getRevision() { * * @return true if the BWAPI module is a DEBUG build, and false if it is a RELEASE build. */ - public boolean isDebug() { + final public boolean isDebug() { return debug; } @@ -2166,7 +2157,7 @@ public boolean isDebug() { * @return true if latency compensation is enabled, false if it is disabled. * @see #setLatCom */ - public boolean isLatComEnabled() { + final public boolean isLatComEnabled() { return latcom; } @@ -2235,7 +2226,7 @@ public void setFrameSkip(int frameSkip) { * allied players have eliminated their opponents. Otherwise, the game will only end if * no other players are remaining in the game. This value is true by default. */ - public boolean setAlliance(Player player, boolean allied, boolean alliedVictory) { + final public boolean setAlliance(Player player, boolean allied, boolean alliedVictory) { if (self() == null || isReplay() || player == null || player.equals(self())) { return false; } @@ -2244,11 +2235,11 @@ public boolean setAlliance(Player player, boolean allied, boolean alliedVictory) return true; } - public boolean setAlliance(Player player, boolean allied) { + final public boolean setAlliance(Player player, boolean allied) { return setAlliance(player, allied, true); } - public boolean setAlliance(Player player) { + final public boolean setAlliance(Player player) { return setAlliance(player, true); } @@ -2264,7 +2255,7 @@ public boolean setAlliance(Player player) { * of the target player will be shown, otherwise the target player will be hidden. This * value is true by default. */ - public boolean setVision(Player player, boolean enabled) { + final public boolean setVision(Player player, boolean enabled) { if (player == null) { return false; } @@ -2329,7 +2320,7 @@ public int getLastEventTime() { * does not have permission from the tournament module, failed to find the map specified, or received an invalid * parameter. */ - public boolean setMap(final String mapFileName) { + final public boolean setMap(final String mapFileName) { if (mapFileName == null || mapFileName.length() >= 260 || mapFileName.charAt(0) == 0) { return false; } @@ -2344,7 +2335,7 @@ public boolean setMap(final String mapFileName) { * @param reveal The state of the reveal all flag. If false, all fog of war will be enabled. If true, * then the fog of war will be revealed. It is true by default. */ - public boolean setRevealAll(boolean reveal) { + final public boolean setRevealAll(boolean reveal) { if (!isReplay()) { return false; } @@ -2352,7 +2343,7 @@ public boolean setRevealAll(boolean reveal) { return true; } - public boolean setRevealAll() { + final public boolean setRevealAll() { return setRevealAll(true); } @@ -2372,7 +2363,7 @@ public boolean setRevealAll() { * @return true if there is a path between the two positions, and false if there is not. * @see Unit#hasPath */ - public boolean hasPath(final Position source, final Position destination) { + final public boolean hasPath(final Position source, final Position destination) { if (source == null || destination == null) { return false; } From 024630bb04ba3bdbf4ae1d2cbf717d682a891bfa Mon Sep 17 00:00:00 2001 From: dgant Date: Thu, 24 Sep 2020 17:56:15 -0400 Subject: [PATCH 37/56] Made thread priority reduction dependent on async mode --- src/main/java/bwapi/BWClient.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index 3d4ab9cc..12f5eec6 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -81,7 +81,10 @@ public void startGame(BWClientConfiguration configuration) { // Use reduced priority to encourage Windows to give priority to StarCraft.exe/BWAPI. // If BWAPI doesn't get priority, it may not detect completion of a frame on our end in timely fashion. - Thread.currentThread().setPriority(4); + Thread.currentThread().setName("JBWAPI Client"); + if (configuration.async) { + Thread.currentThread().setPriority(4); + } if (client == null) { client = new Client(configuration); From a333f1524a618ca1d2995e57d4eeac4a0b491875 Mon Sep 17 00:00:00 2001 From: dgant Date: Fri, 25 Sep 2020 17:04:44 -0400 Subject: [PATCH 38/56] Added performance metric for frame duration which includes time to write to shared memory. --- src/main/java/bwapi/BWClient.java | 23 +++++++++++++----- src/main/java/bwapi/Client.java | 26 +++++++++++---------- src/main/java/bwapi/PerformanceMetrics.java | 21 +++++++++++++---- 3 files changed, 47 insertions(+), 23 deletions(-) diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index 12f5eec6..9886c1a9 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -8,6 +8,7 @@ * Client class to connect to the game with. */ public class BWClient { + private BWClientConfiguration configuration = new BWClientConfiguration(); private final BWEventListener eventListener; private BotWrapper botWrapper; private Client client; @@ -35,6 +36,13 @@ public PerformanceMetrics getPerformanceMetrics() { return performanceMetrics; } + /** + * @return The current configuration + */ + public BWClientConfiguration getConfiguration() { + return configuration; + } + /** * @return The number of frames between the one exposed to the bot and the most recent received by JBWAPI. * This tracks the size of the frame buffer except when the game is paused (which results in multiple frames arriving with the same count). @@ -73,10 +81,12 @@ public void startGame(boolean autoContinue) { /** * Start the game. * - * @param configuration Settings for playing games with this client. + * @param gameConfiguration Settings for playing games with this client. */ - public void startGame(BWClientConfiguration configuration) { - configuration.validate(); + public void startGame(BWClientConfiguration gameConfiguration) { + gameConfiguration.validate(); + this.configuration = gameConfiguration; + this.performanceMetrics = new PerformanceMetrics(configuration); botWrapper = new BotWrapper(configuration, eventListener); // Use reduced priority to encourage Windows to give priority to StarCraft.exe/BWAPI. @@ -87,7 +97,7 @@ public void startGame(BWClientConfiguration configuration) { } if (client == null) { - client = new Client(configuration); + client = new Client(this); } client.reconnect(); @@ -107,7 +117,8 @@ public void startGame(BWClientConfiguration configuration) { boolean timeFrame = liveGameData.getFrameCount() > 0 || ! configuration.unlimitedFrameZero; long ticksBefore = Kernel32.INSTANCE.GetTickCount(); - performanceMetrics.totalFrameDuration.timeIf( + performanceMetrics.endToEndFrameDuration.startTiming(); + performanceMetrics.jbwapiFrameDuration.timeIf( timeFrame, () -> { botWrapper.onFrame(); @@ -116,7 +127,7 @@ public void startGame(BWClientConfiguration configuration) { long ticksAfter = Kernel32.INSTANCE.GetTickCount(); if (timeFrame) { long deltaTicks = ticksAfter - ticksBefore; - long deltaMillis = (long) performanceMetrics.totalFrameDuration.runningTotal.last; + long deltaMillis = (long) performanceMetrics.jbwapiFrameDuration.runningTotal.last; long delta = deltaMillis - deltaTicks; if (Math.abs(delta) > 1000) { System.out.println("Got weird tick delta: " + ticksAfter + ", " + ticksBefore + ", " + deltaTicks + ", " + deltaMillis + ", " + delta); diff --git a/src/main/java/bwapi/Client.java b/src/main/java/bwapi/Client.java index c925dc2b..ed0ab930 100644 --- a/src/main/java/bwapi/Client.java +++ b/src/main/java/bwapi/Client.java @@ -45,15 +45,14 @@ interface MappingKernel extends Kernel32 { private static final int SUPPORTED_BWAPI_VERSION = 10003; private ClientData clientData; + private BWClient bwClient; private boolean connected = false; private RandomAccessFile pipeObjectHandle = null; private ByteBuffer gameTableFileHandle = null; private ByteBuffer mapFileHandle = null; - private BWClientConfiguration configuration = new BWClientConfiguration(); - - Client(BWClientConfiguration configuration) { - this.configuration = configuration; + Client(BWClient bwClient) { + this.bwClient = bwClient; } /** @@ -83,7 +82,7 @@ void reconnect() { } private void disconnect() { - if (configuration.debugConnection) { + if (bwClient.getConfiguration().debugConnection) { System.err.print("Disconnect called by: "); System.err.println(Thread.currentThread().getStackTrace()[2]); } @@ -134,7 +133,7 @@ boolean connect() { } catch (Exception e) { System.err.println("Unable to map Game table."); - if (configuration.debugConnection) { + if (bwClient.getConfiguration().debugConnection) { e.printStackTrace(); } return false; @@ -168,7 +167,7 @@ boolean connect() { } catch (Exception e) { System.err.println("Unable to open communications pipe: " + communicationPipe); - if (configuration.debugConnection) { + if (bwClient.getConfiguration().debugConnection) { e.printStackTrace(); } gameTableFileHandle = null; @@ -184,7 +183,7 @@ boolean connect() { } catch (Exception e) { System.err.println("Unable to open shared memory mapping: " + sharedMemoryName); - if (configuration.debugConnection) { + if (bwClient.getConfiguration().debugConnection) { e.printStackTrace(); } pipeObjectHandle = null; @@ -197,7 +196,7 @@ boolean connect() { } catch (Exception e) { System.err.println("Unable to map game data."); - if (configuration.debugConnection) { + if (bwClient.getConfiguration().debugConnection) { e.printStackTrace(); } return false; @@ -218,7 +217,7 @@ boolean connect() { } catch (Exception e) { System.err.println("Unable to read pipe object."); - if (configuration.debugConnection) { + if (bwClient.getConfiguration().debugConnection) { e.printStackTrace(); } disconnect(); @@ -232,18 +231,21 @@ boolean connect() { } void update() { + // Indicate that we are done with the current frame byte code = 1; try { pipeObjectHandle.writeByte(code); + bwClient.getPerformanceMetrics().endToEndFrameDuration.stopTiming(); } catch (Exception e) { System.err.println("failed, disconnecting"); - if (configuration.debugConnection) { + if (bwClient.getConfiguration().debugConnection) { e.printStackTrace(); } disconnect(); return; } + // Wait for BWAPI to indicate that a new frame is ready while (code != 2) { try { code = pipeObjectHandle.readByte(); @@ -257,7 +259,7 @@ void update() { } catch (Exception e) { System.err.println("failed, disconnecting"); - if (configuration.debugConnection) { + if (bwClient.getConfiguration().debugConnection) { e.printStackTrace(); } disconnect(); diff --git a/src/main/java/bwapi/PerformanceMetrics.java b/src/main/java/bwapi/PerformanceMetrics.java index ffd00f75..57c6ae80 100644 --- a/src/main/java/bwapi/PerformanceMetrics.java +++ b/src/main/java/bwapi/PerformanceMetrics.java @@ -6,11 +6,20 @@ public class PerformanceMetrics { /** - * Total duration of the frame from JBWAPI's perspective. - * Likely to be at least a little bit of an undercount, + * Total duration of the frame from JBWAPI's perspective, + * exclusive of time modifying shared memory to indicate frame completion. + * Likely to be at least a little bit of an undercount from the perspective of BWAPI, * given that the tournament module is timing a superset of JBWAPI's execution time. */ - PerformanceMetric totalFrameDuration; + PerformanceMetric jbwapiFrameDuration; + + /** + * Total duration of the frame from JBWAPI's perspective, + * inclusive of time modifying shared memory to indicate frame completion. + * Likely to be at least a little bit of an undercount from the perspective of BWAPI, + * given that the tournament module is timing a superset of JBWAPI's execution time. + */ + PerformanceMetric endToEndFrameDuration; /** * Time spent copying game data from system pipe shared memory to a frame buffer. @@ -92,7 +101,8 @@ public class PerformanceMetrics { } public void reset() { - totalFrameDuration = new PerformanceMetric("JBWAPI frame duration", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); + jbwapiFrameDuration = new PerformanceMetric("JBWAPI frame duration", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); + endToEndFrameDuration = new PerformanceMetric("End-to-end frame duration", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); copyingToBuffer = new PerformanceMetric("Time copying to buffer", 5, 10, 15, 20, 25, 30); intentionallyBlocking = new PerformanceMetric("Blocking with full buffer", 0); frameBufferSize = new PerformanceMetric("Frames buffered", 0, 1); @@ -111,7 +121,8 @@ public void reset() { @Override public String toString() { return "Performance metrics:" - + "\n" + totalFrameDuration.toString() + + "\n" + jbwapiFrameDuration.toString() + + "\n" + endToEndFrameDuration.toString() + "\n" + copyingToBuffer.toString() + "\n" + intentionallyBlocking.toString() + "\n" + frameBufferSize.toString() From ab695addf01f18fe69e8848cf1bea292c6efa6fe Mon Sep 17 00:00:00 2001 From: dgant Date: Fri, 25 Sep 2020 22:07:04 -0400 Subject: [PATCH 39/56] Assembled complete suite of frame-duration and BWAPI communication performance metrics --- src/main/java/bwapi/BWClient.java | 33 +++--- src/main/java/bwapi/Client.java | 40 ++++--- src/main/java/bwapi/PerformanceMetric.java | 7 +- src/main/java/bwapi/PerformanceMetrics.java | 116 +++++++++++--------- 4 files changed, 111 insertions(+), 85 deletions(-) diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index 9886c1a9..db228069 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -43,6 +43,13 @@ public BWClientConfiguration getConfiguration() { return configuration; } + /** + * @return Whether the current frame should be subject to timing. + */ + boolean doTime() { + return ! configuration.unlimitedFrameZero || (client.isConnected() && client.clientData().gameData().getFrameCount() > 0); + } + /** * @return The number of frames between the one exposed to the bot and the most recent received by JBWAPI. * This tracks the size of the frame buffer except when the game is paused (which results in multiple frames arriving with the same count). @@ -114,32 +121,26 @@ public void startGame(BWClientConfiguration gameConfiguration) { } } while (liveGameData.isInGame()) { - boolean timeFrame = liveGameData.getFrameCount() > 0 || ! configuration.unlimitedFrameZero; - long ticksBefore = Kernel32.INSTANCE.GetTickCount(); - performanceMetrics.endToEndFrameDuration.startTiming(); - performanceMetrics.jbwapiFrameDuration.timeIf( - timeFrame, - () -> { - botWrapper.onFrame(); - performanceMetrics.flushSideEffects.time(() -> getGame().sideEffects.flushTo(liveGameData)); - }); + + botWrapper.onFrame(); + performanceMetrics.flushSideEffects.time(() -> getGame().sideEffects.flushTo(liveGameData)); + performanceMetrics.frameDurationReceiveToSend.stopTiming(); long ticksAfter = Kernel32.INSTANCE.GetTickCount(); - if (timeFrame) { + + // Measure differential between JVM timer and WinAPI's GetTickCount, used by BWAPI 4.4 and below + if (doTime()) { long deltaTicks = ticksAfter - ticksBefore; - long deltaMillis = (long) performanceMetrics.jbwapiFrameDuration.runningTotal.last; + long deltaMillis = (long) performanceMetrics.frameDurationReceiveToSend.runningTotal.last; long delta = deltaMillis - deltaTicks; - if (Math.abs(delta) > 1000) { - System.out.println("Got weird tick delta: " + ticksAfter + ", " + ticksBefore + ", " + deltaTicks + ", " + deltaMillis + ", " + delta); - performanceMetrics.weirdTimeDelta.record(1); - } else if (delta > 0) { + if (delta > 0) { performanceMetrics.positiveTimeDelta.record(delta); } else if (delta < 0) { performanceMetrics.negativeTimeDelta.record(-delta); } } - performanceMetrics.bwapiResponse.time(client::update); + client.update(); if (!client.isConnected()) { System.out.println("Reconnecting..."); client.reconnect(); diff --git a/src/main/java/bwapi/Client.java b/src/main/java/bwapi/Client.java index ed0ab930..59be1859 100644 --- a/src/main/java/bwapi/Client.java +++ b/src/main/java/bwapi/Client.java @@ -231,11 +231,13 @@ boolean connect() { } void update() { - // Indicate that we are done with the current frame - byte code = 1; + + // Tell BWAPI that we are done with the current frame + bwClient.getPerformanceMetrics().frameDurationReceiveToSend.stopTiming(); + bwClient.getPerformanceMetrics().communicationSendToReceive.startTiming(); + bwClient.getPerformanceMetrics().communicationSendToSent.startTiming(); try { - pipeObjectHandle.writeByte(code); - bwClient.getPerformanceMetrics().endToEndFrameDuration.stopTiming(); + pipeObjectHandle.writeByte(1); // The "frame done" signal } catch (Exception e) { System.err.println("failed, disconnecting"); @@ -245,27 +247,33 @@ void update() { disconnect(); return; } - // Wait for BWAPI to indicate that a new frame is ready - while (code != 2) { + bwClient.getPerformanceMetrics().communicationSendToSent.stopTiming(); + bwClient.getPerformanceMetrics().frameDurationReceiveToSent.stopTiming(); + + // Listen for BWAPI to indicate that a new frame is ready + bwClient.getPerformanceMetrics().communicationListenToReceive.startTiming(); + byte code = 1; + while (code != 2) { // The "frame ready" signal try { code = pipeObjectHandle.readByte(); - /* - if (code != 2) { - try { - Thread.sleep(0, 1); - } catch (InterruptedException ignored) {} - } - */ - } - catch (Exception e) { + } catch (Exception e) { System.err.println("failed, disconnecting"); if (bwClient.getConfiguration().debugConnection) { e.printStackTrace(); } disconnect(); - return; + break; } } + bwClient.getPerformanceMetrics().communicationListenToReceive.stopTiming(); + bwClient.getPerformanceMetrics().communicationSendToReceive.stopTiming(); + + if (bwClient.doTime()) { + bwClient.getPerformanceMetrics().frameDurationReceiveToSend.startTiming(); + bwClient.getPerformanceMetrics().frameDurationReceiveToSent.startTiming(); + bwClient.getPerformanceMetrics().frameDurationReceiveToReceive.stopTiming(); + bwClient.getPerformanceMetrics().frameDurationReceiveToReceive.startTiming(); + } } private void sleep(final int millis) { diff --git a/src/main/java/bwapi/PerformanceMetric.java b/src/main/java/bwapi/PerformanceMetric.java index a4aac0ee..5c5e24c0 100644 --- a/src/main/java/bwapi/PerformanceMetric.java +++ b/src/main/java/bwapi/PerformanceMetric.java @@ -42,17 +42,18 @@ public String toString() { } private final String name; - public int interrupted = 0; private long timeStarted = 0; + public int interrupted = 0; RunningTotal runningTotal = new RunningTotal(); private ArrayList thresholds = new ArrayList<>(); - PerformanceMetric(String name, double... thresholds) { + PerformanceMetric(PerformanceMetrics metrics, String name, double... thresholds) { this.name = name; for (double threshold : thresholds) { this.thresholds.add(new Threshold(threshold)); } + metrics.addMetric(this); } /** @@ -121,7 +122,7 @@ public String toString() { } DecimalFormat formatter = new DecimalFormat("###,###.#"); String output = name - + ": " + + ":\n" + formatter.format(runningTotal.samples) + " samples averaging " + formatter.format(runningTotal.mean) diff --git a/src/main/java/bwapi/PerformanceMetrics.java b/src/main/java/bwapi/PerformanceMetrics.java index 57c6ae80..6bef1e33 100644 --- a/src/main/java/bwapi/PerformanceMetrics.java +++ b/src/main/java/bwapi/PerformanceMetrics.java @@ -1,25 +1,33 @@ package bwapi; +import java.util.ArrayList; + /** * Collects various performance metrics. */ public class PerformanceMetrics { /** - * Total duration of the frame from JBWAPI's perspective, - * exclusive of time modifying shared memory to indicate frame completion. - * Likely to be at least a little bit of an undercount from the perspective of BWAPI, - * given that the tournament module is timing a superset of JBWAPI's execution time. + * Duration of the frame cycle steps measured by BWAPI, + * from receiving a frame to BWAPI + * to sending commands back + * *exclusive* of the time spent sending commands back. + */ + PerformanceMetric frameDurationReceiveToSend; + + /** + * Duration of the frame cycle steps measured by BWAPI, + * from receiving a frame to BWAPI + * to sending commands back + * *inclusive* of the time spent sending commands back. */ - PerformanceMetric jbwapiFrameDuration; + PerformanceMetric frameDurationReceiveToSent; /** - * Total duration of the frame from JBWAPI's perspective, - * inclusive of time modifying shared memory to indicate frame completion. - * Likely to be at least a little bit of an undercount from the perspective of BWAPI, - * given that the tournament module is timing a superset of JBWAPI's execution time. + * Duration of a frame cycle originating at + * the time when JBWAPI observes a new frame in shared memory. */ - PerformanceMetric endToEndFrameDuration; + PerformanceMetric frameDurationReceiveToReceive; /** * Time spent copying game data from system pipe shared memory to a frame buffer. @@ -56,9 +64,23 @@ public class PerformanceMetrics { PerformanceMetric botResponse; /** - * Time spent waiting for a response from BWAPI; is likely reflective of the performance of any opponent bots. + * Time spent waiting for a response from BWAPI, + * inclusive of the time spent sending the signal to BWAPI + * and the time spent waiting for and receiving it. */ - PerformanceMetric bwapiResponse; + PerformanceMetric communicationSendToReceive; + + /** + * Time spent sending the "frame complete" signal to BWAPI. + * Significant durations would indicate something blocking writes to shared memory. + */ + PerformanceMetric communicationSendToSent; + + /** + * Time spent waiting for a "frame ready" signal from BWAPI. + * This time likely additional response time spent by other bots and StarCraft itself. + */ + PerformanceMetric communicationListenToReceive; /** * Time bot spends idle. @@ -79,21 +101,17 @@ public class PerformanceMetrics { PerformanceMetric excessSleep; /** - * Instances of System.nanoTime() measuring a longer frame duration with respect to WinAPI's GetTickCount. + * Instances of System.nanoTime() measuring a longer frame duration with respect to WinAPI's GetTickCount which BWAPI uses up to 4.4. */ PerformanceMetric positiveTimeDelta; /** - * Instances of System.nanoTime() measuring a shorter frame duration with respect to WinAPI's GetTickCount. + * Instances of System.nanoTime() measuring a shorter frame duration with respect to WinAPI's GetTickCount which BWAPI uses up to 4.4. */ PerformanceMetric negativeTimeDelta; - /** - * When Kernel32.INSTANCE.GetTickCount() returns at least one inexplicable value - */ - PerformanceMetric weirdTimeDelta; - private BWClientConfiguration configuration; + private ArrayList performanceMetrics = new ArrayList<>(); PerformanceMetrics(BWClientConfiguration configuration) { this.configuration = configuration; @@ -101,40 +119,38 @@ public class PerformanceMetrics { } public void reset() { - jbwapiFrameDuration = new PerformanceMetric("JBWAPI frame duration", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); - endToEndFrameDuration = new PerformanceMetric("End-to-end frame duration", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); - copyingToBuffer = new PerformanceMetric("Time copying to buffer", 5, 10, 15, 20, 25, 30); - intentionallyBlocking = new PerformanceMetric("Blocking with full buffer", 0); - frameBufferSize = new PerformanceMetric("Frames buffered", 0, 1); - framesBehind = new PerformanceMetric("Frames behind real-time", 0, 1); - flushSideEffects = new PerformanceMetric("Flushing side effects", 1, 3, 5); - botResponse = new PerformanceMetric("Bot event handlers", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); - bwapiResponse = new PerformanceMetric("Responses from BWAPI", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); - botIdle = new PerformanceMetric("Bot idle", Long.MAX_VALUE); - clientIdle = new PerformanceMetric("Client idling", configuration.maxFrameDurationMs); - excessSleep = new PerformanceMetric("Excess sleep", 1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); - positiveTimeDelta = new PerformanceMetric("Positive timer delta", 1, 2, 3, 4, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); - negativeTimeDelta = new PerformanceMetric("Negative timer delta", 1, 2, 3, 4, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); - weirdTimeDelta = new PerformanceMetric("Weird timer delta"); + performanceMetrics.clear(); + frameDurationReceiveToSend = new PerformanceMetric(this, "Frame duration: Receiving 'frame ready' -> before sending 'frame done'", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); + frameDurationReceiveToSent = new PerformanceMetric(this, "Frame duration: Receiving 'frame ready' -> after sending 'frame done'", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); + frameDurationReceiveToReceive = new PerformanceMetric(this, "Frame duration: From BWAPI receive to BWAPI receive", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); + communicationSendToReceive = new PerformanceMetric(this, "BWAPI duration: Before sending 'frame done' -> After receiving 'frame ready'", 1, 3, 5, 10, 15, 20, 30); + communicationSendToSent = new PerformanceMetric(this, "BWAPI duration: Before sending 'frame done' -> After sending 'frame done'", 1, 3, 5, 10, 15, 20, 30); + communicationListenToReceive = new PerformanceMetric(this, "BWAPI duration: Before listening for 'frame ready' -> After receiving 'frame ready'", 1, 3, 5, 10, 15, 20, 30); + copyingToBuffer = new PerformanceMetric(this, "Copying frame to buffer", 5, 10, 15, 20, 25, 30); + intentionallyBlocking = new PerformanceMetric(this, "Time holding frame until buffer frees capacity", 0); + frameBufferSize = new PerformanceMetric(this, "Frames already buffered when enqueuing a new frame", 0, 1); + framesBehind = new PerformanceMetric(this, "Frames behind real-time when handling events", 0, 1); + flushSideEffects = new PerformanceMetric(this, "Time flushing side effects", 1, 3, 5); + botResponse = new PerformanceMetric(this, "Duration of bot event handlers", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); + botIdle = new PerformanceMetric(this, "Time bot spent idle", Long.MAX_VALUE); + clientIdle = new PerformanceMetric(this, "Time client spent waiting for bot", configuration.maxFrameDurationMs); + excessSleep = new PerformanceMetric(this, "Excess duration of client sleep", 1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); + positiveTimeDelta = new PerformanceMetric(this, "Positive timer discrepancy compared to BWAPI", 1, 2, 3, 4, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); + negativeTimeDelta = new PerformanceMetric(this, "Negative timer discrepancy compared to BWAPI", 1, 2, 3, 4, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); + } + + void addMetric(PerformanceMetric performanceMetric) { + performanceMetrics.add(performanceMetric); } @Override public String toString() { - return "Performance metrics:" - + "\n" + jbwapiFrameDuration.toString() - + "\n" + endToEndFrameDuration.toString() - + "\n" + copyingToBuffer.toString() - + "\n" + intentionallyBlocking.toString() - + "\n" + frameBufferSize.toString() - + "\n" + framesBehind.toString() - + "\n" + flushSideEffects.toString() - + "\n" + botResponse.toString() - + "\n" + bwapiResponse.toString() - + "\n" + botIdle.toString() - + "\n" + clientIdle.toString() - + "\n" + excessSleep.toString() - + "\n" + positiveTimeDelta.toString() - + "\n" + negativeTimeDelta.toString() - + "\n" + weirdTimeDelta.toString(); + StringBuilder outputBuilder = new StringBuilder(); + outputBuilder.append("Performance metrics:"); + performanceMetrics.forEach(metric -> { + outputBuilder.append("\n"); + outputBuilder.append(metric.toString()); + }); + return outputBuilder.toString(); } } From ebae46c5079aa75f512d6e2503e1065d5bdb84b3 Mon Sep 17 00:00:00 2001 From: dgant Date: Sat, 26 Sep 2020 01:06:29 -0400 Subject: [PATCH 40/56] Fixed typo in performance logging --- src/main/java/bwapi/PerformanceMetric.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/bwapi/PerformanceMetric.java b/src/main/java/bwapi/PerformanceMetric.java index 5c5e24c0..1973b81e 100644 --- a/src/main/java/bwapi/PerformanceMetric.java +++ b/src/main/java/bwapi/PerformanceMetric.java @@ -130,9 +130,7 @@ public String toString() { + formatter.format(runningTotal.min) + " - " + formatter.format(runningTotal.max) - + "] over " - + runningTotal.samples - + " samples. "; + + "]"; for (Threshold threshold : thresholds) { output += threshold.toString(); } From b40e612f9ef7ead1d2f8880ca48b94f455046247 Mon Sep 17 00:00:00 2001 From: dgant Date: Sat, 26 Sep 2020 13:38:51 -0400 Subject: [PATCH 41/56] Added performance metrics related to event count to diagnose whether 4.4 tournament module bugs are ongoing --- src/main/java/bwapi/BWClient.java | 4 +- src/main/java/bwapi/Client.java | 53 ++++++++++++++------- src/main/java/bwapi/PerformanceMetric.java | 16 ++++++- src/main/java/bwapi/PerformanceMetrics.java | 51 ++++++++++++++++++-- 4 files changed, 100 insertions(+), 24 deletions(-) diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index db228069..881be798 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -114,7 +114,7 @@ public void startGame(BWClientConfiguration gameConfiguration) { if (!client.isConnected()) { return; } - client.update(); + client.sendFrameReceiveFrame(); if (liveGameData.isInGame()) { performanceMetrics = new PerformanceMetrics(configuration); botWrapper.startNewGame(client.mapFile(), performanceMetrics); @@ -140,7 +140,7 @@ public void startGame(BWClientConfiguration gameConfiguration) { } } - client.update(); + client.sendFrameReceiveFrame(); if (!client.isConnected()) { System.out.println("Reconnecting..."); client.reconnect(); diff --git a/src/main/java/bwapi/Client.java b/src/main/java/bwapi/Client.java index 59be1859..0fd6bb0c 100644 --- a/src/main/java/bwapi/Client.java +++ b/src/main/java/bwapi/Client.java @@ -230,14 +230,18 @@ boolean connect() { return true; } - void update() { + void sendFrameReceiveFrame() { + final PerformanceMetrics metrics = bwClient.getPerformanceMetrics(); // Tell BWAPI that we are done with the current frame - bwClient.getPerformanceMetrics().frameDurationReceiveToSend.stopTiming(); - bwClient.getPerformanceMetrics().communicationSendToReceive.startTiming(); - bwClient.getPerformanceMetrics().communicationSendToSent.startTiming(); + metrics.frameDurationReceiveToSend.stopTiming(); + if (bwClient.doTime()) { + metrics.communicationSendToReceive.startTiming(); + metrics.communicationSendToSent.startTiming(); + } try { - pipeObjectHandle.writeByte(1); // The "frame done" signal + // 1 is the "frame done" signal to BWAPI + pipeObjectHandle.writeByte(1); } catch (Exception e) { System.err.println("failed, disconnecting"); @@ -247,15 +251,25 @@ void update() { disconnect(); return; } - bwClient.getPerformanceMetrics().communicationSendToSent.stopTiming(); - bwClient.getPerformanceMetrics().frameDurationReceiveToSent.stopTiming(); + metrics.communicationSendToSent.stopTiming(); + metrics.frameDurationReceiveToSent.stopTiming(); + metrics.frameDurationReceiveToSentGTC.stopTiming(); + if (bwClient.doTime()) { + final int eventCount = clientData.gameData().getEventCount(); + metrics.numberOfEvents.record(eventCount); + metrics.numberOfEventsTimesDurationReceiveToSent.record(eventCount * metrics.frameDurationReceiveToSent.runningTotal.last); + metrics.numberOfEventsTimesDurationReceiveToSentGTC.record(eventCount * metrics.frameDurationReceiveToSentGTC.runningTotal.last); + } // Listen for BWAPI to indicate that a new frame is ready - bwClient.getPerformanceMetrics().communicationListenToReceive.startTiming(); - byte code = 1; - while (code != 2) { // The "frame ready" signal + if (bwClient.doTime()) { + metrics.communicationListenToReceive.startTiming(); + } + boolean frameReady = false; + while (!frameReady) { try { - code = pipeObjectHandle.readByte(); + // 2 is the "frame ready" signal from BWAPI + frameReady = pipeObjectHandle.readByte() == 2; } catch (Exception e) { System.err.println("failed, disconnecting"); if (bwClient.getConfiguration().debugConnection) { @@ -265,14 +279,19 @@ void update() { break; } } - bwClient.getPerformanceMetrics().communicationListenToReceive.stopTiming(); - bwClient.getPerformanceMetrics().communicationSendToReceive.stopTiming(); + metrics.communicationListenToReceive.stopTiming(); + metrics.communicationSendToReceive.stopTiming(); if (bwClient.doTime()) { - bwClient.getPerformanceMetrics().frameDurationReceiveToSend.startTiming(); - bwClient.getPerformanceMetrics().frameDurationReceiveToSent.startTiming(); - bwClient.getPerformanceMetrics().frameDurationReceiveToReceive.stopTiming(); - bwClient.getPerformanceMetrics().frameDurationReceiveToReceive.startTiming(); + metrics.frameDurationReceiveToSend.startTiming(); + metrics.frameDurationReceiveToSent.startTiming(); + metrics.frameDurationReceiveToSentGTC.startTiming(); + } + metrics.frameDurationReceiveToReceive.stopTiming(); + metrics.frameDurationReceiveToReceiveGTC.stopTiming(); + if (bwClient.doTime()) { + metrics.frameDurationReceiveToReceive.startTiming(); + metrics.frameDurationReceiveToReceiveGTC.startTiming(); } } diff --git a/src/main/java/bwapi/PerformanceMetric.java b/src/main/java/bwapi/PerformanceMetric.java index 1973b81e..c048e5e5 100644 --- a/src/main/java/bwapi/PerformanceMetric.java +++ b/src/main/java/bwapi/PerformanceMetric.java @@ -1,5 +1,7 @@ package bwapi; +import com.sun.jna.platform.win32.Kernel32; + import java.text.DecimalFormat; import java.util.ArrayList; @@ -44,6 +46,7 @@ public String toString() { private final String name; private long timeStarted = 0; public int interrupted = 0; + private boolean usingGetTickCount = false; RunningTotal runningTotal = new RunningTotal(); private ArrayList thresholds = new ArrayList<>(); @@ -79,6 +82,15 @@ void timeIf(boolean condition, Runnable runnable) { } } + PerformanceMetric useGetTickCount() { + usingGetTickCount = true; + return this; + } + + long getNanoseconds() { + return usingGetTickCount ? Kernel32.INSTANCE.GetTickCount() * 1000000L: System.nanoTime(); + } + /** * Manually start timing. * The next call to stopTiming() will record the duration in fractional milliseconds. @@ -87,7 +99,7 @@ void startTiming() { if (timeStarted > 0) { ++interrupted; } - timeStarted = System.nanoTime(); + timeStarted = getNanoseconds(); } @@ -98,7 +110,7 @@ void startTiming() { void stopTiming() { if (timeStarted <= 0) return; // Use nanosecond resolution timer, but record in units of milliseconds. - long timeEnded = System.nanoTime(); + long timeEnded = getNanoseconds(); long timeDiff = timeEnded - timeStarted; timeStarted = 0; record(timeDiff / 1000000d); diff --git a/src/main/java/bwapi/PerformanceMetrics.java b/src/main/java/bwapi/PerformanceMetrics.java index 6bef1e33..dad61f7a 100644 --- a/src/main/java/bwapi/PerformanceMetrics.java +++ b/src/main/java/bwapi/PerformanceMetrics.java @@ -23,12 +23,26 @@ public class PerformanceMetrics { */ PerformanceMetric frameDurationReceiveToSent; + /** + * Duration of a frame cycle originating at + * the time when JBWAPI observes a new frame in shared memory. + * Uses GetTickCount() instead of System.nanoRime() + */ + PerformanceMetric frameDurationReceiveToSentGTC; + /** * Duration of a frame cycle originating at * the time when JBWAPI observes a new frame in shared memory. */ PerformanceMetric frameDurationReceiveToReceive; + /** + * Duration of a frame cycle originating at + * the time when JBWAPI observes a new frame in shared memory. + * Uses GetTickCount() instead of System.nanoRime() + */ + PerformanceMetric frameDurationReceiveToReceiveGTC; + /** * Time spent copying game data from system pipe shared memory to a frame buffer. * Applicable only in asynchronous mode. @@ -110,6 +124,31 @@ public class PerformanceMetrics { */ PerformanceMetric negativeTimeDelta; + /** + * The number of events sent by BWAPI each frame. + * Might help diagnosing issues related to https://github.com/bwapi/bwapi/issues/860 and https://github.com/davechurchill/StarcraftAITournamentManager/issues/42 + */ + PerformanceMetric numberOfEvents; + + /** + * The number of events sent by BWAPI each frame, + * multiplied by the duration of time spent on that frame (receive-to-sent). + * Might help diagnosing issues related to: + * - https://github.com/bwapi/bwapi/issues/860 + * - https://github.com/davechurchill/StarcraftAITournamentManager/issues/42 + */ + PerformanceMetric numberOfEventsTimesDurationReceiveToSent; + + /** + * The number of events sent by BWAPI each frame, + * multiplied by the duration of time spent on that frame (receive-to-sent), + * and using GetTickCount() instead of System.nanoTime(). + * Might help diagnosing issues related to: + * - https://github.com/bwapi/bwapi/issues/860 + * - https://github.com/davechurchill/StarcraftAITournamentManager/issues/42 + */ + PerformanceMetric numberOfEventsTimesDurationReceiveToSentGTC; + private BWClientConfiguration configuration; private ArrayList performanceMetrics = new ArrayList<>(); @@ -120,9 +159,11 @@ public class PerformanceMetrics { public void reset() { performanceMetrics.clear(); - frameDurationReceiveToSend = new PerformanceMetric(this, "Frame duration: Receiving 'frame ready' -> before sending 'frame done'", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); - frameDurationReceiveToSent = new PerformanceMetric(this, "Frame duration: Receiving 'frame ready' -> after sending 'frame done'", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); - frameDurationReceiveToReceive = new PerformanceMetric(this, "Frame duration: From BWAPI receive to BWAPI receive", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); + frameDurationReceiveToSend = new PerformanceMetric(this, "Frame duration: After receiving 'frame ready' -> before sending 'frame done'", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); + frameDurationReceiveToSent = new PerformanceMetric(this, "Frame duration: After receiving 'frame ready' -> after sending 'frame done'", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); + frameDurationReceiveToSentGTC = new PerformanceMetric(this, "Frame duration: After receiving 'frame ready' -> after sending 'frame done' (Using GetTickCount())", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85).useGetTickCount(); + frameDurationReceiveToReceive = new PerformanceMetric(this, "Frame duration: After receiving 'frame ready' -> receiving next 'frame ready'", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); + frameDurationReceiveToReceiveGTC = new PerformanceMetric(this, "Frame duration: After receiving 'frame ready' -> receiving next 'frame ready' (Using GetTickCount())", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85).useGetTickCount(); communicationSendToReceive = new PerformanceMetric(this, "BWAPI duration: Before sending 'frame done' -> After receiving 'frame ready'", 1, 3, 5, 10, 15, 20, 30); communicationSendToSent = new PerformanceMetric(this, "BWAPI duration: Before sending 'frame done' -> After sending 'frame done'", 1, 3, 5, 10, 15, 20, 30); communicationListenToReceive = new PerformanceMetric(this, "BWAPI duration: Before listening for 'frame ready' -> After receiving 'frame ready'", 1, 3, 5, 10, 15, 20, 30); @@ -137,6 +178,9 @@ public void reset() { excessSleep = new PerformanceMetric(this, "Excess duration of client sleep", 1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); positiveTimeDelta = new PerformanceMetric(this, "Positive timer discrepancy compared to BWAPI", 1, 2, 3, 4, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); negativeTimeDelta = new PerformanceMetric(this, "Negative timer discrepancy compared to BWAPI", 1, 2, 3, 4, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); + numberOfEvents = new PerformanceMetric(this, "Number of events received from BWAPI", 1, 2, 3, 4, 5, 6, 8, 10, 15, 20); + numberOfEventsTimesDurationReceiveToSent = new PerformanceMetric(this, "Number of events received from BWAPI, multiplied by the receive-to-sent duration of that frame", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); + numberOfEventsTimesDurationReceiveToSentGTC = new PerformanceMetric(this, "Number of events received from BWAPI, multiplied by the receive-to-sent duration of that frame (Using GetTickCount())", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); } void addMetric(PerformanceMetric performanceMetric) { @@ -154,3 +198,4 @@ public String toString() { return outputBuilder.toString(); } } + From aa17f4f37614748ab5bfa5652a91e18a1c56615d Mon Sep 17 00:00:00 2001 From: dgant Date: Sun, 4 Oct 2020 21:26:23 -0400 Subject: [PATCH 42/56] Fixed up unit tests --- src/main/java/bwapi/Game.java | 5 +- src/test/java/bwapi/GameTest.java | 14 +- src/test/java/bwapi/PointTest.java | 16 ++- .../bwapi/SynchronizationEnvironment.java | 9 +- src/test/java/bwapi/SynchronizationTest.java | 128 ++++++------------ 5 files changed, 66 insertions(+), 106 deletions(-) diff --git a/src/main/java/bwapi/Game.java b/src/main/java/bwapi/Game.java index a7a2a9b6..69757b2b 100644 --- a/src/main/java/bwapi/Game.java +++ b/src/main/java/bwapi/Game.java @@ -2664,7 +2664,6 @@ void addCommand(final CommandType type, final int value1, final int value2) { sideEffects.enqueue(SideEffect.addCommand(type, value1, value2)); } - /** * Convenience method for adding a game command from raw arguments. */ @@ -2685,4 +2684,8 @@ void addShape(final ShapeType type, final CoordinateType coordType, final int x1 void 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) { sideEffects.enqueue(SideEffect.addShape(type, coordType, x1, y1, x2, y2, text, extra2, color, isSolid)); } + + void setAllUnits(List units) { + allUnits = Collections.unmodifiableList(units); + } } diff --git a/src/test/java/bwapi/GameTest.java b/src/test/java/bwapi/GameTest.java index 844e6156..a5202808 100644 --- a/src/test/java/bwapi/GameTest.java +++ b/src/test/java/bwapi/GameTest.java @@ -8,6 +8,8 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.List; import org.junit.Before; import org.junit.Test; @@ -20,13 +22,7 @@ @RunWith(Theories.class) public class GameTest { - private List allUnits = new ArrayList<>(); - private Game sut = new Game() { - @Override - public List getAllUnits() { - return allUnits; - } - }; + private Game sut = new Game(); private Unit dummy; @DataPoints("overlapping") @@ -63,7 +59,7 @@ public void setup() { public void shouldFindOverlappingUnits( @FromDataPoints("overlapping") Pair rect) { // GIVEN - allUnits.add(dummy); + sut.setAllUnits(Collections.singletonList(dummy)); // WHEN List unitsInRectangle = sut @@ -77,7 +73,7 @@ public void shouldFindOverlappingUnits( public void shouldNotFindNonOverlappingUnits( @FromDataPoints("non-overlapping") Pair rect) { // GIVEN - allUnits.add(dummy); + sut.setAllUnits(Collections.singletonList(dummy)); // WHEN List unitsInRectangle = sut diff --git a/src/test/java/bwapi/PointTest.java b/src/test/java/bwapi/PointTest.java index d36d111d..5aa3dcb5 100644 --- a/src/test/java/bwapi/PointTest.java +++ b/src/test/java/bwapi/PointTest.java @@ -2,9 +2,11 @@ import org.junit.Test; +import java.nio.ByteBuffer; import java.util.Random; import static org.junit.Assert.*; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -54,10 +56,16 @@ public void alternativeConstructorTest() { @Test public void isValidChecks() { - Game game = mock(Game.class); - when(game.mapPixelWidth()).thenReturn(32 * 256); - when(game.mapPixelHeight()).thenReturn(32 * 256); - + Game game = new Game(); + game.clientData().setBuffer(GameBuilder.binToBufferUnchecked(GameBuilder.DEFAULT_BUFFER_PATH)); + game.clientData().gameData().setMapWidth(256); + game.clientData().gameData().setMapHeight(256); + game.init(); + + assertEquals(256, game.mapHeight()); + assertEquals(256, game.mapWidth()); + assertEquals(32 * 256, game.mapPixelHeight()); + assertEquals(32 * 256, game.mapPixelWidth()); assertTrue(new Position(0,0).isValid(game)); assertTrue(new Position(1, 1).isValid(game)); diff --git a/src/test/java/bwapi/SynchronizationEnvironment.java b/src/test/java/bwapi/SynchronizationEnvironment.java index cf8063ab..858920e5 100644 --- a/src/test/java/bwapi/SynchronizationEnvironment.java +++ b/src/test/java/bwapi/SynchronizationEnvironment.java @@ -16,15 +16,15 @@ class SynchronizationEnvironment { BWClientConfiguration configuration; BWClient bwClient; - private BWEventListener listener; private Client client; private int onEndFrame; private long bwapiDelayMs; private Map onFrames; SynchronizationEnvironment() { + BWEventListener listener = mock(BWEventListener.class); + configuration = new BWClientConfiguration(); - listener = mock(BWEventListener.class); client = mock(Client.class); bwClient = new BWClient(listener); bwClient.setClient(client); @@ -42,7 +42,7 @@ class SynchronizationEnvironment { doAnswer(answer -> { clientUpdate(); return null; - }).when(client).update(); + }).when(client).sendFrameReceiveFrame(); doAnswer(answer -> { configuration.log("Test: onStart()"); return null; @@ -52,11 +52,12 @@ class SynchronizationEnvironment { return null; }).when(listener).onEnd(anyBoolean()); doAnswer(answer -> { - configuration.log("Test: onFrame()"); + configuration.log("Test: onFrame() start"); int botFrame = bwClient.getGame().getFrameCount(); if (onFrames.containsKey(botFrame)) { onFrames.get(botFrame).run(); } + configuration.log("Test: onFrame() end"); return null; }).when(listener).onFrame(); } diff --git a/src/test/java/bwapi/SynchronizationTest.java b/src/test/java/bwapi/SynchronizationTest.java index 7b9d7c62..7fe24c68 100644 --- a/src/test/java/bwapi/SynchronizationTest.java +++ b/src/test/java/bwapi/SynchronizationTest.java @@ -115,26 +115,6 @@ public void async_IfBotDelay_ThenClientStalls() { environment.runGame(); } - @Test - public void async_IfUnsafe_ThenBotProceedsImmediately() { - SynchronizationEnvironment environment = new SynchronizationEnvironment(); - environment.configuration.async = true; - environment.configuration.asyncUnsafe = true; - environment.configuration.maxFrameDurationMs = 100; - environment.configuration.logVerbosely = true; - - environment.onFrame(1, () -> { - assertEquals("Bot should be observing frame 1", 1, environment.bwClient.getGame().getFrameCount()); - assertEquals("Live game should be observing frame 1", 1, environment.liveGameData().getFrameCount()); - environment.liveGameData().setFps(12345); - assertEquals("Bot should be observing live game", 12345, environment.bwClient.getGame().getFPS()); - sleepUnchecked(50); - assertFalse("Bot should be observing frame buffer", 12345 == environment.bwClient.getGame().getFPS()); - }); - - environment.runGame(3); - } - @Test public void async_IfFrameZeroWaitsEnabled_ThenAllowInfiniteTime() { SynchronizationEnvironment environment = new SynchronizationEnvironment(); @@ -150,7 +130,7 @@ public void async_IfFrameZeroWaitsEnabled_ThenAllowInfiniteTime() { assertEquals("Bot should not be behind the live game", 0, environment.bwClient.framesBehind()); }); - environment.runGame(); + environment.runGame(2); } @Test @@ -168,25 +148,7 @@ public void async_IfFrameZeroWaitsDisabled_ThenClientBuffers() { assertEquals("Bot should be behind the live game", 2, environment.bwClient.framesBehind()); }); - environment.runGame(); - } - - @Test - public void async_MeasurePerformance_TotalFrameDuration() { - final int frames = 10; - final int frameSleep = 20; - SynchronizationEnvironment environment = new SynchronizationEnvironment(); - environment.configuration.async = true; - environment.configuration.unlimitedFrameZero = true; - environment.configuration.maxFrameDurationMs = frameSleep + 20; - IntStream.range(0, frames).forEach(i -> environment.onFrame(i, () -> { - sleepUnchecked(frameSleep); - })); - environment.runGame(frames); - - // Assume copying accounts for almost all the frame time except what the bot uses - double meanCopy = environment.metrics().copyingToBuffer.avgValue; - assertWithin("Total frame duration: Average", environment.metrics().totalFrameDuration.avgValue, meanCopy + frameSleep, MS_MARGIN); + environment.runGame(2); } @Test @@ -199,10 +161,10 @@ public void async_MeasurePerformance_CopyingToBuffer() { final double maxObserved = 15; final double meanObserved = (minObserved + maxObserved) / 2; final double rangeObserved = (maxObserved - minObserved) / 2; - assertWithin("Copy to buffer: minimum", environment.metrics().copyingToBuffer.minValue, meanObserved, rangeObserved); - assertWithin("Copy to buffer: maximum", environment.metrics().copyingToBuffer.maxValue, meanObserved, rangeObserved); - assertWithin("Copy to buffer: average", environment.metrics().copyingToBuffer.avgValue, meanObserved, rangeObserved); - assertWithin("Copy to buffer: previous", environment.metrics().copyingToBuffer.lastValue, meanObserved, rangeObserved); + assertWithin("Copy to buffer: minimum", environment.metrics().copyingToBuffer.runningTotal.min, meanObserved, rangeObserved); + assertWithin("Copy to buffer: maximum", environment.metrics().copyingToBuffer.runningTotal.max, meanObserved, rangeObserved); + assertWithin("Copy to buffer: average", environment.metrics().copyingToBuffer.runningTotal.mean, meanObserved, rangeObserved); + assertWithin("Copy to buffer: previous", environment.metrics().copyingToBuffer.runningTotal.last, meanObserved, rangeObserved); } @Test @@ -214,25 +176,25 @@ public void async_MeasurePerformance_FrameBufferSizeAndFramesBehind() { environment.configuration.maxFrameDurationMs = 20; environment.onFrame(5, () -> { - assertWithin("5: Frame buffer average", 0, environment.metrics().frameBufferSize.avgValue, 0.1); - assertWithin("5: Frame buffer minimum", 0, environment.metrics().frameBufferSize.minValue, 0.1); - assertWithin("5: Frame buffer maximum", 0, environment.metrics().frameBufferSize.maxValue, 0.1); - assertWithin("5: Frame buffer previous", 0, environment.metrics().frameBufferSize.lastValue, 0.1); - assertWithin("5: Frames behind average", 0, environment.metrics().framesBehind.avgValue, 0.1); - assertWithin("5: Frames behind minimum", 0, environment.metrics().framesBehind.minValue, 0.1); - assertWithin("5: Frames behind maximum", 0, environment.metrics().framesBehind.maxValue, 0.1); - assertWithin("5: Frames behind previous", 0, environment.metrics().framesBehind.lastValue, 0.1); + assertWithin("5: Frame buffer average", 0, environment.metrics().frameBufferSize.runningTotal.mean, 0.1); + assertWithin("5: Frame buffer minimum", 0, environment.metrics().frameBufferSize.runningTotal.min, 0.1); + assertWithin("5: Frame buffer maximum", 0, environment.metrics().frameBufferSize.runningTotal.max, 0.1); + assertWithin("5: Frame buffer previous", 0, environment.metrics().frameBufferSize.runningTotal.last, 0.1); + assertWithin("5: Frames behind average", 0, environment.metrics().framesBehind.runningTotal.mean, 0.1); + assertWithin("5: Frames behind minimum", 0, environment.metrics().framesBehind.runningTotal.min, 0.1); + assertWithin("5: Frames behind maximum", 0, environment.metrics().framesBehind.runningTotal.max, 0.1); + assertWithin("5: Frames behind previous", 0, environment.metrics().framesBehind.runningTotal.last, 0.1); sleepUnchecked(200); }); environment.onFrame(6, () -> { - assertWithin("6: Frame buffer average", 1 / 6.0 + 2 / 7.0, environment.metrics().frameBufferSize.avgValue, 0.1); - assertWithin("6: Frame buffer minimum", 0, environment.metrics().frameBufferSize.minValue, 0.1); - assertWithin("6: Frame buffer maximum", 2, environment.metrics().frameBufferSize.maxValue, 0.1); - assertWithin("6: Frame buffer previous", 2, environment.metrics().frameBufferSize.lastValue, 0.1); - assertWithin("6: Frames behind average", 1 / 6.0, environment.metrics().framesBehind.avgValue, 0.1); - assertWithin("6: Frames behind minimum", 0, environment.metrics().framesBehind.minValue, 0.1); - assertWithin("6: Frames behind maximum", 1, environment.metrics().framesBehind.maxValue, 0.1); - assertWithin("6: Frames behind previous", 1, environment.metrics().framesBehind.lastValue, 0.1); + assertWithin("6: Frame buffer average", 1 / 6.0 + 2 / 7.0, environment.metrics().frameBufferSize.runningTotal.mean, 0.1); + assertWithin("6: Frame buffer minimum", 0, environment.metrics().frameBufferSize.runningTotal.min, 0.1); + assertWithin("6: Frame buffer maximum", 2, environment.metrics().frameBufferSize.runningTotal.max, 0.1); + assertWithin("6: Frame buffer previous", 2, environment.metrics().frameBufferSize.runningTotal.last, 0.1); + assertWithin("6: Frames behind average", 1 / 6.0, environment.metrics().framesBehind.runningTotal.mean, 0.1); + assertWithin("6: Frames behind minimum", 0, environment.metrics().framesBehind.runningTotal.min, 0.1); + assertWithin("6: Frames behind maximum", 1, environment.metrics().framesBehind.runningTotal.max, 0.1); + assertWithin("6: Frames behind previous", 1, environment.metrics().framesBehind.runningTotal.last, 0.1); }); environment.runGame(8); @@ -256,36 +218,26 @@ public void MeasurePerformance_BotResponse() { sleepUnchecked(100); }); environment.onFrame(2, () -> { - assertWithin("2: Bot response average", 100, environment.metrics().botResponse.avgValue, MS_MARGIN); - assertWithin("2: Bot response minimum", 100, environment.metrics().botResponse.minValue, MS_MARGIN); - assertWithin("2: Bot response maximum", 100, environment.metrics().botResponse.maxValue, MS_MARGIN); - assertWithin("2: Bot response previous", 100, environment.metrics().botResponse.lastValue, MS_MARGIN); + assertWithin("2: Bot response average", 100, environment.metrics().botResponse.runningTotal.mean, MS_MARGIN); + assertWithin("2: Bot response minimum", 100, environment.metrics().botResponse.runningTotal.min, MS_MARGIN); + assertWithin("2: Bot response maximum", 100, environment.metrics().botResponse.runningTotal.max, MS_MARGIN); + assertWithin("2: Bot response previous", 100, environment.metrics().botResponse.runningTotal.last, MS_MARGIN); sleepUnchecked(300); }); environment.onFrame(3, () -> { - assertWithin("3: Bot response average", 200, environment.metrics().botResponse.avgValue, MS_MARGIN); - assertWithin("3: Bot response minimum", 100, environment.metrics().botResponse.minValue, MS_MARGIN); - assertWithin("3: Bot response maximum", 300, environment.metrics().botResponse.maxValue, MS_MARGIN); - assertWithin("3: Bot response previous", 300, environment.metrics().botResponse.lastValue, MS_MARGIN); + assertWithin("3: Bot response average", 200, environment.metrics().botResponse.runningTotal.mean, MS_MARGIN); + assertWithin("3: Bot response minimum", 100, environment.metrics().botResponse.runningTotal.min, MS_MARGIN); + assertWithin("3: Bot response maximum", 300, environment.metrics().botResponse.runningTotal.max, MS_MARGIN); + assertWithin("3: Bot response previous", 300, environment.metrics().botResponse.runningTotal.last, MS_MARGIN); sleepUnchecked(200); }); environment.runGame(4); - assertWithin("Final: Bot response average", 200, environment.metrics().botResponse.avgValue, MS_MARGIN); - assertWithin("Final: Bot response minimum", 100, environment.metrics().botResponse.minValue, MS_MARGIN); - assertWithin("Final: Bot response maximum", 300, environment.metrics().botResponse.maxValue, MS_MARGIN); - assertWithin("Final: Bot response previous", 200, environment.metrics().botResponse.lastValue, MS_MARGIN); - } - - @Test - public void MeasurePerformance_BwapiResponse() { - final long bwapiDelayMs = 50; - SynchronizationEnvironment environment = new SynchronizationEnvironment(); - environment.setBwapiDelayMs(bwapiDelayMs); - environment.runGame(); - System.out.println(environment.metrics()); - assertWithin("BWAPI Response: Average", environment.metrics().bwapiResponse.avgValue, bwapiDelayMs, MS_MARGIN); + assertWithin("Final: Bot response average", 200, environment.metrics().botResponse.runningTotal.mean, MS_MARGIN); + assertWithin("Final: Bot response minimum", 100, environment.metrics().botResponse.runningTotal.min, MS_MARGIN); + assertWithin("Final: Bot response maximum", 300, environment.metrics().botResponse.runningTotal.max, MS_MARGIN); + assertWithin("Final: Bot response previous", 200, environment.metrics().botResponse.runningTotal.last, MS_MARGIN); } @Test @@ -298,8 +250,8 @@ public void MeasurePerformance_BotIdle() { environment.configuration.unlimitedFrameZero = true; environment.setBwapiDelayMs(bwapiDelayMs); environment.runGame(frames); - double expected = environment.metrics().copyingToBuffer.avgValue + bwapiDelayMs; - assertWithin("Bot Idle: Average", environment.metrics().botIdle.avgValue, expected, MS_MARGIN); + double expected = environment.metrics().copyingToBuffer.runningTotal.mean + bwapiDelayMs; + assertWithin("Bot Idle: Average", environment.metrics().botIdle.runningTotal.mean, expected, MS_MARGIN); } @Test @@ -315,10 +267,10 @@ public void async_MeasurePerformance_IntentionallyBlocking() { }); environment.onFrame(2, () -> { assertWithin( - "2: Intentionally blocking previous", - environment.metrics().intentionallyBlocking.lastValue, - frameDelayMs - environment.configuration.asyncFrameBufferCapacity * environment.configuration.maxFrameDurationMs, - MS_MARGIN); + "2: Intentionally blocking previous", + environment.metrics().intentionallyBlocking.runningTotal.last, + frameDelayMs - environment.configuration.asyncFrameBufferCapacity * environment.configuration.maxFrameDurationMs, + MS_MARGIN); sleepUnchecked(100); }); environment.runGame(3); From dcaaa123083d5db72369b92230516207ea92e732 Mon Sep 17 00:00:00 2001 From: dgant Date: Sun, 4 Oct 2020 22:40:27 -0400 Subject: [PATCH 43/56] Moved PerformanceMetric(s) properties behind getters --- src/main/java/bwapi/BWClient.java | 16 +-- .../java/bwapi/BWClientConfiguration.java | 4 +- src/main/java/bwapi/BotWrapper.java | 14 +- src/main/java/bwapi/Client.java | 38 +++--- src/main/java/bwapi/FrameBuffer.java | 8 +- src/main/java/bwapi/PerformanceMetric.java | 39 ++++-- src/main/java/bwapi/PerformanceMetrics.java | 123 +++++++++++++----- src/test/java/bwapi/SynchronizationTest.java | 74 +++++------ 8 files changed, 190 insertions(+), 126 deletions(-) diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index 881be798..f4544a89 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -124,22 +124,10 @@ public void startGame(BWClientConfiguration gameConfiguration) { long ticksBefore = Kernel32.INSTANCE.GetTickCount(); botWrapper.onFrame(); - performanceMetrics.flushSideEffects.time(() -> getGame().sideEffects.flushTo(liveGameData)); - performanceMetrics.frameDurationReceiveToSend.stopTiming(); + performanceMetrics.getFlushSideEffects().time(() -> getGame().sideEffects.flushTo(liveGameData)); + performanceMetrics.getFrameDurationReceiveToSend().stopTiming(); long ticksAfter = Kernel32.INSTANCE.GetTickCount(); - // Measure differential between JVM timer and WinAPI's GetTickCount, used by BWAPI 4.4 and below - if (doTime()) { - long deltaTicks = ticksAfter - ticksBefore; - long deltaMillis = (long) performanceMetrics.frameDurationReceiveToSend.runningTotal.last; - long delta = deltaMillis - deltaTicks; - if (delta > 0) { - performanceMetrics.positiveTimeDelta.record(delta); - } else if (delta < 0) { - performanceMetrics.negativeTimeDelta.record(-delta); - } - } - client.sendFrameReceiveFrame(); if (!client.isConnected()) { System.out.println("Reconnecting..."); diff --git a/src/main/java/bwapi/BWClientConfiguration.java b/src/main/java/bwapi/BWClientConfiguration.java index 19092197..7614b3c9 100644 --- a/src/main/java/bwapi/BWClientConfiguration.java +++ b/src/main/java/bwapi/BWClientConfiguration.java @@ -69,7 +69,7 @@ public class BWClientConfiguration { /** * Checks that the configuration is in a valid state. Throws an IllegalArgumentException if it isn't. */ - public void validate() { + void validate() { if (async && maxFrameDurationMs < 0) { 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)."); } @@ -78,7 +78,7 @@ public void validate() { } } - public void log(String value) { + void log(String value) { if (logVerbosely) { System.out.println(value); } diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java index 6c6080ae..444915cb 100644 --- a/src/main/java/bwapi/BotWrapper.java +++ b/src/main/java/bwapi/BotWrapper.java @@ -113,7 +113,7 @@ void onFrame() { configuration.log("Main: Enqueued frame #" + frame); if (frame > 0) { - performanceMetrics.clientIdle.startTiming(); + performanceMetrics.getClientIdle().startTiming(); } frameBuffer.lockSize.lock(); try { @@ -147,13 +147,13 @@ void onFrame() { configuration.log("Main: Waiting " + remainingNanos / 1000000 + "ms for bot on frame #" + frame); frameBuffer.conditionSize.awaitNanos(remainingNanos); long excessNanos = Math.max(0, (System.nanoTime() - endNanos) / 1000000); - performanceMetrics.excessSleep.record(excessNanos); + performanceMetrics.getExcessSleep().record(excessNanos); } } } catch(InterruptedException ignored) { } finally { frameBuffer.lockSize.unlock(); - performanceMetrics.clientIdle.stopTiming(); + performanceMetrics.getClientIdle().stopTiming(); configuration.log("Main: onFrame asynchronous end"); } } else { @@ -189,7 +189,7 @@ private Thread createBotThread() { boolean doUnsafeRead = false; configuration.log("Bot: Ready for another frame"); - performanceMetrics.botIdle.startTiming(); + performanceMetrics.getBotIdle().startTiming(); frameBuffer.lockSize.lock(); try { doUnsafeRead = isUnsafeReadReady(); @@ -201,7 +201,7 @@ private Thread createBotThread() { } finally { frameBuffer.lockSize.unlock(); } - performanceMetrics.botIdle.stopTiming(); + performanceMetrics.getBotIdle().stopTiming(); if (doUnsafeRead) { configuration.log("Bot: Reading live frame"); @@ -243,10 +243,10 @@ private void handleEvents() { } if (configuration.async) { - performanceMetrics.framesBehind.record(Math.max(1, frameBuffer.framesBuffered()) - 1); + performanceMetrics.getFramesBehind().record(Math.max(1, frameBuffer.framesBuffered()) - 1); } - performanceMetrics.botResponse.timeIf( + performanceMetrics.getBotResponse().timeIf( ! gameOver && (gameData.getFrameCount() > 0 || ! configuration.unlimitedFrameZero), () -> { for (int i = 0; i < gameData.getEventCount(); i++) { diff --git a/src/main/java/bwapi/Client.java b/src/main/java/bwapi/Client.java index 0fd6bb0c..692e4523 100644 --- a/src/main/java/bwapi/Client.java +++ b/src/main/java/bwapi/Client.java @@ -234,10 +234,10 @@ void sendFrameReceiveFrame() { final PerformanceMetrics metrics = bwClient.getPerformanceMetrics(); // Tell BWAPI that we are done with the current frame - metrics.frameDurationReceiveToSend.stopTiming(); + metrics.getFrameDurationReceiveToSend().stopTiming(); if (bwClient.doTime()) { - metrics.communicationSendToReceive.startTiming(); - metrics.communicationSendToSent.startTiming(); + metrics.getCommunicationSendToReceive().startTiming(); + metrics.getCommunicationSendToSent().startTiming(); } try { // 1 is the "frame done" signal to BWAPI @@ -251,19 +251,19 @@ void sendFrameReceiveFrame() { disconnect(); return; } - metrics.communicationSendToSent.stopTiming(); - metrics.frameDurationReceiveToSent.stopTiming(); - metrics.frameDurationReceiveToSentGTC.stopTiming(); + metrics.getCommunicationSendToSent().stopTiming(); + metrics.getFrameDurationReceiveToSent().stopTiming(); + metrics.getFrameDurationReceiveToSentGTC().stopTiming(); if (bwClient.doTime()) { final int eventCount = clientData.gameData().getEventCount(); - metrics.numberOfEvents.record(eventCount); - metrics.numberOfEventsTimesDurationReceiveToSent.record(eventCount * metrics.frameDurationReceiveToSent.runningTotal.last); - metrics.numberOfEventsTimesDurationReceiveToSentGTC.record(eventCount * metrics.frameDurationReceiveToSentGTC.runningTotal.last); + metrics.getNumberOfEvents().record(eventCount); + metrics.getNumberOfEventsTimesDurationReceiveToSent().record(eventCount * metrics.getFrameDurationReceiveToSent().getRunningTotal().getLast()); + metrics.getNumberOfEventsTimesDurationReceiveToSentGTC().record(eventCount * metrics.getFrameDurationReceiveToSentGTC().getRunningTotal().getLast()); } // Listen for BWAPI to indicate that a new frame is ready if (bwClient.doTime()) { - metrics.communicationListenToReceive.startTiming(); + metrics.getCommunicationListenToReceive().startTiming(); } boolean frameReady = false; while (!frameReady) { @@ -279,19 +279,19 @@ void sendFrameReceiveFrame() { break; } } - metrics.communicationListenToReceive.stopTiming(); - metrics.communicationSendToReceive.stopTiming(); + metrics.getCommunicationListenToReceive().stopTiming(); + metrics.getCommunicationSendToReceive().stopTiming(); if (bwClient.doTime()) { - metrics.frameDurationReceiveToSend.startTiming(); - metrics.frameDurationReceiveToSent.startTiming(); - metrics.frameDurationReceiveToSentGTC.startTiming(); + metrics.getFrameDurationReceiveToSend().startTiming(); + metrics.getFrameDurationReceiveToSent().startTiming(); + metrics.getFrameDurationReceiveToSentGTC().startTiming(); } - metrics.frameDurationReceiveToReceive.stopTiming(); - metrics.frameDurationReceiveToReceiveGTC.stopTiming(); + metrics.getFrameDurationReceiveToReceive().stopTiming(); + metrics.getFrameDurationReceiveToReceiveGTC().stopTiming(); if (bwClient.doTime()) { - metrics.frameDurationReceiveToReceive.startTiming(); - metrics.frameDurationReceiveToReceiveGTC.startTiming(); + metrics.getFrameDurationReceiveToReceive().startTiming(); + metrics.getFrameDurationReceiveToReceiveGTC().startTiming(); } } diff --git a/src/main/java/bwapi/FrameBuffer.java b/src/main/java/bwapi/FrameBuffer.java index 96b9e009..fcda6d1f 100644 --- a/src/main/java/bwapi/FrameBuffer.java +++ b/src/main/java/bwapi/FrameBuffer.java @@ -101,10 +101,10 @@ void enqueueFrame() { try { while (full()) { configuration.log("Main: Waiting for frame buffer capacity"); - performanceMetrics.intentionallyBlocking.startTiming(); + performanceMetrics.getIntentionallyBlocking().startTiming(); conditionSize.awaitUninterruptibly(); } - performanceMetrics.intentionallyBlocking.stopTiming(); + performanceMetrics.getIntentionallyBlocking().stopTiming(); } finally { lockSize.unlock(); }; // For the first frame of the game, populate all buffers completely @@ -115,7 +115,7 @@ void enqueueFrame() { copyBuffer(liveData, frameBuffer, true); } } else { - performanceMetrics.copyingToBuffer.time(() -> { + performanceMetrics.getCopyingToBuffer().time(() -> { ByteBuffer dataTarget = dataBuffer.get(indexGame()); copyBuffer(liveData, dataTarget, false); }); @@ -123,7 +123,7 @@ void enqueueFrame() { lockSize.lock(); try { - performanceMetrics.frameBufferSize.record(framesBuffered()); + performanceMetrics.getFrameBufferSize().record(framesBuffered()); ++stepGame; conditionSize.signalAll(); } finally { lockSize.unlock(); } diff --git a/src/main/java/bwapi/PerformanceMetric.java b/src/main/java/bwapi/PerformanceMetric.java index c048e5e5..c31451ae 100644 --- a/src/main/java/bwapi/PerformanceMetric.java +++ b/src/main/java/bwapi/PerformanceMetric.java @@ -9,12 +9,12 @@ * Aggregates labeled time series data. */ public class PerformanceMetric { - class RunningTotal { - int samples = 0; - double last = 0d; - double mean = 0d; - double min = Long.MAX_VALUE; - double max = Long.MIN_VALUE; + public class RunningTotal { + private int samples = 0; + private double last = 0d; + private double mean = 0d; + private double min = Long.MAX_VALUE; + private double max = Long.MIN_VALUE; void record(double value) { last = value; min = Math.min(min, value); @@ -22,6 +22,21 @@ void record(double value) { mean = (mean * samples + value) / (samples + 1d); ++samples; } + public double getSamples() { + return samples; + } + public double getLast() { + return last; + } + public double getMean() { + return mean; + } + public double getMin() { + return min; + } + public double getMax() { + return max; + } } class Threshold { double threshold; @@ -45,10 +60,10 @@ public String toString() { private final String name; private long timeStarted = 0; - public int interrupted = 0; + private int interrupted = 0; private boolean usingGetTickCount = false; - RunningTotal runningTotal = new RunningTotal(); + private final RunningTotal runningTotal = new RunningTotal(); private ArrayList thresholds = new ArrayList<>(); PerformanceMetric(PerformanceMetrics metrics, String name, double... thresholds) { @@ -59,6 +74,14 @@ public String toString() { metrics.addMetric(this); } + public RunningTotal getRunningTotal() { + return runningTotal; + } + + public int getInterrupted() { + return interrupted; + } + /** * Records the duration of a function call. * @param runnable The function to time diff --git a/src/main/java/bwapi/PerformanceMetrics.java b/src/main/java/bwapi/PerformanceMetrics.java index dad61f7a..63d4e899 100644 --- a/src/main/java/bwapi/PerformanceMetrics.java +++ b/src/main/java/bwapi/PerformanceMetrics.java @@ -13,7 +13,10 @@ public class PerformanceMetrics { * to sending commands back * *exclusive* of the time spent sending commands back. */ - PerformanceMetric frameDurationReceiveToSend; + public PerformanceMetric getFrameDurationReceiveToSend() { + return frameDurationReceiveToSend; + } + private PerformanceMetric frameDurationReceiveToSend; /** * Duration of the frame cycle steps measured by BWAPI, @@ -21,132 +24,181 @@ public class PerformanceMetrics { * to sending commands back * *inclusive* of the time spent sending commands back. */ - PerformanceMetric frameDurationReceiveToSent; + public PerformanceMetric getFrameDurationReceiveToSent() { + return frameDurationReceiveToSent; + } + private PerformanceMetric frameDurationReceiveToSent; /** * Duration of a frame cycle originating at * the time when JBWAPI observes a new frame in shared memory. * Uses GetTickCount() instead of System.nanoRime() */ - PerformanceMetric frameDurationReceiveToSentGTC; + public PerformanceMetric getFrameDurationReceiveToSentGTC() { + return frameDurationReceiveToSentGTC; + } + private PerformanceMetric frameDurationReceiveToSentGTC; /** * Duration of a frame cycle originating at * the time when JBWAPI observes a new frame in shared memory. */ - PerformanceMetric frameDurationReceiveToReceive; + public PerformanceMetric getFrameDurationReceiveToReceive() { + return frameDurationReceiveToReceive; + } + private PerformanceMetric frameDurationReceiveToReceive; /** * Duration of a frame cycle originating at * the time when JBWAPI observes a new frame in shared memory. * Uses GetTickCount() instead of System.nanoRime() */ - PerformanceMetric frameDurationReceiveToReceiveGTC; + public PerformanceMetric getFrameDurationReceiveToReceiveGTC() { + return frameDurationReceiveToReceiveGTC; + } + private PerformanceMetric frameDurationReceiveToReceiveGTC; /** * Time spent copying game data from system pipe shared memory to a frame buffer. * Applicable only in asynchronous mode. */ - PerformanceMetric copyingToBuffer; + public PerformanceMetric getCopyingToBuffer() { + return copyingToBuffer; + } + private PerformanceMetric copyingToBuffer; /** * Time spent intentionally blocking on bot operation due to a full frame buffer. * Applicable only in asynchronous mode. */ - PerformanceMetric intentionallyBlocking; + public PerformanceMetric getIntentionallyBlocking() { + return intentionallyBlocking; + } + private PerformanceMetric intentionallyBlocking; /** * Number of frames backed up in the frame buffer, after enqueuing each frame (and not including the newest frame). * Applicable only in asynchronous mode. */ - PerformanceMetric frameBufferSize; + public PerformanceMetric getFrameBufferSize() { + return frameBufferSize; + } + private PerformanceMetric frameBufferSize; /** * Number of frames behind real-time the bot is at the time it handles events. * Applicable only in asynchronous mode. */ - PerformanceMetric framesBehind; + public PerformanceMetric getFramesBehind() { + return framesBehind; + } + private PerformanceMetric framesBehind; /** * Time spent applying bot commands to the live frame. */ - PerformanceMetric flushSideEffects; + public PerformanceMetric getFlushSideEffects() { + return flushSideEffects; + } + private PerformanceMetric flushSideEffects; /** * Time spent waiting for bot event handlers to complete for a single frame. */ - PerformanceMetric botResponse; + public PerformanceMetric getBotResponse() { + return botResponse; + } + private PerformanceMetric botResponse; /** * Time spent waiting for a response from BWAPI, * inclusive of the time spent sending the signal to BWAPI * and the time spent waiting for and receiving it. */ - PerformanceMetric communicationSendToReceive; + public PerformanceMetric getCommunicationSendToReceive() { + return communicationSendToReceive; + } + private PerformanceMetric communicationSendToReceive; /** * Time spent sending the "frame complete" signal to BWAPI. * Significant durations would indicate something blocking writes to shared memory. */ - PerformanceMetric communicationSendToSent; + public PerformanceMetric getCommunicationSendToSent() { + return communicationSendToSent; + } + private PerformanceMetric communicationSendToSent; /** * Time spent waiting for a "frame ready" signal from BWAPI. * This time likely additional response time spent by other bots and StarCraft itself. */ - PerformanceMetric communicationListenToReceive; + public PerformanceMetric getCommunicationListenToReceive() { + return communicationListenToReceive; + } + private PerformanceMetric communicationListenToReceive; /** * Time bot spends idle. * Applicable only in asynchronous mode. */ - PerformanceMetric botIdle; + public PerformanceMetric getBotIdle() { + return botIdle; + } + private PerformanceMetric botIdle; /** * Time the main thread spends idle, waiting for the bot to finish processing frames. * Applicable only in asynchronous mode. */ - PerformanceMetric clientIdle; + public PerformanceMetric getClientIdle() { + return clientIdle; + } + private PerformanceMetric clientIdle; /** * Time the main thread spends oversleeping its timeout target, potentially causing overtime frames. * Applicable only in asynchronous mode. */ - PerformanceMetric excessSleep; - - /** - * Instances of System.nanoTime() measuring a longer frame duration with respect to WinAPI's GetTickCount which BWAPI uses up to 4.4. - */ - PerformanceMetric positiveTimeDelta; - - /** - * Instances of System.nanoTime() measuring a shorter frame duration with respect to WinAPI's GetTickCount which BWAPI uses up to 4.4. - */ - PerformanceMetric negativeTimeDelta; + public PerformanceMetric getExcessSleep() { + return excessSleep; + } + private PerformanceMetric excessSleep; /** * The number of events sent by BWAPI each frame. - * Might help diagnosing issues related to https://github.com/bwapi/bwapi/issues/860 and https://github.com/davechurchill/StarcraftAITournamentManager/issues/42 + * Helps detect use of broken BWAPI 4.4 tournament modules, with respect to: + * - https://github.com/bwapi/bwapi/issues/860 + * - https://github.com/davechurchill/StarcraftAITournamentManager/issues/42 */ - PerformanceMetric numberOfEvents; + public PerformanceMetric getNumberOfEvents() { + return numberOfEvents; + } + private PerformanceMetric numberOfEvents; /** * The number of events sent by BWAPI each frame, * multiplied by the duration of time spent on that frame (receive-to-sent). - * Might help diagnosing issues related to: + * Helps detect use of broken BWAPI 4.4 tournament modules, with respect to: * - https://github.com/bwapi/bwapi/issues/860 * - https://github.com/davechurchill/StarcraftAITournamentManager/issues/42 */ + public PerformanceMetric getNumberOfEventsTimesDurationReceiveToSent() { + return numberOfEventsTimesDurationReceiveToSent; + } PerformanceMetric numberOfEventsTimesDurationReceiveToSent; /** * The number of events sent by BWAPI each frame, * multiplied by the duration of time spent on that frame (receive-to-sent), * and using GetTickCount() instead of System.nanoTime(). - * Might help diagnosing issues related to: - * - https://github.com/bwapi/bwapi/issues/860 - * - https://github.com/davechurchill/StarcraftAITournamentManager/issues/42 + * Helps detect use of broken BWAPI 4.4 tournament modules, with respect to: + * * - https://github.com/bwapi/bwapi/issues/860 + * * - https://github.com/davechurchill/StarcraftAITournamentManager/issues/42 */ + public PerformanceMetric getNumberOfEventsTimesDurationReceiveToSentGTC() { + return numberOfEventsTimesDurationReceiveToSentGTC; + } PerformanceMetric numberOfEventsTimesDurationReceiveToSentGTC; private BWClientConfiguration configuration; @@ -157,6 +209,9 @@ public class PerformanceMetrics { reset(); } + /** + * Clears all tracked data and starts counting from a blank slate. + */ public void reset() { performanceMetrics.clear(); frameDurationReceiveToSend = new PerformanceMetric(this, "Frame duration: After receiving 'frame ready' -> before sending 'frame done'", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); @@ -176,8 +231,6 @@ public void reset() { botIdle = new PerformanceMetric(this, "Time bot spent idle", Long.MAX_VALUE); clientIdle = new PerformanceMetric(this, "Time client spent waiting for bot", configuration.maxFrameDurationMs); excessSleep = new PerformanceMetric(this, "Excess duration of client sleep", 1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); - positiveTimeDelta = new PerformanceMetric(this, "Positive timer discrepancy compared to BWAPI", 1, 2, 3, 4, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); - negativeTimeDelta = new PerformanceMetric(this, "Negative timer discrepancy compared to BWAPI", 1, 2, 3, 4, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); numberOfEvents = new PerformanceMetric(this, "Number of events received from BWAPI", 1, 2, 3, 4, 5, 6, 8, 10, 15, 20); numberOfEventsTimesDurationReceiveToSent = new PerformanceMetric(this, "Number of events received from BWAPI, multiplied by the receive-to-sent duration of that frame", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); numberOfEventsTimesDurationReceiveToSentGTC = new PerformanceMetric(this, "Number of events received from BWAPI, multiplied by the receive-to-sent duration of that frame (Using GetTickCount())", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); diff --git a/src/test/java/bwapi/SynchronizationTest.java b/src/test/java/bwapi/SynchronizationTest.java index 7fe24c68..42fbe23d 100644 --- a/src/test/java/bwapi/SynchronizationTest.java +++ b/src/test/java/bwapi/SynchronizationTest.java @@ -26,8 +26,8 @@ private boolean measureApproximateEquality(double expected, double actual, doubl private void assertWithin(String message, double expected, double actual, double margin) { assertTrue( - message + ": " + describeApproximateExpectation(expected, actual, margin), - measureApproximateEquality(expected, actual, margin)); + message + ": " + describeApproximateExpectation(expected, actual, margin), + measureApproximateEquality(expected, actual, margin)); } @Test @@ -161,10 +161,10 @@ public void async_MeasurePerformance_CopyingToBuffer() { final double maxObserved = 15; final double meanObserved = (minObserved + maxObserved) / 2; final double rangeObserved = (maxObserved - minObserved) / 2; - assertWithin("Copy to buffer: minimum", environment.metrics().copyingToBuffer.runningTotal.min, meanObserved, rangeObserved); - assertWithin("Copy to buffer: maximum", environment.metrics().copyingToBuffer.runningTotal.max, meanObserved, rangeObserved); - assertWithin("Copy to buffer: average", environment.metrics().copyingToBuffer.runningTotal.mean, meanObserved, rangeObserved); - assertWithin("Copy to buffer: previous", environment.metrics().copyingToBuffer.runningTotal.last, meanObserved, rangeObserved); + assertWithin("Copy to buffer: minimum", environment.metrics().getCopyingToBuffer().getRunningTotal().getMin(), meanObserved, rangeObserved); + assertWithin("Copy to buffer: maximum", environment.metrics().getCopyingToBuffer().getRunningTotal().getMax(), meanObserved, rangeObserved); + assertWithin("Copy to buffer: average", environment.metrics().getCopyingToBuffer().getRunningTotal().getMean(), meanObserved, rangeObserved); + assertWithin("Copy to buffer: previous", environment.metrics().getCopyingToBuffer().getRunningTotal().getLast(), meanObserved, rangeObserved); } @Test @@ -176,25 +176,25 @@ public void async_MeasurePerformance_FrameBufferSizeAndFramesBehind() { environment.configuration.maxFrameDurationMs = 20; environment.onFrame(5, () -> { - assertWithin("5: Frame buffer average", 0, environment.metrics().frameBufferSize.runningTotal.mean, 0.1); - assertWithin("5: Frame buffer minimum", 0, environment.metrics().frameBufferSize.runningTotal.min, 0.1); - assertWithin("5: Frame buffer maximum", 0, environment.metrics().frameBufferSize.runningTotal.max, 0.1); - assertWithin("5: Frame buffer previous", 0, environment.metrics().frameBufferSize.runningTotal.last, 0.1); - assertWithin("5: Frames behind average", 0, environment.metrics().framesBehind.runningTotal.mean, 0.1); - assertWithin("5: Frames behind minimum", 0, environment.metrics().framesBehind.runningTotal.min, 0.1); - assertWithin("5: Frames behind maximum", 0, environment.metrics().framesBehind.runningTotal.max, 0.1); - assertWithin("5: Frames behind previous", 0, environment.metrics().framesBehind.runningTotal.last, 0.1); + assertWithin("5: Frame buffer average", 0, environment.metrics().getFrameBufferSize().getRunningTotal().getMean(), 0.1); + assertWithin("5: Frame buffer minimum", 0, environment.metrics().getFrameBufferSize().getRunningTotal().getMin(), 0.1); + assertWithin("5: Frame buffer maximum", 0, environment.metrics().getFrameBufferSize().getRunningTotal().getMax(), 0.1); + assertWithin("5: Frame buffer previous", 0, environment.metrics().getFrameBufferSize().getRunningTotal().getLast(), 0.1); + assertWithin("5: Frames behind average", 0, environment.metrics().getFramesBehind().getRunningTotal().getMean(), 0.1); + assertWithin("5: Frames behind minimum", 0, environment.metrics().getFramesBehind().getRunningTotal().getMin(), 0.1); + assertWithin("5: Frames behind maximum", 0, environment.metrics().getFramesBehind().getRunningTotal().getMax(), 0.1); + assertWithin("5: Frames behind previous", 0, environment.metrics().getFramesBehind().getRunningTotal().getLast(), 0.1); sleepUnchecked(200); }); environment.onFrame(6, () -> { - assertWithin("6: Frame buffer average", 1 / 6.0 + 2 / 7.0, environment.metrics().frameBufferSize.runningTotal.mean, 0.1); - assertWithin("6: Frame buffer minimum", 0, environment.metrics().frameBufferSize.runningTotal.min, 0.1); - assertWithin("6: Frame buffer maximum", 2, environment.metrics().frameBufferSize.runningTotal.max, 0.1); - assertWithin("6: Frame buffer previous", 2, environment.metrics().frameBufferSize.runningTotal.last, 0.1); - assertWithin("6: Frames behind average", 1 / 6.0, environment.metrics().framesBehind.runningTotal.mean, 0.1); - assertWithin("6: Frames behind minimum", 0, environment.metrics().framesBehind.runningTotal.min, 0.1); - assertWithin("6: Frames behind maximum", 1, environment.metrics().framesBehind.runningTotal.max, 0.1); - assertWithin("6: Frames behind previous", 1, environment.metrics().framesBehind.runningTotal.last, 0.1); + assertWithin("6: Frame buffer average", 1 / 6.0 + 2 / 7.0, environment.metrics().getFrameBufferSize().getRunningTotal().getMean(), 0.1); + assertWithin("6: Frame buffer minimum", 0, environment.metrics().getFrameBufferSize().getRunningTotal().getMin(), 0.1); + assertWithin("6: Frame buffer maximum", 2, environment.metrics().getFrameBufferSize().getRunningTotal().getMax(), 0.1); + assertWithin("6: Frame buffer previous", 2, environment.metrics().getFrameBufferSize().getRunningTotal().getLast(), 0.1); + assertWithin("6: Frames behind average", 1 / 6.0, environment.metrics().getFramesBehind().getRunningTotal().getMean(), 0.1); + assertWithin("6: Frames behind minimum", 0, environment.metrics().getFramesBehind().getRunningTotal().getMin(), 0.1); + assertWithin("6: Frames behind maximum", 1, environment.metrics().getFramesBehind().getRunningTotal().getMax(), 0.1); + assertWithin("6: Frames behind previous", 1, environment.metrics().getFramesBehind().getRunningTotal().getLast(), 0.1); }); environment.runGame(8); @@ -218,26 +218,26 @@ public void MeasurePerformance_BotResponse() { sleepUnchecked(100); }); environment.onFrame(2, () -> { - assertWithin("2: Bot response average", 100, environment.metrics().botResponse.runningTotal.mean, MS_MARGIN); - assertWithin("2: Bot response minimum", 100, environment.metrics().botResponse.runningTotal.min, MS_MARGIN); - assertWithin("2: Bot response maximum", 100, environment.metrics().botResponse.runningTotal.max, MS_MARGIN); - assertWithin("2: Bot response previous", 100, environment.metrics().botResponse.runningTotal.last, MS_MARGIN); + assertWithin("2: Bot response average", 100, environment.metrics().getBotResponse().getRunningTotal().getMean(), MS_MARGIN); + assertWithin("2: Bot response minimum", 100, environment.metrics().getBotResponse().getRunningTotal().getMin(), MS_MARGIN); + assertWithin("2: Bot response maximum", 100, environment.metrics().getBotResponse().getRunningTotal().getMax(), MS_MARGIN); + assertWithin("2: Bot response previous", 100, environment.metrics().getBotResponse().getRunningTotal().getLast(), MS_MARGIN); sleepUnchecked(300); }); environment.onFrame(3, () -> { - assertWithin("3: Bot response average", 200, environment.metrics().botResponse.runningTotal.mean, MS_MARGIN); - assertWithin("3: Bot response minimum", 100, environment.metrics().botResponse.runningTotal.min, MS_MARGIN); - assertWithin("3: Bot response maximum", 300, environment.metrics().botResponse.runningTotal.max, MS_MARGIN); - assertWithin("3: Bot response previous", 300, environment.metrics().botResponse.runningTotal.last, MS_MARGIN); + assertWithin("3: Bot response average", 200, environment.metrics().getBotResponse().getRunningTotal().getMean(), MS_MARGIN); + assertWithin("3: Bot response minimum", 100, environment.metrics().getBotResponse().getRunningTotal().getMin(), MS_MARGIN); + assertWithin("3: Bot response maximum", 300, environment.metrics().getBotResponse().getRunningTotal().getMax(), MS_MARGIN); + assertWithin("3: Bot response previous", 300, environment.metrics().getBotResponse().getRunningTotal().getLast(), MS_MARGIN); sleepUnchecked(200); }); environment.runGame(4); - assertWithin("Final: Bot response average", 200, environment.metrics().botResponse.runningTotal.mean, MS_MARGIN); - assertWithin("Final: Bot response minimum", 100, environment.metrics().botResponse.runningTotal.min, MS_MARGIN); - assertWithin("Final: Bot response maximum", 300, environment.metrics().botResponse.runningTotal.max, MS_MARGIN); - assertWithin("Final: Bot response previous", 200, environment.metrics().botResponse.runningTotal.last, MS_MARGIN); + assertWithin("Final: Bot response average", 200, environment.metrics().getBotResponse().getRunningTotal().getMean(), MS_MARGIN); + assertWithin("Final: Bot response minimum", 100, environment.metrics().getBotResponse().getRunningTotal().getMin(), MS_MARGIN); + assertWithin("Final: Bot response maximum", 300, environment.metrics().getBotResponse().getRunningTotal().getMax(), MS_MARGIN); + assertWithin("Final: Bot response previous", 200, environment.metrics().getBotResponse().getRunningTotal().getLast(), MS_MARGIN); } @Test @@ -250,8 +250,8 @@ public void MeasurePerformance_BotIdle() { environment.configuration.unlimitedFrameZero = true; environment.setBwapiDelayMs(bwapiDelayMs); environment.runGame(frames); - double expected = environment.metrics().copyingToBuffer.runningTotal.mean + bwapiDelayMs; - assertWithin("Bot Idle: Average", environment.metrics().botIdle.runningTotal.mean, expected, MS_MARGIN); + double expected = environment.metrics().getCopyingToBuffer().getRunningTotal().getMean() + bwapiDelayMs; + assertWithin("Bot Idle: Average", environment.metrics().getBotIdle().getRunningTotal().getMean(), expected, MS_MARGIN); } @Test @@ -268,7 +268,7 @@ public void async_MeasurePerformance_IntentionallyBlocking() { environment.onFrame(2, () -> { assertWithin( "2: Intentionally blocking previous", - environment.metrics().intentionallyBlocking.runningTotal.last, + environment.metrics().getIntentionallyBlocking().getRunningTotal().getLast(), frameDelayMs - environment.configuration.asyncFrameBufferCapacity * environment.configuration.maxFrameDurationMs, MS_MARGIN); sleepUnchecked(100); From e021d194dd065e1e2c0530d7ebc64d6931be84a7 Mon Sep 17 00:00:00 2001 From: dgant Date: Sun, 4 Oct 2020 23:16:47 -0400 Subject: [PATCH 44/56] Put configuration properties behind getters and chain-setters --- src/main/java/bwapi/BWClient.java | 8 +- .../java/bwapi/BWClientConfiguration.java | 75 ++++++++++++++++-- src/main/java/bwapi/BotWrapper.java | 18 ++--- src/main/java/bwapi/Client.java | 16 ++-- src/main/java/bwapi/FrameBuffer.java | 2 +- src/main/java/bwapi/PerformanceMetrics.java | 2 +- .../bwapi/SynchronizationEnvironment.java | 4 +- src/test/java/bwapi/SynchronizationTest.java | 77 +++++++++++-------- 8 files changed, 135 insertions(+), 67 deletions(-) diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index f4544a89..33ece553 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -47,7 +47,7 @@ public BWClientConfiguration getConfiguration() { * @return Whether the current frame should be subject to timing. */ boolean doTime() { - return ! configuration.unlimitedFrameZero || (client.isConnected() && client.clientData().gameData().getFrameCount() > 0); + return ! configuration.getUnlimitedFrameZero() || (client.isConnected() && client.clientData().gameData().getFrameCount() > 0); } /** @@ -81,7 +81,7 @@ public void startGame() { @Deprecated public void startGame(boolean autoContinue) { BWClientConfiguration configuration = new BWClientConfiguration(); - configuration.autoContinue = autoContinue; + configuration.withAutoContinue(autoContinue); startGame(configuration); } @@ -99,7 +99,7 @@ public void startGame(BWClientConfiguration gameConfiguration) { // Use reduced priority to encourage Windows to give priority to StarCraft.exe/BWAPI. // If BWAPI doesn't get priority, it may not detect completion of a frame on our end in timely fashion. Thread.currentThread().setName("JBWAPI Client"); - if (configuration.async) { + if (configuration.getAsync()) { Thread.currentThread().setPriority(4); } @@ -135,7 +135,7 @@ public void startGame(BWClientConfiguration gameConfiguration) { } } botWrapper.endGame(); - } while (configuration.autoContinue); + } while (configuration.getAutoContinue()); } /** diff --git a/src/main/java/bwapi/BWClientConfiguration.java b/src/main/java/bwapi/BWClientConfiguration.java index 7614b3c9..9865dfbb 100644 --- a/src/main/java/bwapi/BWClientConfiguration.java +++ b/src/main/java/bwapi/BWClientConfiguration.java @@ -8,12 +8,26 @@ public class BWClientConfiguration { /** * Set to `true` for more explicit error messages (which might spam the terminal). */ - public boolean debugConnection; + public BWClientConfiguration withDebugConnection(boolean value) { + debugConnection = value; + return this; + } + boolean getDebugConnection() { + return debugConnection; + } + private boolean debugConnection; /** * When true, restarts the client loop when a game ends, allowing the client to play multiple games without restarting. */ - public boolean autoContinue = false; + public BWClientConfiguration withAutoContinue(boolean value) { + autoContinue = value; + return this; + } + boolean getAutoContinue() { + return autoContinue; + } + private boolean autoContinue = false; /** * 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, @@ -23,7 +37,14 @@ public class BWClientConfiguration { * Performance metrics omit the frame as an outlier. * Asynchronous operation will block until the bot's event handlers are complete. */ - public boolean unlimitedFrameZero = true; + public BWClientConfiguration withUnlimitedFrameZero(boolean value) { + unlimitedFrameZero = value; + return this; + } + boolean getUnlimitedFrameZero() { + return unlimitedFrameZero; + } + private boolean unlimitedFrameZero = true; /** * The maximum amount of time the bot is supposed to spend on a single frame. @@ -31,7 +52,14 @@ public class BWClientConfiguration { * In synchronous mode, JBWAPI is not empowered to prevent the bot to exceed this amount, but will record overruns in performance metrics. * Real-time human play typically uses the "fastest" game speed, which has 42.86ms (42,860ns) between frames. */ - public int maxFrameDurationMs = 40; + public BWClientConfiguration withMaxFrameDurationMs(int value) { + maxFrameDurationMs = value; + return this; + } + int getMaxFrameDurationMs() { + return maxFrameDurationMs; + } + private int maxFrameDurationMs = 40; /** * Runs the bot in asynchronous mode. Asynchronous mode helps attempt to ensure that the bot adheres to real-time performance constraints. @@ -44,13 +72,27 @@ public class BWClientConfiguration { * 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 * issuing commands later than intended, and a marginally larger memory footprint. */ - public boolean async = false; + public BWClientConfiguration withAsync(boolean value) { + async = value; + return this; + } + boolean getAsync() { + return async; + } + private boolean async = false; /** * The maximum number of frames to buffer while waiting on a bot. * Each frame buffered adds about 33 megabytes to JBWAPI's memory footprint. */ - public int asyncFrameBufferCapacity = 10; + public BWClientConfiguration withAsyncFrameBufferCapacity(int size) { + asyncFrameBufferCapacity = size; + return this; + } + int getAsyncFrameBufferCapacity() { + return asyncFrameBufferCapacity; + } + private int asyncFrameBufferCapacity = 10; /** * Enables thread-unsafe async mode. @@ -59,17 +101,34 @@ public class BWClientConfiguration { * This should enhance performance by allowing the bot to act while the frame is copied, but poses unidentified risk due to * the non-thread-safe switc from shared memory reads to frame buffer reads. */ - public boolean asyncUnsafe = false; + public BWClientConfiguration withAsyncUnsafe(boolean value) { + asyncUnsafe = value; + return this; + } + boolean getAsyncUnsafe() { + return asyncUnsafe; + } + private boolean asyncUnsafe = false; /** * Toggles verbose logging, particularly of synchronization steps. */ - public boolean logVerbosely = false; + public BWClientConfiguration withLogVerbosely(boolean value) { + logVerbosely = value; + return this; + } + boolean getLogVerbosely() { + return logVerbosely; + } + private boolean logVerbosely = false; /** * Checks that the configuration is in a valid state. Throws an IllegalArgumentException if it isn't. */ void validate() { + if (asyncUnsafe && ! async) { + throw new IllegalArgumentException("asyncUnsafe mode needs async mode."); + } if (async && maxFrameDurationMs < 0) { 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)."); } diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java index 444915cb..46076dc2 100644 --- a/src/main/java/bwapi/BotWrapper.java +++ b/src/main/java/bwapi/BotWrapper.java @@ -24,14 +24,14 @@ class BotWrapper { BotWrapper(BWClientConfiguration configuration, BWEventListener eventListener) { this.configuration = configuration; this.eventListener = eventListener; - frameBuffer = configuration.async ? new FrameBuffer(configuration) : null; + frameBuffer = configuration.getAsync() ? new FrameBuffer(configuration) : null; } /** * Resets the BotWrapper for a new botGame. */ void startNewGame(ByteBuffer liveData, PerformanceMetrics performanceMetrics) { - if (configuration.async) { + if (configuration.getAsync()) { frameBuffer.initialize(liveData, performanceMetrics); } this.performanceMetrics = performanceMetrics; @@ -73,10 +73,10 @@ private void setUnsafeReadReady(boolean value) { * Handles the arrival of a new frame from BWAPI */ void onFrame() { - if (configuration.async) { + if (configuration.getAsync()) { configuration.log("Main: onFrame asynchronous start"); long startNanos = System.nanoTime(); - long endNanos = startNanos + configuration.maxFrameDurationMs * 1000000; + long endNanos = startNanos + configuration.getMaxFrameDurationMs() * 1000000; if (botThread == null) { configuration.log("Main: Starting bot thread"); botThread = createBotThread(); @@ -89,7 +89,7 @@ void onFrame() { // Unsafe mode: // If the frame buffer is empty (meaning the bot must be idle) // allow the bot to read directly from shared memory while we copy it over - if (configuration.asyncUnsafe) { + if (configuration.getAsyncUnsafe()) { frameBuffer.lockSize.lock(); try { if (frameBuffer.empty()) { @@ -123,7 +123,7 @@ void onFrame() { // We don't synchronize on calls which access the buffer // (to avoid tens of thousands of synchronized calls per frame) // so there's no guarantee of safety here. - if (configuration.asyncUnsafe && frameBuffer.size() == 1) { + if (configuration.getAsyncUnsafe() && frameBuffer.size() == 1) { configuration.log("Main: Weaning bot off live data"); botGame.clientData().setBuffer(frameBuffer.peek()); } @@ -135,7 +135,7 @@ void onFrame() { throw new RuntimeException(lastThrow); } - if (configuration.unlimitedFrameZero && frame == 0) { + if (configuration.getUnlimitedFrameZero() && frame == 0) { configuration.log("Main: Waiting indefinitely on frame #" + frame); frameBuffer.conditionSize.await(); } else { @@ -242,12 +242,12 @@ private void handleEvents() { gameOver = gameOver || gameData.getEvents(i).getType() == EventType.MatchEnd; } - if (configuration.async) { + if (configuration.getAsync()) { performanceMetrics.getFramesBehind().record(Math.max(1, frameBuffer.framesBuffered()) - 1); } performanceMetrics.getBotResponse().timeIf( - ! gameOver && (gameData.getFrameCount() > 0 || ! configuration.unlimitedFrameZero), + ! gameOver && (gameData.getFrameCount() > 0 || ! configuration.getUnlimitedFrameZero()), () -> { for (int i = 0; i < gameData.getEventCount(); i++) { EventHandler.operation(eventListener, botGame, gameData.getEvents(i)); diff --git a/src/main/java/bwapi/Client.java b/src/main/java/bwapi/Client.java index 692e4523..6dd647bf 100644 --- a/src/main/java/bwapi/Client.java +++ b/src/main/java/bwapi/Client.java @@ -82,7 +82,7 @@ void reconnect() { } private void disconnect() { - if (bwClient.getConfiguration().debugConnection) { + if (bwClient.getConfiguration().getDebugConnection()) { System.err.print("Disconnect called by: "); System.err.println(Thread.currentThread().getStackTrace()[2]); } @@ -133,7 +133,7 @@ boolean connect() { } catch (Exception e) { System.err.println("Unable to map Game table."); - if (bwClient.getConfiguration().debugConnection) { + if (bwClient.getConfiguration().getDebugConnection()) { e.printStackTrace(); } return false; @@ -167,7 +167,7 @@ boolean connect() { } catch (Exception e) { System.err.println("Unable to open communications pipe: " + communicationPipe); - if (bwClient.getConfiguration().debugConnection) { + if (bwClient.getConfiguration().getDebugConnection()) { e.printStackTrace(); } gameTableFileHandle = null; @@ -183,7 +183,7 @@ boolean connect() { } catch (Exception e) { System.err.println("Unable to open shared memory mapping: " + sharedMemoryName); - if (bwClient.getConfiguration().debugConnection) { + if (bwClient.getConfiguration().getDebugConnection()) { e.printStackTrace(); } pipeObjectHandle = null; @@ -196,7 +196,7 @@ boolean connect() { } catch (Exception e) { System.err.println("Unable to map game data."); - if (bwClient.getConfiguration().debugConnection) { + if (bwClient.getConfiguration().getDebugConnection()) { e.printStackTrace(); } return false; @@ -217,7 +217,7 @@ boolean connect() { } catch (Exception e) { System.err.println("Unable to read pipe object."); - if (bwClient.getConfiguration().debugConnection) { + if (bwClient.getConfiguration().getDebugConnection()) { e.printStackTrace(); } disconnect(); @@ -245,7 +245,7 @@ void sendFrameReceiveFrame() { } catch (Exception e) { System.err.println("failed, disconnecting"); - if (bwClient.getConfiguration().debugConnection) { + if (bwClient.getConfiguration().getDebugConnection()) { e.printStackTrace(); } disconnect(); @@ -272,7 +272,7 @@ void sendFrameReceiveFrame() { frameReady = pipeObjectHandle.readByte() == 2; } catch (Exception e) { System.err.println("failed, disconnecting"); - if (bwClient.getConfiguration().debugConnection) { + if (bwClient.getConfiguration().getDebugConnection()) { e.printStackTrace(); } disconnect(); diff --git a/src/main/java/bwapi/FrameBuffer.java b/src/main/java/bwapi/FrameBuffer.java index fcda6d1f..4c570757 100644 --- a/src/main/java/bwapi/FrameBuffer.java +++ b/src/main/java/bwapi/FrameBuffer.java @@ -27,7 +27,7 @@ class FrameBuffer { final Condition conditionSize = lockSize.newCondition(); FrameBuffer(BWClientConfiguration configuration) { - this.capacity = configuration.asyncFrameBufferCapacity; + this.capacity = configuration.getAsyncFrameBufferCapacity(); this.configuration = configuration; while(dataBuffer.size() < capacity) { dataBuffer.add(ByteBuffer.allocateDirect(BUFFER_SIZE)); diff --git a/src/main/java/bwapi/PerformanceMetrics.java b/src/main/java/bwapi/PerformanceMetrics.java index 63d4e899..1ea0dc56 100644 --- a/src/main/java/bwapi/PerformanceMetrics.java +++ b/src/main/java/bwapi/PerformanceMetrics.java @@ -229,7 +229,7 @@ public void reset() { flushSideEffects = new PerformanceMetric(this, "Time flushing side effects", 1, 3, 5); botResponse = new PerformanceMetric(this, "Duration of bot event handlers", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); botIdle = new PerformanceMetric(this, "Time bot spent idle", Long.MAX_VALUE); - clientIdle = new PerformanceMetric(this, "Time client spent waiting for bot", configuration.maxFrameDurationMs); + clientIdle = new PerformanceMetric(this, "Time client spent waiting for bot", configuration.getMaxFrameDurationMs()); excessSleep = new PerformanceMetric(this, "Excess duration of client sleep", 1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); numberOfEvents = new PerformanceMetric(this, "Number of events received from BWAPI", 1, 2, 3, 4, 5, 6, 8, 10, 15, 20); numberOfEventsTimesDurationReceiveToSent = new PerformanceMetric(this, "Number of events received from BWAPI, multiplied by the receive-to-sent duration of that frame", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); diff --git a/src/test/java/bwapi/SynchronizationEnvironment.java b/src/test/java/bwapi/SynchronizationEnvironment.java index 858920e5..5a13de31 100644 --- a/src/test/java/bwapi/SynchronizationEnvironment.java +++ b/src/test/java/bwapi/SynchronizationEnvironment.java @@ -84,10 +84,10 @@ void runGame() { void runGame(int onEndFrame) { this.onEndFrame = onEndFrame; - if (configuration.async) { + if (configuration.getAsync()) { final long MEGABYTE = 1024 * 1024; long memoryFree = Runtime.getRuntime().freeMemory() / MEGABYTE; - long memoryRequired = configuration.asyncFrameBufferCapacity * ClientData.GameData.SIZE / MEGABYTE; + long memoryRequired = configuration.getAsyncFrameBufferCapacity() * ClientData.GameData.SIZE / MEGABYTE; assertTrue( "Unit test needs to be run with sufficient memory to allocate frame buffer. Has " + memoryFree diff --git a/src/test/java/bwapi/SynchronizationTest.java b/src/test/java/bwapi/SynchronizationTest.java index 42fbe23d..23a79227 100644 --- a/src/test/java/bwapi/SynchronizationTest.java +++ b/src/test/java/bwapi/SynchronizationTest.java @@ -33,7 +33,7 @@ private void assertWithin(String message, double expected, double actual, double @Test public void sync_IfException_ThrowException() { SynchronizationEnvironment environment = new SynchronizationEnvironment(); - environment.configuration.async = false; + environment.configuration.withAsync(false); environment.onFrame(0, () -> { throw new RuntimeException("Simulated bot exception"); }); assertThrows(RuntimeException.class, environment::runGame); } @@ -42,8 +42,9 @@ public void sync_IfException_ThrowException() { public void async_IfException_ThrowException() { // An exception in the bot thread must be re-thrown by the main thread. SynchronizationEnvironment environment = new SynchronizationEnvironment(); - environment.configuration.async = true; - environment.configuration.asyncFrameBufferCapacity = 3; + environment.configuration + .withAsync(true) + .withAsyncFrameBufferCapacity(3); environment.onFrame(0, () -> { throw new RuntimeException("Simulated bot exception"); }); assertThrows(RuntimeException.class, environment::runGame); } @@ -51,9 +52,10 @@ public void async_IfException_ThrowException() { @Test public void sync_IfDelay_ThenNoBuffer() { SynchronizationEnvironment environment = new SynchronizationEnvironment(); - environment.configuration.async = false; - environment.configuration.maxFrameDurationMs = 1; - environment.configuration.asyncFrameBufferCapacity = 3; + environment.configuration + .withAsync(false) + .withMaxFrameDurationMs(1) + .withAsyncFrameBufferCapacity(3); IntStream.range(0, 5).forEach(frame -> { environment.onFrame(frame, () -> { @@ -70,9 +72,10 @@ public void sync_IfDelay_ThenNoBuffer() { @Test public void async_IfBotDelay_ThenClientBuffers() { SynchronizationEnvironment environment = new SynchronizationEnvironment(); - environment.configuration.async = true; - environment.configuration.maxFrameDurationMs = 10; - environment.configuration.asyncFrameBufferCapacity = 4; + environment.configuration + .withAsync(true) + .withMaxFrameDurationMs(10) + .withAsyncFrameBufferCapacity(4); environment.onFrame(1, () -> { sleepUnchecked(50); @@ -93,9 +96,10 @@ public void async_IfBotDelay_ThenClientBuffers() { @Test public void async_IfBotDelay_ThenClientStalls() { SynchronizationEnvironment environment = new SynchronizationEnvironment(); - environment.configuration.async = true; - environment.configuration.maxFrameDurationMs = 50; - environment.configuration.asyncFrameBufferCapacity = 5; + environment.configuration + .withAsync(true) + .withMaxFrameDurationMs(50) + .withAsyncFrameBufferCapacity(5); environment.onFrame(1, () -> { sleepUnchecked(125); @@ -118,10 +122,11 @@ public void async_IfBotDelay_ThenClientStalls() { @Test public void async_IfFrameZeroWaitsEnabled_ThenAllowInfiniteTime() { SynchronizationEnvironment environment = new SynchronizationEnvironment(); - environment.configuration.async = true; - environment.configuration.unlimitedFrameZero = true; - environment.configuration.maxFrameDurationMs = 5; - environment.configuration.asyncFrameBufferCapacity = 2; + environment.configuration + .withAsync(true) + .withUnlimitedFrameZero(true) + .withMaxFrameDurationMs(5) + .withAsyncFrameBufferCapacity(2); environment.onFrame(0, () -> { sleepUnchecked(50); @@ -136,10 +141,11 @@ public void async_IfFrameZeroWaitsEnabled_ThenAllowInfiniteTime() { @Test public void async_IfFrameZeroWaitsDisabled_ThenClientBuffers() { SynchronizationEnvironment environment = new SynchronizationEnvironment(); - environment.configuration.async = true; - environment.configuration.unlimitedFrameZero = false; - environment.configuration.maxFrameDurationMs = 5; - environment.configuration.asyncFrameBufferCapacity = 2; + environment.configuration + .withAsync(true) + .withUnlimitedFrameZero(false) + .withMaxFrameDurationMs(5) + .withAsyncFrameBufferCapacity(2); environment.onFrame(0, () -> { sleepUnchecked(50); @@ -155,7 +161,7 @@ public void async_IfFrameZeroWaitsDisabled_ThenClientBuffers() { public void async_MeasurePerformance_CopyingToBuffer() { // Somewhat lazy test; just verify that we're getting sane values SynchronizationEnvironment environment = new SynchronizationEnvironment(); - environment.configuration.async = true; + environment.configuration.withAsync(true); environment.runGame(20); final double minObserved = 0.25; final double maxObserved = 15; @@ -170,10 +176,11 @@ public void async_MeasurePerformance_CopyingToBuffer() { @Test public void async_MeasurePerformance_FrameBufferSizeAndFramesBehind() { SynchronizationEnvironment environment = new SynchronizationEnvironment(); - environment.configuration.async = true; - environment.configuration.unlimitedFrameZero = true; - environment.configuration.asyncFrameBufferCapacity = 3; - environment.configuration.maxFrameDurationMs = 20; + environment.configuration + .withAsync(true) + .withUnlimitedFrameZero(true) + .withAsyncFrameBufferCapacity(3) + .withMaxFrameDurationMs(20); environment.onFrame(5, () -> { assertWithin("5: Frame buffer average", 0, environment.metrics().getFrameBufferSize().getRunningTotal().getMean(), 0.1); @@ -212,7 +219,7 @@ public void MeasurePerformance_BotResponse() { // Frame zero appears to take an extra 60ms, so let's disable timing for it // (and also verify that we omit frame zero from performance metrics) - environment.configuration.unlimitedFrameZero = true; + environment.configuration.withUnlimitedFrameZero(true); environment.onFrame(1, () -> { sleepUnchecked(100); @@ -245,9 +252,10 @@ public void MeasurePerformance_BotIdle() { final long bwapiDelayMs = 10; final int frames = 10; SynchronizationEnvironment environment = new SynchronizationEnvironment(); - environment.configuration.async = true; - environment.configuration.asyncFrameBufferCapacity = 3; - environment.configuration.unlimitedFrameZero = true; + environment.configuration + .withAsync(true) + .withAsyncFrameBufferCapacity(3) + .withUnlimitedFrameZero(true); environment.setBwapiDelayMs(bwapiDelayMs); environment.runGame(frames); double expected = environment.metrics().getCopyingToBuffer().getRunningTotal().getMean() + bwapiDelayMs; @@ -257,10 +265,11 @@ public void MeasurePerformance_BotIdle() { @Test public void async_MeasurePerformance_IntentionallyBlocking() { SynchronizationEnvironment environment = new SynchronizationEnvironment(); - environment.configuration.async = true; - environment.configuration.unlimitedFrameZero = true; - environment.configuration.asyncFrameBufferCapacity = 2; - environment.configuration.maxFrameDurationMs = 20; + environment.configuration + .withAsync(true) + .withUnlimitedFrameZero(true) + .withAsyncFrameBufferCapacity(2) + .withMaxFrameDurationMs(20); final int frameDelayMs = 100; environment.onFrame(1, () -> { sleepUnchecked(100); @@ -269,7 +278,7 @@ public void async_MeasurePerformance_IntentionallyBlocking() { assertWithin( "2: Intentionally blocking previous", environment.metrics().getIntentionallyBlocking().getRunningTotal().getLast(), - frameDelayMs - environment.configuration.asyncFrameBufferCapacity * environment.configuration.maxFrameDurationMs, + frameDelayMs - environment.configuration.getAsyncFrameBufferCapacity() * environment.configuration.getMaxFrameDurationMs(), MS_MARGIN); sleepUnchecked(100); }); From 736ff2c854846cb652a1316024e4699434df1303 Mon Sep 17 00:00:00 2001 From: dgant Date: Mon, 5 Oct 2020 15:58:18 -0400 Subject: [PATCH 45/56] Configuration now can not be modified after game starts --- src/main/java/bwapi/BWClient.java | 2 +- .../java/bwapi/BWClientConfiguration.java | 18 +- src/main/java/bwapi/BotWrapper.java | 22 +- src/main/java/bwapi/ClientData.java | 1718 ++++++++--------- src/main/java/bwapi/EventHandler.java | 6 +- src/main/java/bwapi/Game.java | 2 +- src/test/java/DumpToClient.java | 16 +- src/test/java/bwapi/ClientDataBenchmark.java | 4 +- src/test/java/bwapi/GameBuilder.java | 3 +- src/test/java/bwapi/GameTest.java | 4 +- src/test/java/bwapi/PointTest.java | 8 +- 11 files changed, 907 insertions(+), 896 deletions(-) diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index 33ece553..26ca9a7d 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -91,7 +91,7 @@ public void startGame(boolean autoContinue) { * @param gameConfiguration Settings for playing games with this client. */ public void startGame(BWClientConfiguration gameConfiguration) { - gameConfiguration.validate(); + gameConfiguration.validateAndLock(); this.configuration = gameConfiguration; this.performanceMetrics = new PerformanceMetrics(configuration); botWrapper = new BotWrapper(configuration, eventListener); diff --git a/src/main/java/bwapi/BWClientConfiguration.java b/src/main/java/bwapi/BWClientConfiguration.java index 9865dfbb..b72705aa 100644 --- a/src/main/java/bwapi/BWClientConfiguration.java +++ b/src/main/java/bwapi/BWClientConfiguration.java @@ -9,6 +9,7 @@ public class BWClientConfiguration { * Set to `true` for more explicit error messages (which might spam the terminal). */ public BWClientConfiguration withDebugConnection(boolean value) { + throwIfLocked(); debugConnection = value; return this; } @@ -21,6 +22,7 @@ boolean getDebugConnection() { * When true, restarts the client loop when a game ends, allowing the client to play multiple games without restarting. */ public BWClientConfiguration withAutoContinue(boolean value) { + throwIfLocked(); autoContinue = value; return this; } @@ -38,6 +40,7 @@ boolean getAutoContinue() { * Asynchronous operation will block until the bot's event handlers are complete. */ public BWClientConfiguration withUnlimitedFrameZero(boolean value) { + throwIfLocked(); unlimitedFrameZero = value; return this; } @@ -53,6 +56,7 @@ boolean getUnlimitedFrameZero() { * Real-time human play typically uses the "fastest" game speed, which has 42.86ms (42,860ns) between frames. */ public BWClientConfiguration withMaxFrameDurationMs(int value) { + throwIfLocked(); maxFrameDurationMs = value; return this; } @@ -73,6 +77,7 @@ int getMaxFrameDurationMs() { * issuing commands later than intended, and a marginally larger memory footprint. */ public BWClientConfiguration withAsync(boolean value) { + throwIfLocked(); async = value; return this; } @@ -86,6 +91,7 @@ boolean getAsync() { * Each frame buffered adds about 33 megabytes to JBWAPI's memory footprint. */ public BWClientConfiguration withAsyncFrameBufferCapacity(int size) { + throwIfLocked(); asyncFrameBufferCapacity = size; return this; } @@ -102,6 +108,7 @@ int getAsyncFrameBufferCapacity() { * the non-thread-safe switc from shared memory reads to frame buffer reads. */ public BWClientConfiguration withAsyncUnsafe(boolean value) { + throwIfLocked(); asyncUnsafe = value; return this; } @@ -114,6 +121,7 @@ boolean getAsyncUnsafe() { * Toggles verbose logging, particularly of synchronization steps. */ public BWClientConfiguration withLogVerbosely(boolean value) { + throwIfLocked(); logVerbosely = value; return this; } @@ -125,7 +133,7 @@ boolean getLogVerbosely() { /** * Checks that the configuration is in a valid state. Throws an IllegalArgumentException if it isn't. */ - void validate() { + void validateAndLock() { if (asyncUnsafe && ! async) { throw new IllegalArgumentException("asyncUnsafe mode needs async mode."); } @@ -135,6 +143,14 @@ void validate() { if (async && asyncFrameBufferCapacity < 1) { throw new IllegalArgumentException("asyncFrameBufferCapacity needs to be a positive number (There needs to be at least one frame buffer)."); } + locked = true; + } + private boolean locked = false; + + void throwIfLocked() { + if (locked) { + throw new RuntimeException("Configuration can not be modified after the game has started"); + } } void log(String value) { diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java index 46076dc2..b76a0ae8 100644 --- a/src/main/java/bwapi/BotWrapper.java +++ b/src/main/java/bwapi/BotWrapper.java @@ -36,7 +36,7 @@ void startNewGame(ByteBuffer liveData, PerformanceMetrics performanceMetrics) { } this.performanceMetrics = performanceMetrics; botGame = new Game(); - botGame.clientData().setBuffer(liveData); + botGame.botClientData().setBuffer(liveData); liveClientData.setBuffer(liveData); this.liveData = liveData; botThread = null; @@ -76,7 +76,7 @@ void onFrame() { if (configuration.getAsync()) { configuration.log("Main: onFrame asynchronous start"); long startNanos = System.nanoTime(); - long endNanos = startNanos + configuration.getMaxFrameDurationMs() * 1000000; + long endNanos = startNanos + (long) configuration.getMaxFrameDurationMs() * 1000000; if (botThread == null) { configuration.log("Main: Starting bot thread"); botThread = createBotThread(); @@ -94,7 +94,7 @@ void onFrame() { try { if (frameBuffer.empty()) { configuration.log("Main: Putting bot on live data"); - botGame.clientData().setBuffer(liveData); + botGame.botClientData().setBuffer(liveData); setUnsafeReadReady(true); } else { setUnsafeReadReady(false); @@ -125,7 +125,7 @@ void onFrame() { // so there's no guarantee of safety here. if (configuration.getAsyncUnsafe() && frameBuffer.size() == 1) { configuration.log("Main: Weaning bot off live data"); - botGame.clientData().setBuffer(frameBuffer.peek()); + botGame.botClientData().setBuffer(frameBuffer.peek()); } // Make bot exceptions fall through to the main thread. @@ -209,7 +209,7 @@ private Thread createBotThread() { // TODO: Maybe we should point it at live data from here? } else { configuration.log("Bot: Peeking next frame from buffer"); - botGame.clientData().setBuffer(frameBuffer.peek()); + botGame.botClientData().setBuffer(frameBuffer.peek()); } configuration.log("Bot: Handling events on frame #" + botGame.getFrameCount()); @@ -235,11 +235,11 @@ private Thread createBotThread() { } private void handleEvents() { - ClientData.GameData gameData = botGame.clientData().gameData(); + ClientData.GameData botGameData = botGame.botClientData().gameData(); // Populate gameOver before invoking event handlers (in case the bot throws) - for (int i = 0; i < gameData.getEventCount(); i++) { - gameOver = gameOver || gameData.getEvents(i).getType() == EventType.MatchEnd; + for (int i = 0; i < botGameData.getEventCount(); i++) { + gameOver = gameOver || botGameData.getEvents(i).getType() == EventType.MatchEnd; } if (configuration.getAsync()) { @@ -247,10 +247,10 @@ private void handleEvents() { } performanceMetrics.getBotResponse().timeIf( - ! gameOver && (gameData.getFrameCount() > 0 || ! configuration.getUnlimitedFrameZero()), + ! gameOver && (botGameData.getFrameCount() > 0 || ! configuration.getUnlimitedFrameZero()), () -> { - for (int i = 0; i < gameData.getEventCount(); i++) { - EventHandler.operation(eventListener, botGame, gameData.getEvents(i)); + for (int i = 0; i < botGameData.getEventCount(); i++) { + EventHandler.operation(eventListener, botGame, botGameData.getEvents(i)); } }); } diff --git a/src/main/java/bwapi/ClientData.java b/src/main/java/bwapi/ClientData.java index 2dca5ac7..a5908147 100644 --- a/src/main/java/bwapi/ClientData.java +++ b/src/main/java/bwapi/ClientData.java @@ -4,7 +4,7 @@ final class ClientData { private WrappedBuffer buffer; private GameData gameData; ClientData() { - gameData = new GameData(0); + gameData = new ClientData.GameData(0); } GameData gameData() { return gameData; @@ -12,1988 +12,1988 @@ GameData gameData() { void setBuffer(ByteBuffer buffer) { this.buffer = new WrappedBuffer(buffer); } - class UnitCommand { - static final int SIZE = 24; + class UnitCommand { + static final int SIZE = 24; private int myOffset; - public UnitCommand(int myOffset) { + UnitCommand(int myOffset) { this.myOffset = myOffset; } - int getTid() { - int offset = myOffset + 0; + int getTid() { + int offset = myOffset + 0; return buffer.getInt(offset); } void setTid(int value) { - buffer.putInt(myOffset + 0, value); + buffer.putInt(myOffset + 0, value); } - int getUnitIndex() { - int offset = myOffset + 4; + int getUnitIndex() { + int offset = myOffset + 4; return buffer.getInt(offset); } void setUnitIndex(int value) { - buffer.putInt(myOffset + 4, value); + buffer.putInt(myOffset + 4, value); } - int getTargetIndex() { - int offset = myOffset + 8; + int getTargetIndex() { + int offset = myOffset + 8; return buffer.getInt(offset); } void setTargetIndex(int value) { - buffer.putInt(myOffset + 8, value); + buffer.putInt(myOffset + 8, value); } - int getX() { - int offset = myOffset + 12; + int getX() { + int offset = myOffset + 12; return buffer.getInt(offset); } void setX(int value) { - buffer.putInt(myOffset + 12, value); + buffer.putInt(myOffset + 12, value); } - int getY() { - int offset = myOffset + 16; + int getY() { + int offset = myOffset + 16; return buffer.getInt(offset); } void setY(int value) { - buffer.putInt(myOffset + 16, value); + buffer.putInt(myOffset + 16, value); } - int getExtra() { - int offset = myOffset + 20; + int getExtra() { + int offset = myOffset + 20; return buffer.getInt(offset); } void setExtra(int value) { - buffer.putInt(myOffset + 20, value); + buffer.putInt(myOffset + 20, value); } } - class GameData { - static final int SIZE = 33017048; + class GameData { + static final int SIZE = 33017048; private int myOffset; - GameData(int myOffset) { + GameData(int myOffset) { this.myOffset = myOffset; } - int getClient_version() { - int offset = myOffset + 0; + int getClient_version() { + int offset = myOffset + 0; return buffer.getInt(offset); } void setClient_version(int value) { - buffer.putInt(myOffset + 0, value); + buffer.putInt(myOffset + 0, value); } - int getRevision() { - int offset = myOffset + 4; + int getRevision() { + int offset = myOffset + 4; return buffer.getInt(offset); } void setRevision(int value) { - buffer.putInt(myOffset + 4, value); + buffer.putInt(myOffset + 4, value); } - boolean isDebug() { - int offset = myOffset + 8; + boolean isDebug() { + int offset = myOffset + 8; return buffer.getByte(offset) != 0; } void setIsDebug(boolean value) { - buffer.putByte(myOffset + 8, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 8, (byte) (value ? 1 : 0)); } - int getInstanceID() { - int offset = myOffset + 12; + int getInstanceID() { + int offset = myOffset + 12; return buffer.getInt(offset); } void setInstanceID(int value) { - buffer.putInt(myOffset + 12, value); + buffer.putInt(myOffset + 12, value); } - int getBotAPM_noselects() { - int offset = myOffset + 16; + int getBotAPM_noselects() { + int offset = myOffset + 16; return buffer.getInt(offset); } void setBotAPM_noselects(int value) { - buffer.putInt(myOffset + 16, value); + buffer.putInt(myOffset + 16, value); } - int getBotAPM_selects() { - int offset = myOffset + 20; + int getBotAPM_selects() { + int offset = myOffset + 20; return buffer.getInt(offset); } void setBotAPM_selects(int value) { - buffer.putInt(myOffset + 20, value); + buffer.putInt(myOffset + 20, value); } - int getForceCount() { - int offset = myOffset + 24; + int getForceCount() { + int offset = myOffset + 24; return buffer.getInt(offset); } void setForceCount(int value) { - buffer.putInt(myOffset + 24, value); + buffer.putInt(myOffset + 24, value); } - ForceData getForces(int i) { - int offset = myOffset + 28 + 32 * 1 * i; + ForceData getForces(int i) { + int offset = myOffset + 28 + 32 * 1 * i; return new ForceData(offset); } - int getPlayerCount() { - int offset = myOffset + 188; + int getPlayerCount() { + int offset = myOffset + 188; return buffer.getInt(offset); } void setPlayerCount(int value) { - buffer.putInt(myOffset + 188, value); + buffer.putInt(myOffset + 188, value); } - PlayerData getPlayers(int i) { - int offset = myOffset + 192 + 5788 * 1 * i; + PlayerData getPlayers(int i) { + int offset = myOffset + 192 + 5788 * 1 * i; return new PlayerData(offset); } - int getInitialUnitCount() { - int offset = myOffset + 69648; + int getInitialUnitCount() { + int offset = myOffset + 69648; return buffer.getInt(offset); } void setInitialUnitCount(int value) { - buffer.putInt(myOffset + 69648, value); + buffer.putInt(myOffset + 69648, value); } - UnitData getUnits(int i) { - int offset = myOffset + 69656 + 336 * 1 * i; + UnitData getUnits(int i) { + int offset = myOffset + 69656 + 336 * 1 * i; return new UnitData(offset); } - int getUnitArray(int i) { - int offset = myOffset + 3429656 + 4 * 1 * i; + int getUnitArray(int i) { + int offset = myOffset + 3429656 + 4 * 1 * i; return buffer.getInt(offset); } void setUnitArray(int i, int value) { - buffer.putInt(myOffset + 3429656 + 4 * 1 * i, value); + buffer.putInt(myOffset + 3429656 + 4 * 1 * i, value); } - BulletData getBullets(int i) { - int offset = myOffset + 3436456 + 80 * 1 * i; + BulletData getBullets(int i) { + int offset = myOffset + 3436456 + 80 * 1 * i; return new BulletData(offset); } - int getNukeDotCount() { - int offset = myOffset + 3444456; + int getNukeDotCount() { + int offset = myOffset + 3444456; return buffer.getInt(offset); } void setNukeDotCount(int value) { - buffer.putInt(myOffset + 3444456, value); + buffer.putInt(myOffset + 3444456, value); } - Position getNukeDots(int i) { - int offset = myOffset + 3444460 + 8 * 1 * i; + Position getNukeDots(int i) { + int offset = myOffset + 3444460 + 8 * 1 * i; return new Position(offset); } - int getGameType() { - int offset = myOffset + 3446060; + int getGameType() { + int offset = myOffset + 3446060; return buffer.getInt(offset); } void setGameType(int value) { - buffer.putInt(myOffset + 3446060, value); + buffer.putInt(myOffset + 3446060, value); } - int getLatency() { - int offset = myOffset + 3446064; + int getLatency() { + int offset = myOffset + 3446064; return buffer.getInt(offset); } void setLatency(int value) { - buffer.putInt(myOffset + 3446064, value); + buffer.putInt(myOffset + 3446064, value); } - int getLatencyFrames() { - int offset = myOffset + 3446068; + int getLatencyFrames() { + int offset = myOffset + 3446068; return buffer.getInt(offset); } void setLatencyFrames(int value) { - buffer.putInt(myOffset + 3446068, value); + buffer.putInt(myOffset + 3446068, value); } - int getLatencyTime() { - int offset = myOffset + 3446072; + int getLatencyTime() { + int offset = myOffset + 3446072; return buffer.getInt(offset); } void setLatencyTime(int value) { - buffer.putInt(myOffset + 3446072, value); + buffer.putInt(myOffset + 3446072, value); } - int getRemainingLatencyFrames() { - int offset = myOffset + 3446076; + int getRemainingLatencyFrames() { + int offset = myOffset + 3446076; return buffer.getInt(offset); } void setRemainingLatencyFrames(int value) { - buffer.putInt(myOffset + 3446076, value); + buffer.putInt(myOffset + 3446076, value); } - int getRemainingLatencyTime() { - int offset = myOffset + 3446080; + int getRemainingLatencyTime() { + int offset = myOffset + 3446080; return buffer.getInt(offset); } void setRemainingLatencyTime(int value) { - buffer.putInt(myOffset + 3446080, value); + buffer.putInt(myOffset + 3446080, value); } - boolean getHasLatCom() { - int offset = myOffset + 3446084; + boolean getHasLatCom() { + int offset = myOffset + 3446084; return buffer.getByte(offset) != 0; } void setHasLatCom(boolean value) { - buffer.putByte(myOffset + 3446084, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 3446084, (byte) (value ? 1 : 0)); } - boolean getHasGUI() { - int offset = myOffset + 3446085; + boolean getHasGUI() { + int offset = myOffset + 3446085; return buffer.getByte(offset) != 0; } void setHasGUI(boolean value) { - buffer.putByte(myOffset + 3446085, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 3446085, (byte) (value ? 1 : 0)); } - int getReplayFrameCount() { - int offset = myOffset + 3446088; + int getReplayFrameCount() { + int offset = myOffset + 3446088; return buffer.getInt(offset); } void setReplayFrameCount(int value) { - buffer.putInt(myOffset + 3446088, value); + buffer.putInt(myOffset + 3446088, value); } - int getRandomSeed() { - int offset = myOffset + 3446092; + int getRandomSeed() { + int offset = myOffset + 3446092; return buffer.getInt(offset); } void setRandomSeed(int value) { - buffer.putInt(myOffset + 3446092, value); + buffer.putInt(myOffset + 3446092, value); } - int getFrameCount() { - int offset = myOffset + 3446096; + int getFrameCount() { + int offset = myOffset + 3446096; return buffer.getInt(offset); } void setFrameCount(int value) { - buffer.putInt(myOffset + 3446096, value); + buffer.putInt(myOffset + 3446096, value); } - int getElapsedTime() { - int offset = myOffset + 3446100; + int getElapsedTime() { + int offset = myOffset + 3446100; return buffer.getInt(offset); } void setElapsedTime(int value) { - buffer.putInt(myOffset + 3446100, value); + buffer.putInt(myOffset + 3446100, value); } - int getCountdownTimer() { - int offset = myOffset + 3446104; + int getCountdownTimer() { + int offset = myOffset + 3446104; return buffer.getInt(offset); } void setCountdownTimer(int value) { - buffer.putInt(myOffset + 3446104, value); + buffer.putInt(myOffset + 3446104, value); } - int getFps() { - int offset = myOffset + 3446108; + int getFps() { + int offset = myOffset + 3446108; return buffer.getInt(offset); } void setFps(int value) { - buffer.putInt(myOffset + 3446108, value); + buffer.putInt(myOffset + 3446108, value); } - double getAverageFPS() { - int offset = myOffset + 3446112; + double getAverageFPS() { + int offset = myOffset + 3446112; return buffer.getDouble(offset); } void setAverageFPS(double value) { - buffer.putDouble(myOffset + 3446112, value); + buffer.putDouble(myOffset + 3446112, value); } - int getMouseX() { - int offset = myOffset + 3446120; + int getMouseX() { + int offset = myOffset + 3446120; return buffer.getInt(offset); } void setMouseX(int value) { - buffer.putInt(myOffset + 3446120, value); + buffer.putInt(myOffset + 3446120, value); } - int getMouseY() { - int offset = myOffset + 3446124; + int getMouseY() { + int offset = myOffset + 3446124; return buffer.getInt(offset); } void setMouseY(int value) { - buffer.putInt(myOffset + 3446124, value); + buffer.putInt(myOffset + 3446124, value); } - boolean getMouseState(int i) { - int offset = myOffset + 3446128 + 1 * 1 * i; + boolean getMouseState(int i) { + int offset = myOffset + 3446128 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setMouseState(int i, boolean value) { - buffer.putByte(myOffset + 3446128 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 3446128 + 1 * 1 * i, (byte) (value ? 1 : 0)); } - boolean getKeyState(int i) { - int offset = myOffset + 3446131 + 1 * 1 * i; + boolean getKeyState(int i) { + int offset = myOffset + 3446131 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setKeyState(int i, boolean value) { - buffer.putByte(myOffset + 3446131 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 3446131 + 1 * 1 * i, (byte) (value ? 1 : 0)); } - int getScreenX() { - int offset = myOffset + 3446388; + int getScreenX() { + int offset = myOffset + 3446388; return buffer.getInt(offset); } void setScreenX(int value) { - buffer.putInt(myOffset + 3446388, value); + buffer.putInt(myOffset + 3446388, value); } - int getScreenY() { - int offset = myOffset + 3446392; + int getScreenY() { + int offset = myOffset + 3446392; return buffer.getInt(offset); } void setScreenY(int value) { - buffer.putInt(myOffset + 3446392, value); + buffer.putInt(myOffset + 3446392, value); } - boolean getFlags(int i) { - int offset = myOffset + 3446396 + 1 * 1 * i; + boolean getFlags(int i) { + int offset = myOffset + 3446396 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setFlags(int i, boolean value) { - buffer.putByte(myOffset + 3446396 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 3446396 + 1 * 1 * i, (byte) (value ? 1 : 0)); } - int getMapWidth() { - int offset = myOffset + 3446400; + int getMapWidth() { + int offset = myOffset + 3446400; return buffer.getInt(offset); } void setMapWidth(int value) { - buffer.putInt(myOffset + 3446400, value); + buffer.putInt(myOffset + 3446400, value); } - int getMapHeight() { - int offset = myOffset + 3446404; + int getMapHeight() { + int offset = myOffset + 3446404; return buffer.getInt(offset); } void setMapHeight(int value) { - buffer.putInt(myOffset + 3446404, value); + buffer.putInt(myOffset + 3446404, value); } - String getMapFileName() { - int offset = myOffset + 3446408; + String getMapFileName() { + int offset = myOffset + 3446408; return buffer.getString(offset, 261); } void setMapFileName(String value) { - buffer.putString(myOffset + 3446408, 261, value); + buffer.putString(myOffset + 3446408, 261, value); } - String getMapPathName() { - int offset = myOffset + 3446669; + String getMapPathName() { + int offset = myOffset + 3446669; return buffer.getString(offset, 261); } void setMapPathName(String value) { - buffer.putString(myOffset + 3446669, 261, value); + buffer.putString(myOffset + 3446669, 261, value); } - String getMapName() { - int offset = myOffset + 3446930; + String getMapName() { + int offset = myOffset + 3446930; return buffer.getString(offset, 33); } void setMapName(String value) { - buffer.putString(myOffset + 3446930, 33, value); + buffer.putString(myOffset + 3446930, 33, value); } - String getMapHash() { - int offset = myOffset + 3446963; + String getMapHash() { + int offset = myOffset + 3446963; return buffer.getString(offset, 41); } void setMapHash(String value) { - buffer.putString(myOffset + 3446963, 41, value); + buffer.putString(myOffset + 3446963, 41, value); } - int getGroundHeight(int i, int j) { - int offset = myOffset + 3447004 + 4 * 1 * j + 4 * 256 * i; + int getGroundHeight(int i, int j) { + int offset = myOffset + 3447004 + 4 * 1 * j + 4 * 256 * i; return buffer.getInt(offset); } void setGetGroundHeight(int i, int j, int value) { - buffer.putInt(myOffset + 3447004 + 4 * 1 * j + 4 * 256 * i, value); + buffer.putInt(myOffset + 3447004 + 4 * 1 * j + 4 * 256 * i, value); } - boolean isWalkable(int i, int j) { - int offset = myOffset + 3709148 + 1 * 1 * j + 1 * 1024 * i; + boolean isWalkable(int i, int j) { + int offset = myOffset + 3709148 + 1 * 1 * j + 1 * 1024 * i; return buffer.getByte(offset) != 0; } void setIsWalkable(int i, int j, boolean value) { - buffer.putByte(myOffset + 3709148 + 1 * 1 * j + 1 * 1024 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 3709148 + 1 * 1 * j + 1 * 1024 * i, (byte) (value ? 1 : 0)); } - boolean isBuildable(int i, int j) { - int offset = myOffset + 4757724 + 1 * 1 * j + 1 * 256 * i; + boolean isBuildable(int i, int j) { + int offset = myOffset + 4757724 + 1 * 1 * j + 1 * 256 * i; return buffer.getByte(offset) != 0; } void setIsBuildable(int i, int j, boolean value) { - buffer.putByte(myOffset + 4757724 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 4757724 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); } - boolean isVisible(int i, int j) { - int offset = myOffset + 4823260 + 1 * 1 * j + 1 * 256 * i; + boolean isVisible(int i, int j) { + int offset = myOffset + 4823260 + 1 * 1 * j + 1 * 256 * i; return buffer.getByte(offset) != 0; } void setIsVisible(int i, int j, boolean value) { - buffer.putByte(myOffset + 4823260 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 4823260 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); } - boolean isExplored(int i, int j) { - int offset = myOffset + 4888796 + 1 * 1 * j + 1 * 256 * i; + boolean isExplored(int i, int j) { + int offset = myOffset + 4888796 + 1 * 1 * j + 1 * 256 * i; return buffer.getByte(offset) != 0; } void setIsExplored(int i, int j, boolean value) { - buffer.putByte(myOffset + 4888796 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 4888796 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); } - boolean getHasCreep(int i, int j) { - int offset = myOffset + 4954332 + 1 * 1 * j + 1 * 256 * i; + boolean getHasCreep(int i, int j) { + int offset = myOffset + 4954332 + 1 * 1 * j + 1 * 256 * i; return buffer.getByte(offset) != 0; } void setHasCreep(int i, int j, boolean value) { - buffer.putByte(myOffset + 4954332 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 4954332 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); } - boolean isOccupied(int i, int j) { - int offset = myOffset + 5019868 + 1 * 1 * j + 1 * 256 * i; + boolean isOccupied(int i, int j) { + int offset = myOffset + 5019868 + 1 * 1 * j + 1 * 256 * i; return buffer.getByte(offset) != 0; } void setIsOccupied(int i, int j, boolean value) { - buffer.putByte(myOffset + 5019868 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 5019868 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); } - short getMapTileRegionId(int i, int j) { - int offset = myOffset + 5085404 + 2 * 1 * j + 2 * 256 * i; + short getMapTileRegionId(int i, int j) { + int offset = myOffset + 5085404 + 2 * 1 * j + 2 * 256 * i; return buffer.getShort(offset); } void setMapTileRegionId(int i, int j, short value) { - buffer.putShort(myOffset + 5085404 + 2 * 1 * j + 2 * 256 * i, value); + buffer.putShort(myOffset + 5085404 + 2 * 1 * j + 2 * 256 * i, value); } - short getMapSplitTilesMiniTileMask(int i) { - int offset = myOffset + 5216476 + 2 * 1 * i; + short getMapSplitTilesMiniTileMask(int i) { + int offset = myOffset + 5216476 + 2 * 1 * i; return buffer.getShort(offset); } void setMapSplitTilesMiniTileMask(int i, short value) { - buffer.putShort(myOffset + 5216476 + 2 * 1 * i, value); + buffer.putShort(myOffset + 5216476 + 2 * 1 * i, value); } - short getMapSplitTilesRegion1(int i) { - int offset = myOffset + 5226476 + 2 * 1 * i; + short getMapSplitTilesRegion1(int i) { + int offset = myOffset + 5226476 + 2 * 1 * i; return buffer.getShort(offset); } void setMapSplitTilesRegion1(int i, short value) { - buffer.putShort(myOffset + 5226476 + 2 * 1 * i, value); + buffer.putShort(myOffset + 5226476 + 2 * 1 * i, value); } - short getMapSplitTilesRegion2(int i) { - int offset = myOffset + 5236476 + 2 * 1 * i; + short getMapSplitTilesRegion2(int i) { + int offset = myOffset + 5236476 + 2 * 1 * i; return buffer.getShort(offset); } void setMapSplitTilesRegion2(int i, short value) { - buffer.putShort(myOffset + 5236476 + 2 * 1 * i, value); + buffer.putShort(myOffset + 5236476 + 2 * 1 * i, value); } - int getRegionCount() { - int offset = myOffset + 5246476; + int getRegionCount() { + int offset = myOffset + 5246476; return buffer.getInt(offset); } void setRegionCount(int value) { - buffer.putInt(myOffset + 5246476, value); + buffer.putInt(myOffset + 5246476, value); } - RegionData getRegions(int i) { - int offset = myOffset + 5246480 + 1068 * 1 * i; + RegionData getRegions(int i) { + int offset = myOffset + 5246480 + 1068 * 1 * i; return new RegionData(offset); } - int getStartLocationCount() { - int offset = myOffset + 10586480; + int getStartLocationCount() { + int offset = myOffset + 10586480; return buffer.getInt(offset); } void setStartLocationCount(int value) { - buffer.putInt(myOffset + 10586480, value); + buffer.putInt(myOffset + 10586480, value); } - Position getStartLocations(int i) { - int offset = myOffset + 10586484 + 8 * 1 * i; + Position getStartLocations(int i) { + int offset = myOffset + 10586484 + 8 * 1 * i; return new Position(offset); } - boolean isInGame() { - int offset = myOffset + 10586548; + boolean isInGame() { + int offset = myOffset + 10586548; return buffer.getByte(offset) != 0; } void setIsInGame(boolean value) { - buffer.putByte(myOffset + 10586548, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 10586548, (byte) (value ? 1 : 0)); } - boolean isMultiplayer() { - int offset = myOffset + 10586549; + boolean isMultiplayer() { + int offset = myOffset + 10586549; return buffer.getByte(offset) != 0; } void setIsMultiplayer(boolean value) { - buffer.putByte(myOffset + 10586549, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 10586549, (byte) (value ? 1 : 0)); } - boolean isBattleNet() { - int offset = myOffset + 10586550; + boolean isBattleNet() { + int offset = myOffset + 10586550; return buffer.getByte(offset) != 0; } void setIsBattleNet(boolean value) { - buffer.putByte(myOffset + 10586550, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 10586550, (byte) (value ? 1 : 0)); } - boolean isPaused() { - int offset = myOffset + 10586551; + boolean isPaused() { + int offset = myOffset + 10586551; return buffer.getByte(offset) != 0; } void setIsPaused(boolean value) { - buffer.putByte(myOffset + 10586551, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 10586551, (byte) (value ? 1 : 0)); } - boolean isReplay() { - int offset = myOffset + 10586552; + boolean isReplay() { + int offset = myOffset + 10586552; return buffer.getByte(offset) != 0; } void setIsReplay(boolean value) { - buffer.putByte(myOffset + 10586552, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 10586552, (byte) (value ? 1 : 0)); } - int getSelectedUnitCount() { - int offset = myOffset + 10586556; + int getSelectedUnitCount() { + int offset = myOffset + 10586556; return buffer.getInt(offset); } void setSelectedUnitCount(int value) { - buffer.putInt(myOffset + 10586556, value); + buffer.putInt(myOffset + 10586556, value); } - int getSelectedUnits(int i) { - int offset = myOffset + 10586560 + 4 * 1 * i; + int getSelectedUnits(int i) { + int offset = myOffset + 10586560 + 4 * 1 * i; return buffer.getInt(offset); } void setSelectedUnits(int i, int value) { - buffer.putInt(myOffset + 10586560 + 4 * 1 * i, value); + buffer.putInt(myOffset + 10586560 + 4 * 1 * i, value); } - int getSelf() { - int offset = myOffset + 10586608; + int getSelf() { + int offset = myOffset + 10586608; return buffer.getInt(offset); } void setSelf(int value) { - buffer.putInt(myOffset + 10586608, value); + buffer.putInt(myOffset + 10586608, value); } - int getEnemy() { - int offset = myOffset + 10586612; + int getEnemy() { + int offset = myOffset + 10586612; return buffer.getInt(offset); } void setEnemy(int value) { - buffer.putInt(myOffset + 10586612, value); + buffer.putInt(myOffset + 10586612, value); } - int getNeutral() { - int offset = myOffset + 10586616; + int getNeutral() { + int offset = myOffset + 10586616; return buffer.getInt(offset); } void setNeutral(int value) { - buffer.putInt(myOffset + 10586616, value); + buffer.putInt(myOffset + 10586616, value); } - int getEventCount() { - int offset = myOffset + 10586620; + int getEventCount() { + int offset = myOffset + 10586620; return buffer.getInt(offset); } void setEventCount(int value) { - buffer.putInt(myOffset + 10586620, value); + buffer.putInt(myOffset + 10586620, value); } - Event getEvents(int i) { - int offset = myOffset + 10586624 + 12 * 1 * i; + Event getEvents(int i) { + int offset = myOffset + 10586624 + 12 * 1 * i; return new Event(offset); } - int getEventStringCount() { - int offset = myOffset + 10706624; + int getEventStringCount() { + int offset = myOffset + 10706624; return buffer.getInt(offset); } void setEventStringCount(int value) { - buffer.putInt(myOffset + 10706624, value); + buffer.putInt(myOffset + 10706624, value); } - String getEventStrings(int i) { - int offset = myOffset + 10706628 + 1 * 256 * i; + String getEventStrings(int i) { + int offset = myOffset + 10706628 + 1 * 256 * i; return buffer.getString(offset, 256); } void setEventStrings(int i, String value) { - buffer.putString(myOffset + 10706628 + 1 * 256 * i, 256, value); + buffer.putString(myOffset + 10706628 + 1 * 256 * i, 256, value); } - int getStringCount() { - int offset = myOffset + 10962628; + int getStringCount() { + int offset = myOffset + 10962628; return buffer.getInt(offset); } void setStringCount(int value) { - buffer.putInt(myOffset + 10962628, value); + buffer.putInt(myOffset + 10962628, value); } - String getStrings(int i) { - int offset = myOffset + 10962632 + 1 * 1024 * i; + String getStrings(int i) { + int offset = myOffset + 10962632 + 1 * 1024 * i; return buffer.getString(offset, 1024); } void setStrings(int i, String value) { - buffer.putString(myOffset + 10962632 + 1 * 1024 * i, 1024, value); + buffer.putString(myOffset + 10962632 + 1 * 1024 * i, 1024, value); } - int getShapeCount() { - int offset = myOffset + 31442632; + int getShapeCount() { + int offset = myOffset + 31442632; return buffer.getInt(offset); } void setShapeCount(int value) { - buffer.putInt(myOffset + 31442632, value); + buffer.putInt(myOffset + 31442632, value); } - Shape getShapes(int i) { - int offset = myOffset + 31442636 + 40 * 1 * i; + Shape getShapes(int i) { + int offset = myOffset + 31442636 + 40 * 1 * i; return new Shape(offset); } - int getCommandCount() { - int offset = myOffset + 32242636; + int getCommandCount() { + int offset = myOffset + 32242636; return buffer.getInt(offset); } void setCommandCount(int value) { - buffer.putInt(myOffset + 32242636, value); + buffer.putInt(myOffset + 32242636, value); } - Command getCommands(int i) { - int offset = myOffset + 32242640 + 12 * 1 * i; + Command getCommands(int i) { + int offset = myOffset + 32242640 + 12 * 1 * i; return new Command(offset); } - int getUnitCommandCount() { - int offset = myOffset + 32482640; + int getUnitCommandCount() { + int offset = myOffset + 32482640; return buffer.getInt(offset); } void setUnitCommandCount(int value) { - buffer.putInt(myOffset + 32482640, value); + buffer.putInt(myOffset + 32482640, value); } - UnitCommand getUnitCommands(int i) { - int offset = myOffset + 32482644 + 24 * 1 * i; + UnitCommand getUnitCommands(int i) { + int offset = myOffset + 32482644 + 24 * 1 * i; return new UnitCommand(offset); } - int getUnitSearchSize() { - int offset = myOffset + 32962644; + int getUnitSearchSize() { + int offset = myOffset + 32962644; return buffer.getInt(offset); } void setUnitSearchSize(int value) { - buffer.putInt(myOffset + 32962644, value); + buffer.putInt(myOffset + 32962644, value); } - unitFinder getXUnitSearch(int i) { - int offset = myOffset + 32962648 + 8 * 1 * i; + unitFinder getXUnitSearch(int i) { + int offset = myOffset + 32962648 + 8 * 1 * i; return new unitFinder(offset); } - unitFinder getYUnitSearch(int i) { - int offset = myOffset + 32989848 + 8 * 1 * i; + unitFinder getYUnitSearch(int i) { + int offset = myOffset + 32989848 + 8 * 1 * i; return new unitFinder(offset); } } - class Shape { - static final int SIZE = 40; + class Shape { + static final int SIZE = 40; private int myOffset; - public Shape(int myOffset) { + Shape(int myOffset) { this.myOffset = myOffset; } - ShapeType getType() { - int offset = myOffset + 0; + ShapeType getType() { + int offset = myOffset + 0; return ShapeType.idToEnum[buffer.getInt(offset)]; } - void setType(ShapeType value) { - buffer.putInt(myOffset + 0, value.id); + void setType(ShapeType value) { + buffer.putInt(myOffset + 0, value.id); } - CoordinateType getCtype() { - int offset = myOffset + 4; + CoordinateType getCtype() { + int offset = myOffset + 4; return CoordinateType.idToEnum[buffer.getInt(offset)]; } - void setCtype(CoordinateType value) { - buffer.putInt(myOffset + 4, value.id); + void setCtype(CoordinateType value) { + buffer.putInt(myOffset + 4, value.id); } - int getX1() { - int offset = myOffset + 8; + int getX1() { + int offset = myOffset + 8; return buffer.getInt(offset); } void setX1(int value) { - buffer.putInt(myOffset + 8, value); + buffer.putInt(myOffset + 8, value); } - int getY1() { - int offset = myOffset + 12; + int getY1() { + int offset = myOffset + 12; return buffer.getInt(offset); } void setY1(int value) { - buffer.putInt(myOffset + 12, value); + buffer.putInt(myOffset + 12, value); } - int getX2() { - int offset = myOffset + 16; + int getX2() { + int offset = myOffset + 16; return buffer.getInt(offset); } void setX2(int value) { - buffer.putInt(myOffset + 16, value); + buffer.putInt(myOffset + 16, value); } - int getY2() { - int offset = myOffset + 20; + int getY2() { + int offset = myOffset + 20; return buffer.getInt(offset); } void setY2(int value) { - buffer.putInt(myOffset + 20, value); + buffer.putInt(myOffset + 20, value); } - int getExtra1() { - int offset = myOffset + 24; + int getExtra1() { + int offset = myOffset + 24; return buffer.getInt(offset); } void setExtra1(int value) { - buffer.putInt(myOffset + 24, value); + buffer.putInt(myOffset + 24, value); } - int getExtra2() { - int offset = myOffset + 28; + int getExtra2() { + int offset = myOffset + 28; return buffer.getInt(offset); } void setExtra2(int value) { - buffer.putInt(myOffset + 28, value); + buffer.putInt(myOffset + 28, value); } - int getColor() { - int offset = myOffset + 32; + int getColor() { + int offset = myOffset + 32; return buffer.getInt(offset); } void setColor(int value) { - buffer.putInt(myOffset + 32, value); + buffer.putInt(myOffset + 32, value); } - boolean isSolid() { - int offset = myOffset + 36; + boolean isSolid() { + int offset = myOffset + 36; return buffer.getByte(offset) != 0; } void setIsSolid(boolean value) { - buffer.putByte(myOffset + 36, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 36, (byte) (value ? 1 : 0)); } } - class Command { - static final int SIZE = 12; + class Command { + static final int SIZE = 12; private int myOffset; - public Command(int myOffset) { + Command(int myOffset) { this.myOffset = myOffset; } - CommandType getType() { - int offset = myOffset + 0; + CommandType getType() { + int offset = myOffset + 0; return CommandType.idToEnum[buffer.getInt(offset)]; } - void setType(CommandType value) { - buffer.putInt(myOffset + 0, value.id); + void setType(CommandType value) { + buffer.putInt(myOffset + 0, value.id); } - int getValue1() { - int offset = myOffset + 4; + int getValue1() { + int offset = myOffset + 4; return buffer.getInt(offset); } void setValue1(int value) { - buffer.putInt(myOffset + 4, value); + buffer.putInt(myOffset + 4, value); } - int getValue2() { - int offset = myOffset + 8; + int getValue2() { + int offset = myOffset + 8; return buffer.getInt(offset); } void setValue2(int value) { - buffer.putInt(myOffset + 8, value); + buffer.putInt(myOffset + 8, value); } } - class Position { - static final int SIZE = 8; + class Position { + static final int SIZE = 8; private int myOffset; - public Position(int myOffset) { + Position(int myOffset) { this.myOffset = myOffset; } - int getX() { - int offset = myOffset + 0; + int getX() { + int offset = myOffset + 0; return buffer.getInt(offset); } void setX(int value) { - buffer.putInt(myOffset + 0, value); + buffer.putInt(myOffset + 0, value); } - int getY() { - int offset = myOffset + 4; + int getY() { + int offset = myOffset + 4; return buffer.getInt(offset); } void setY(int value) { - buffer.putInt(myOffset + 4, value); + buffer.putInt(myOffset + 4, value); } } - class Event { - static final int SIZE = 12; + class Event { + static final int SIZE = 12; private int myOffset; - public Event(int myOffset) { + Event(int myOffset) { this.myOffset = myOffset; } - EventType getType() { - int offset = myOffset + 0; + EventType getType() { + int offset = myOffset + 0; return EventType.idToEnum[buffer.getInt(offset)]; } - void setType(EventType value) { - buffer.putInt(myOffset + 0, value.id); + void setType(EventType value) { + buffer.putInt(myOffset + 0, value.id); } - int getV1() { - int offset = myOffset + 4; + int getV1() { + int offset = myOffset + 4; return buffer.getInt(offset); } void setV1(int value) { - buffer.putInt(myOffset + 4, value); + buffer.putInt(myOffset + 4, value); } - int getV2() { - int offset = myOffset + 8; + int getV2() { + int offset = myOffset + 8; return buffer.getInt(offset); } void setV2(int value) { - buffer.putInt(myOffset + 8, value); + buffer.putInt(myOffset + 8, value); } } - class RegionData { - static final int SIZE = 1068; + class RegionData { + static final int SIZE = 1068; private int myOffset; - public RegionData(int myOffset) { + RegionData(int myOffset) { this.myOffset = myOffset; } - int getId() { - int offset = myOffset + 0; + int getId() { + int offset = myOffset + 0; return buffer.getInt(offset); } void setId(int value) { - buffer.putInt(myOffset + 0, value); + buffer.putInt(myOffset + 0, value); } - int islandID() { - int offset = myOffset + 4; + int islandID() { + int offset = myOffset + 4; return buffer.getInt(offset); } void setIslandID(int value) { - buffer.putInt(myOffset + 4, value); + buffer.putInt(myOffset + 4, value); } - int getCenter_x() { - int offset = myOffset + 8; + int getCenter_x() { + int offset = myOffset + 8; return buffer.getInt(offset); } void setCenter_x(int value) { - buffer.putInt(myOffset + 8, value); + buffer.putInt(myOffset + 8, value); } - int getCenter_y() { - int offset = myOffset + 12; + int getCenter_y() { + int offset = myOffset + 12; return buffer.getInt(offset); } void setCenter_y(int value) { - buffer.putInt(myOffset + 12, value); + buffer.putInt(myOffset + 12, value); } - int getPriority() { - int offset = myOffset + 16; + int getPriority() { + int offset = myOffset + 16; return buffer.getInt(offset); } void setPriority(int value) { - buffer.putInt(myOffset + 16, value); + buffer.putInt(myOffset + 16, value); } - int getLeftMost() { - int offset = myOffset + 20; + int getLeftMost() { + int offset = myOffset + 20; return buffer.getInt(offset); } void setLeftMost(int value) { - buffer.putInt(myOffset + 20, value); + buffer.putInt(myOffset + 20, value); } - int getRightMost() { - int offset = myOffset + 24; + int getRightMost() { + int offset = myOffset + 24; return buffer.getInt(offset); } void setRightMost(int value) { - buffer.putInt(myOffset + 24, value); + buffer.putInt(myOffset + 24, value); } - int getTopMost() { - int offset = myOffset + 28; + int getTopMost() { + int offset = myOffset + 28; return buffer.getInt(offset); } void setTopMost(int value) { - buffer.putInt(myOffset + 28, value); + buffer.putInt(myOffset + 28, value); } - int getBottomMost() { - int offset = myOffset + 32; + int getBottomMost() { + int offset = myOffset + 32; return buffer.getInt(offset); } void setBottomMost(int value) { - buffer.putInt(myOffset + 32, value); + buffer.putInt(myOffset + 32, value); } - int getNeighborCount() { - int offset = myOffset + 36; + int getNeighborCount() { + int offset = myOffset + 36; return buffer.getInt(offset); } void setNeighborCount(int value) { - buffer.putInt(myOffset + 36, value); + buffer.putInt(myOffset + 36, value); } - int getNeighbors(int i) { - int offset = myOffset + 40 + 4 * 1 * i; + int getNeighbors(int i) { + int offset = myOffset + 40 + 4 * 1 * i; return buffer.getInt(offset); } void setNeighbors(int i, int value) { - buffer.putInt(myOffset + 40 + 4 * 1 * i, value); + buffer.putInt(myOffset + 40 + 4 * 1 * i, value); } - boolean isAccessible() { - int offset = myOffset + 1064; + boolean isAccessible() { + int offset = myOffset + 1064; return buffer.getByte(offset) != 0; } void setIsAccessible(boolean value) { - buffer.putByte(myOffset + 1064, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 1064, (byte) (value ? 1 : 0)); } - boolean isHigherGround() { - int offset = myOffset + 1065; + boolean isHigherGround() { + int offset = myOffset + 1065; return buffer.getByte(offset) != 0; } void setIsHigherGround(boolean value) { - buffer.putByte(myOffset + 1065, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 1065, (byte) (value ? 1 : 0)); } } - class ForceData { - static final int SIZE = 32; + class ForceData { + static final int SIZE = 32; private int myOffset; - public ForceData(int myOffset) { + ForceData(int myOffset) { this.myOffset = myOffset; } - String getName() { - int offset = myOffset + 0; + String getName() { + int offset = myOffset + 0; return buffer.getString(offset, 32); } void setName(String value) { - buffer.putString(myOffset + 0, 32, value); + buffer.putString(myOffset + 0, 32, value); } } - class PlayerData { - static final int SIZE = 5788; + class PlayerData { + static final int SIZE = 5788; private int myOffset; - public PlayerData(int myOffset) { + PlayerData(int myOffset) { this.myOffset = myOffset; } - String getName() { - int offset = myOffset + 0; + String getName() { + int offset = myOffset + 0; return buffer.getString(offset, 25); } void setName(String value) { - buffer.putString(myOffset + 0, 25, value); + buffer.putString(myOffset + 0, 25, value); } - int getRace() { - int offset = myOffset + 28; + int getRace() { + int offset = myOffset + 28; return buffer.getInt(offset); } void setRace(int value) { - buffer.putInt(myOffset + 28, value); + buffer.putInt(myOffset + 28, value); } - int getType() { - int offset = myOffset + 32; + int getType() { + int offset = myOffset + 32; return buffer.getInt(offset); } void setType(int value) { - buffer.putInt(myOffset + 32, value); + buffer.putInt(myOffset + 32, value); } - int getForce() { - int offset = myOffset + 36; + int getForce() { + int offset = myOffset + 36; return buffer.getInt(offset); } void setForce(int value) { - buffer.putInt(myOffset + 36, value); + buffer.putInt(myOffset + 36, value); } - boolean isAlly(int i) { - int offset = myOffset + 40 + 1 * 1 * i; + boolean isAlly(int i) { + int offset = myOffset + 40 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setIsAlly(int i, boolean value) { - buffer.putByte(myOffset + 40 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 40 + 1 * 1 * i, (byte) (value ? 1 : 0)); } - boolean isEnemy(int i) { - int offset = myOffset + 52 + 1 * 1 * i; + boolean isEnemy(int i) { + int offset = myOffset + 52 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setIsEnemy(int i, boolean value) { - buffer.putByte(myOffset + 52 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 52 + 1 * 1 * i, (byte) (value ? 1 : 0)); } - boolean isNeutral() { - int offset = myOffset + 64; + boolean isNeutral() { + int offset = myOffset + 64; return buffer.getByte(offset) != 0; } void setIsNeutral(boolean value) { - buffer.putByte(myOffset + 64, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 64, (byte) (value ? 1 : 0)); } - int getStartLocationX() { - int offset = myOffset + 68; + int getStartLocationX() { + int offset = myOffset + 68; return buffer.getInt(offset); } void setStartLocationX(int value) { - buffer.putInt(myOffset + 68, value); + buffer.putInt(myOffset + 68, value); } - int getStartLocationY() { - int offset = myOffset + 72; + int getStartLocationY() { + int offset = myOffset + 72; return buffer.getInt(offset); } void setStartLocationY(int value) { - buffer.putInt(myOffset + 72, value); + buffer.putInt(myOffset + 72, value); } - boolean isVictorious() { - int offset = myOffset + 76; + boolean isVictorious() { + int offset = myOffset + 76; return buffer.getByte(offset) != 0; } void setIsVictorious(boolean value) { - buffer.putByte(myOffset + 76, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 76, (byte) (value ? 1 : 0)); } - boolean isDefeated() { - int offset = myOffset + 77; + boolean isDefeated() { + int offset = myOffset + 77; return buffer.getByte(offset) != 0; } void setIsDefeated(boolean value) { - buffer.putByte(myOffset + 77, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 77, (byte) (value ? 1 : 0)); } - boolean getLeftGame() { - int offset = myOffset + 78; + boolean getLeftGame() { + int offset = myOffset + 78; return buffer.getByte(offset) != 0; } void setLeftGame(boolean value) { - buffer.putByte(myOffset + 78, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 78, (byte) (value ? 1 : 0)); } - boolean isParticipating() { - int offset = myOffset + 79; + boolean isParticipating() { + int offset = myOffset + 79; return buffer.getByte(offset) != 0; } void setIsParticipating(boolean value) { - buffer.putByte(myOffset + 79, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 79, (byte) (value ? 1 : 0)); } - int getMinerals() { - int offset = myOffset + 80; + int getMinerals() { + int offset = myOffset + 80; return buffer.getInt(offset); } void setMinerals(int value) { - buffer.putInt(myOffset + 80, value); + buffer.putInt(myOffset + 80, value); } - int getGas() { - int offset = myOffset + 84; + int getGas() { + int offset = myOffset + 84; return buffer.getInt(offset); } void setGas(int value) { - buffer.putInt(myOffset + 84, value); + buffer.putInt(myOffset + 84, value); } - int getGatheredMinerals() { - int offset = myOffset + 88; + int getGatheredMinerals() { + int offset = myOffset + 88; return buffer.getInt(offset); } void setGatheredMinerals(int value) { - buffer.putInt(myOffset + 88, value); + buffer.putInt(myOffset + 88, value); } - int getGatheredGas() { - int offset = myOffset + 92; + int getGatheredGas() { + int offset = myOffset + 92; return buffer.getInt(offset); } void setGatheredGas(int value) { - buffer.putInt(myOffset + 92, value); + buffer.putInt(myOffset + 92, value); } - int getRepairedMinerals() { - int offset = myOffset + 96; + int getRepairedMinerals() { + int offset = myOffset + 96; return buffer.getInt(offset); } void setRepairedMinerals(int value) { - buffer.putInt(myOffset + 96, value); + buffer.putInt(myOffset + 96, value); } - int getRepairedGas() { - int offset = myOffset + 100; + int getRepairedGas() { + int offset = myOffset + 100; return buffer.getInt(offset); } void setRepairedGas(int value) { - buffer.putInt(myOffset + 100, value); + buffer.putInt(myOffset + 100, value); } - int getRefundedMinerals() { - int offset = myOffset + 104; + int getRefundedMinerals() { + int offset = myOffset + 104; return buffer.getInt(offset); } void setRefundedMinerals(int value) { - buffer.putInt(myOffset + 104, value); + buffer.putInt(myOffset + 104, value); } - int getRefundedGas() { - int offset = myOffset + 108; + int getRefundedGas() { + int offset = myOffset + 108; return buffer.getInt(offset); } void setRefundedGas(int value) { - buffer.putInt(myOffset + 108, value); + buffer.putInt(myOffset + 108, value); } - int getSupplyTotal(int i) { - int offset = myOffset + 112 + 4 * 1 * i; + int getSupplyTotal(int i) { + int offset = myOffset + 112 + 4 * 1 * i; return buffer.getInt(offset); } void setSupplyTotal(int i, int value) { - buffer.putInt(myOffset + 112 + 4 * 1 * i, value); + buffer.putInt(myOffset + 112 + 4 * 1 * i, value); } - int getSupplyUsed(int i) { - int offset = myOffset + 124 + 4 * 1 * i; + int getSupplyUsed(int i) { + int offset = myOffset + 124 + 4 * 1 * i; return buffer.getInt(offset); } void setSupplyUsed(int i, int value) { - buffer.putInt(myOffset + 124 + 4 * 1 * i, value); + buffer.putInt(myOffset + 124 + 4 * 1 * i, value); } - int getAllUnitCount(int i) { - int offset = myOffset + 136 + 4 * 1 * i; + int getAllUnitCount(int i) { + int offset = myOffset + 136 + 4 * 1 * i; return buffer.getInt(offset); } void setAllUnitCount(int i, int value) { - buffer.putInt(myOffset + 136 + 4 * 1 * i, value); + buffer.putInt(myOffset + 136 + 4 * 1 * i, value); } - int getVisibleUnitCount(int i) { - int offset = myOffset + 1072 + 4 * 1 * i; + int getVisibleUnitCount(int i) { + int offset = myOffset + 1072 + 4 * 1 * i; return buffer.getInt(offset); } void setVisibleUnitCount(int i, int value) { - buffer.putInt(myOffset + 1072 + 4 * 1 * i, value); + buffer.putInt(myOffset + 1072 + 4 * 1 * i, value); } - int getCompletedUnitCount(int i) { - int offset = myOffset + 2008 + 4 * 1 * i; + int getCompletedUnitCount(int i) { + int offset = myOffset + 2008 + 4 * 1 * i; return buffer.getInt(offset); } void setCompletedUnitCount(int i, int value) { - buffer.putInt(myOffset + 2008 + 4 * 1 * i, value); + buffer.putInt(myOffset + 2008 + 4 * 1 * i, value); } - int getDeadUnitCount(int i) { - int offset = myOffset + 2944 + 4 * 1 * i; + int getDeadUnitCount(int i) { + int offset = myOffset + 2944 + 4 * 1 * i; return buffer.getInt(offset); } void setDeadUnitCount(int i, int value) { - buffer.putInt(myOffset + 2944 + 4 * 1 * i, value); + buffer.putInt(myOffset + 2944 + 4 * 1 * i, value); } - int getKilledUnitCount(int i) { - int offset = myOffset + 3880 + 4 * 1 * i; + int getKilledUnitCount(int i) { + int offset = myOffset + 3880 + 4 * 1 * i; return buffer.getInt(offset); } void setKilledUnitCount(int i, int value) { - buffer.putInt(myOffset + 3880 + 4 * 1 * i, value); + buffer.putInt(myOffset + 3880 + 4 * 1 * i, value); } - int getUpgradeLevel(int i) { - int offset = myOffset + 4816 + 4 * 1 * i; + int getUpgradeLevel(int i) { + int offset = myOffset + 4816 + 4 * 1 * i; return buffer.getInt(offset); } void setUpgradeLevel(int i, int value) { - buffer.putInt(myOffset + 4816 + 4 * 1 * i, value); + buffer.putInt(myOffset + 4816 + 4 * 1 * i, value); } - boolean getHasResearched(int i) { - int offset = myOffset + 5068 + 1 * 1 * i; + boolean getHasResearched(int i) { + int offset = myOffset + 5068 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setHasResearched(int i, boolean value) { - buffer.putByte(myOffset + 5068 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 5068 + 1 * 1 * i, (byte) (value ? 1 : 0)); } - boolean isResearching(int i) { - int offset = myOffset + 5115 + 1 * 1 * i; + boolean isResearching(int i) { + int offset = myOffset + 5115 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setIsResearching(int i, boolean value) { - buffer.putByte(myOffset + 5115 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 5115 + 1 * 1 * i, (byte) (value ? 1 : 0)); } - boolean isUpgrading(int i) { - int offset = myOffset + 5162 + 1 * 1 * i; + boolean isUpgrading(int i) { + int offset = myOffset + 5162 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setIsUpgrading(int i, boolean value) { - buffer.putByte(myOffset + 5162 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 5162 + 1 * 1 * i, (byte) (value ? 1 : 0)); } - int getColor() { - int offset = myOffset + 5228; + int getColor() { + int offset = myOffset + 5228; return buffer.getInt(offset); } void setColor(int value) { - buffer.putInt(myOffset + 5228, value); + buffer.putInt(myOffset + 5228, value); } - int getTotalUnitScore() { - int offset = myOffset + 5232; + int getTotalUnitScore() { + int offset = myOffset + 5232; return buffer.getInt(offset); } void setTotalUnitScore(int value) { - buffer.putInt(myOffset + 5232, value); + buffer.putInt(myOffset + 5232, value); } - int getTotalKillScore() { - int offset = myOffset + 5236; + int getTotalKillScore() { + int offset = myOffset + 5236; return buffer.getInt(offset); } void setTotalKillScore(int value) { - buffer.putInt(myOffset + 5236, value); + buffer.putInt(myOffset + 5236, value); } - int getTotalBuildingScore() { - int offset = myOffset + 5240; + int getTotalBuildingScore() { + int offset = myOffset + 5240; return buffer.getInt(offset); } void setTotalBuildingScore(int value) { - buffer.putInt(myOffset + 5240, value); + buffer.putInt(myOffset + 5240, value); } - int getTotalRazingScore() { - int offset = myOffset + 5244; + int getTotalRazingScore() { + int offset = myOffset + 5244; return buffer.getInt(offset); } void setTotalRazingScore(int value) { - buffer.putInt(myOffset + 5244, value); + buffer.putInt(myOffset + 5244, value); } - int getCustomScore() { - int offset = myOffset + 5248; + int getCustomScore() { + int offset = myOffset + 5248; return buffer.getInt(offset); } void setCustomScore(int value) { - buffer.putInt(myOffset + 5248, value); + buffer.putInt(myOffset + 5248, value); } - int getMaxUpgradeLevel(int i) { - int offset = myOffset + 5252 + 4 * 1 * i; + int getMaxUpgradeLevel(int i) { + int offset = myOffset + 5252 + 4 * 1 * i; return buffer.getInt(offset); } void setMaxUpgradeLevel(int i, int value) { - buffer.putInt(myOffset + 5252 + 4 * 1 * i, value); + buffer.putInt(myOffset + 5252 + 4 * 1 * i, value); } - boolean isResearchAvailable(int i) { - int offset = myOffset + 5504 + 1 * 1 * i; + boolean isResearchAvailable(int i) { + int offset = myOffset + 5504 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setIsResearchAvailable(int i, boolean value) { - buffer.putByte(myOffset + 5504 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 5504 + 1 * 1 * i, (byte) (value ? 1 : 0)); } - boolean isUnitAvailable(int i) { - int offset = myOffset + 5551 + 1 * 1 * i; + boolean isUnitAvailable(int i) { + int offset = myOffset + 5551 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setIsUnitAvailable(int i, boolean value) { - buffer.putByte(myOffset + 5551 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 5551 + 1 * 1 * i, (byte) (value ? 1 : 0)); } } - class BulletData { - static final int SIZE = 80; + class BulletData { + static final int SIZE = 80; private int myOffset; - public BulletData(int myOffset) { + BulletData(int myOffset) { this.myOffset = myOffset; } - int getId() { - int offset = myOffset + 0; + int getId() { + int offset = myOffset + 0; return buffer.getInt(offset); } void setId(int value) { - buffer.putInt(myOffset + 0, value); + buffer.putInt(myOffset + 0, value); } - int getPlayer() { - int offset = myOffset + 4; + int getPlayer() { + int offset = myOffset + 4; return buffer.getInt(offset); } void setPlayer(int value) { - buffer.putInt(myOffset + 4, value); + buffer.putInt(myOffset + 4, value); } - int getType() { - int offset = myOffset + 8; + int getType() { + int offset = myOffset + 8; return buffer.getInt(offset); } void setType(int value) { - buffer.putInt(myOffset + 8, value); + buffer.putInt(myOffset + 8, value); } - int getSource() { - int offset = myOffset + 12; + int getSource() { + int offset = myOffset + 12; return buffer.getInt(offset); } void setSource(int value) { - buffer.putInt(myOffset + 12, value); + buffer.putInt(myOffset + 12, value); } - int getPositionX() { - int offset = myOffset + 16; + int getPositionX() { + int offset = myOffset + 16; return buffer.getInt(offset); } void setPositionX(int value) { - buffer.putInt(myOffset + 16, value); + buffer.putInt(myOffset + 16, value); } - int getPositionY() { - int offset = myOffset + 20; + int getPositionY() { + int offset = myOffset + 20; return buffer.getInt(offset); } void setPositionY(int value) { - buffer.putInt(myOffset + 20, value); + buffer.putInt(myOffset + 20, value); } - double getAngle() { - int offset = myOffset + 24; + double getAngle() { + int offset = myOffset + 24; return buffer.getDouble(offset); } void setAngle(double value) { - buffer.putDouble(myOffset + 24, value); + buffer.putDouble(myOffset + 24, value); } - double getVelocityX() { - int offset = myOffset + 32; + double getVelocityX() { + int offset = myOffset + 32; return buffer.getDouble(offset); } void setVelocityX(double value) { - buffer.putDouble(myOffset + 32, value); + buffer.putDouble(myOffset + 32, value); } - double getVelocityY() { - int offset = myOffset + 40; + double getVelocityY() { + int offset = myOffset + 40; return buffer.getDouble(offset); } void setVelocityY(double value) { - buffer.putDouble(myOffset + 40, value); + buffer.putDouble(myOffset + 40, value); } - int getTarget() { - int offset = myOffset + 48; + int getTarget() { + int offset = myOffset + 48; return buffer.getInt(offset); } void setTarget(int value) { - buffer.putInt(myOffset + 48, value); + buffer.putInt(myOffset + 48, value); } - int getTargetPositionX() { - int offset = myOffset + 52; + int getTargetPositionX() { + int offset = myOffset + 52; return buffer.getInt(offset); } void setTargetPositionX(int value) { - buffer.putInt(myOffset + 52, value); + buffer.putInt(myOffset + 52, value); } - int getTargetPositionY() { - int offset = myOffset + 56; + int getTargetPositionY() { + int offset = myOffset + 56; return buffer.getInt(offset); } void setTargetPositionY(int value) { - buffer.putInt(myOffset + 56, value); + buffer.putInt(myOffset + 56, value); } - int getRemoveTimer() { - int offset = myOffset + 60; + int getRemoveTimer() { + int offset = myOffset + 60; return buffer.getInt(offset); } void setRemoveTimer(int value) { - buffer.putInt(myOffset + 60, value); + buffer.putInt(myOffset + 60, value); } - boolean getExists() { - int offset = myOffset + 64; + boolean getExists() { + int offset = myOffset + 64; return buffer.getByte(offset) != 0; } void setExists(boolean value) { - buffer.putByte(myOffset + 64, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 64, (byte) (value ? 1 : 0)); } - boolean isVisible(int i) { - int offset = myOffset + 65 + 1 * 1 * i; + boolean isVisible(int i) { + int offset = myOffset + 65 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setIsVisible(int i, boolean value) { - buffer.putByte(myOffset + 65 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 65 + 1 * 1 * i, (byte) (value ? 1 : 0)); } } - class unitFinder { - static final int SIZE = 8; + class unitFinder { + static final int SIZE = 8; private int myOffset; - public unitFinder(int myOffset) { + unitFinder(int myOffset) { this.myOffset = myOffset; } - int getUnitIndex() { - int offset = myOffset + 0; + int getUnitIndex() { + int offset = myOffset + 0; return buffer.getInt(offset); } void setUnitIndex(int value) { - buffer.putInt(myOffset + 0, value); + buffer.putInt(myOffset + 0, value); } - int getSearchValue() { - int offset = myOffset + 4; + int getSearchValue() { + int offset = myOffset + 4; return buffer.getInt(offset); } void setSearchValue(int value) { - buffer.putInt(myOffset + 4, value); + buffer.putInt(myOffset + 4, value); } } - class UnitData { - static final int SIZE = 336; + class UnitData { + static final int SIZE = 336; private int myOffset; - public UnitData(int myOffset) { + UnitData(int myOffset) { this.myOffset = myOffset; } - int getClearanceLevel() { - int offset = myOffset + 0; + int getClearanceLevel() { + int offset = myOffset + 0; return buffer.getInt(offset); } void setClearanceLevel(int value) { - buffer.putInt(myOffset + 0, value); + buffer.putInt(myOffset + 0, value); } - int getId() { - int offset = myOffset + 4; + int getId() { + int offset = myOffset + 4; return buffer.getInt(offset); } void setId(int value) { - buffer.putInt(myOffset + 4, value); + buffer.putInt(myOffset + 4, value); } - int getPlayer() { - int offset = myOffset + 8; + int getPlayer() { + int offset = myOffset + 8; return buffer.getInt(offset); } void setPlayer(int value) { - buffer.putInt(myOffset + 8, value); + buffer.putInt(myOffset + 8, value); } - int getType() { - int offset = myOffset + 12; + int getType() { + int offset = myOffset + 12; return buffer.getInt(offset); } void setType(int value) { - buffer.putInt(myOffset + 12, value); + buffer.putInt(myOffset + 12, value); } - int getPositionX() { - int offset = myOffset + 16; + int getPositionX() { + int offset = myOffset + 16; return buffer.getInt(offset); } void setPositionX(int value) { - buffer.putInt(myOffset + 16, value); + buffer.putInt(myOffset + 16, value); } - int getPositionY() { - int offset = myOffset + 20; + int getPositionY() { + int offset = myOffset + 20; return buffer.getInt(offset); } void setPositionY(int value) { - buffer.putInt(myOffset + 20, value); + buffer.putInt(myOffset + 20, value); } - double getAngle() { - int offset = myOffset + 24; + double getAngle() { + int offset = myOffset + 24; return buffer.getDouble(offset); } void setAngle(double value) { - buffer.putDouble(myOffset + 24, value); + buffer.putDouble(myOffset + 24, value); } - double getVelocityX() { - int offset = myOffset + 32; + double getVelocityX() { + int offset = myOffset + 32; return buffer.getDouble(offset); } void setVelocityX(double value) { - buffer.putDouble(myOffset + 32, value); + buffer.putDouble(myOffset + 32, value); } - double getVelocityY() { - int offset = myOffset + 40; + double getVelocityY() { + int offset = myOffset + 40; return buffer.getDouble(offset); } void setVelocityY(double value) { - buffer.putDouble(myOffset + 40, value); + buffer.putDouble(myOffset + 40, value); } - int getHitPoints() { - int offset = myOffset + 48; + int getHitPoints() { + int offset = myOffset + 48; return buffer.getInt(offset); } void setHitPoints(int value) { - buffer.putInt(myOffset + 48, value); + buffer.putInt(myOffset + 48, value); } - int getLastHitPoints() { - int offset = myOffset + 52; + int getLastHitPoints() { + int offset = myOffset + 52; return buffer.getInt(offset); } void setLastHitPoints(int value) { - buffer.putInt(myOffset + 52, value); + buffer.putInt(myOffset + 52, value); } - int getShields() { - int offset = myOffset + 56; + int getShields() { + int offset = myOffset + 56; return buffer.getInt(offset); } void setShields(int value) { - buffer.putInt(myOffset + 56, value); + buffer.putInt(myOffset + 56, value); } - int getEnergy() { - int offset = myOffset + 60; + int getEnergy() { + int offset = myOffset + 60; return buffer.getInt(offset); } void setEnergy(int value) { - buffer.putInt(myOffset + 60, value); + buffer.putInt(myOffset + 60, value); } - int getResources() { - int offset = myOffset + 64; + int getResources() { + int offset = myOffset + 64; return buffer.getInt(offset); } void setResources(int value) { - buffer.putInt(myOffset + 64, value); + buffer.putInt(myOffset + 64, value); } - int getResourceGroup() { - int offset = myOffset + 68; + int getResourceGroup() { + int offset = myOffset + 68; return buffer.getInt(offset); } void setResourceGroup(int value) { - buffer.putInt(myOffset + 68, value); + buffer.putInt(myOffset + 68, value); } - int getKillCount() { - int offset = myOffset + 72; + int getKillCount() { + int offset = myOffset + 72; return buffer.getInt(offset); } void setKillCount(int value) { - buffer.putInt(myOffset + 72, value); + buffer.putInt(myOffset + 72, value); } - int getAcidSporeCount() { - int offset = myOffset + 76; + int getAcidSporeCount() { + int offset = myOffset + 76; return buffer.getInt(offset); } void setAcidSporeCount(int value) { - buffer.putInt(myOffset + 76, value); + buffer.putInt(myOffset + 76, value); } - int getScarabCount() { - int offset = myOffset + 80; + int getScarabCount() { + int offset = myOffset + 80; return buffer.getInt(offset); } void setScarabCount(int value) { - buffer.putInt(myOffset + 80, value); + buffer.putInt(myOffset + 80, value); } - int getInterceptorCount() { - int offset = myOffset + 84; + int getInterceptorCount() { + int offset = myOffset + 84; return buffer.getInt(offset); } void setInterceptorCount(int value) { - buffer.putInt(myOffset + 84, value); + buffer.putInt(myOffset + 84, value); } - int getSpiderMineCount() { - int offset = myOffset + 88; + int getSpiderMineCount() { + int offset = myOffset + 88; return buffer.getInt(offset); } void setSpiderMineCount(int value) { - buffer.putInt(myOffset + 88, value); + buffer.putInt(myOffset + 88, value); } - int getGroundWeaponCooldown() { - int offset = myOffset + 92; + int getGroundWeaponCooldown() { + int offset = myOffset + 92; return buffer.getInt(offset); } void setGroundWeaponCooldown(int value) { - buffer.putInt(myOffset + 92, value); + buffer.putInt(myOffset + 92, value); } - int getAirWeaponCooldown() { - int offset = myOffset + 96; + int getAirWeaponCooldown() { + int offset = myOffset + 96; return buffer.getInt(offset); } void setAirWeaponCooldown(int value) { - buffer.putInt(myOffset + 96, value); + buffer.putInt(myOffset + 96, value); } - int getSpellCooldown() { - int offset = myOffset + 100; + int getSpellCooldown() { + int offset = myOffset + 100; return buffer.getInt(offset); } void setSpellCooldown(int value) { - buffer.putInt(myOffset + 100, value); + buffer.putInt(myOffset + 100, value); } - int getDefenseMatrixPoints() { - int offset = myOffset + 104; + int getDefenseMatrixPoints() { + int offset = myOffset + 104; return buffer.getInt(offset); } void setDefenseMatrixPoints(int value) { - buffer.putInt(myOffset + 104, value); + buffer.putInt(myOffset + 104, value); } - int getDefenseMatrixTimer() { - int offset = myOffset + 108; + int getDefenseMatrixTimer() { + int offset = myOffset + 108; return buffer.getInt(offset); } void setDefenseMatrixTimer(int value) { - buffer.putInt(myOffset + 108, value); + buffer.putInt(myOffset + 108, value); } - int getEnsnareTimer() { - int offset = myOffset + 112; + int getEnsnareTimer() { + int offset = myOffset + 112; return buffer.getInt(offset); } void setEnsnareTimer(int value) { - buffer.putInt(myOffset + 112, value); + buffer.putInt(myOffset + 112, value); } - int getIrradiateTimer() { - int offset = myOffset + 116; + int getIrradiateTimer() { + int offset = myOffset + 116; return buffer.getInt(offset); } void setIrradiateTimer(int value) { - buffer.putInt(myOffset + 116, value); + buffer.putInt(myOffset + 116, value); } - int getLockdownTimer() { - int offset = myOffset + 120; + int getLockdownTimer() { + int offset = myOffset + 120; return buffer.getInt(offset); } void setLockdownTimer(int value) { - buffer.putInt(myOffset + 120, value); + buffer.putInt(myOffset + 120, value); } - int getMaelstromTimer() { - int offset = myOffset + 124; + int getMaelstromTimer() { + int offset = myOffset + 124; return buffer.getInt(offset); } void setMaelstromTimer(int value) { - buffer.putInt(myOffset + 124, value); + buffer.putInt(myOffset + 124, value); } - int getOrderTimer() { - int offset = myOffset + 128; + int getOrderTimer() { + int offset = myOffset + 128; return buffer.getInt(offset); } void setOrderTimer(int value) { - buffer.putInt(myOffset + 128, value); + buffer.putInt(myOffset + 128, value); } - int getPlagueTimer() { - int offset = myOffset + 132; + int getPlagueTimer() { + int offset = myOffset + 132; return buffer.getInt(offset); } void setPlagueTimer(int value) { - buffer.putInt(myOffset + 132, value); + buffer.putInt(myOffset + 132, value); } - int getRemoveTimer() { - int offset = myOffset + 136; + int getRemoveTimer() { + int offset = myOffset + 136; return buffer.getInt(offset); } void setRemoveTimer(int value) { - buffer.putInt(myOffset + 136, value); + buffer.putInt(myOffset + 136, value); } - int getStasisTimer() { - int offset = myOffset + 140; + int getStasisTimer() { + int offset = myOffset + 140; return buffer.getInt(offset); } void setStasisTimer(int value) { - buffer.putInt(myOffset + 140, value); + buffer.putInt(myOffset + 140, value); } - int getStimTimer() { - int offset = myOffset + 144; + int getStimTimer() { + int offset = myOffset + 144; return buffer.getInt(offset); } void setStimTimer(int value) { - buffer.putInt(myOffset + 144, value); + buffer.putInt(myOffset + 144, value); } - int getBuildType() { - int offset = myOffset + 148; + int getBuildType() { + int offset = myOffset + 148; return buffer.getInt(offset); } void setBuildType(int value) { - buffer.putInt(myOffset + 148, value); + buffer.putInt(myOffset + 148, value); } - int getTrainingQueueCount() { - int offset = myOffset + 152; + int getTrainingQueueCount() { + int offset = myOffset + 152; return buffer.getInt(offset); } void setTrainingQueueCount(int value) { - buffer.putInt(myOffset + 152, value); + buffer.putInt(myOffset + 152, value); } - int getTrainingQueue(int i) { - int offset = myOffset + 156 + 4 * 1 * i; + int getTrainingQueue(int i) { + int offset = myOffset + 156 + 4 * 1 * i; return buffer.getInt(offset); } void setTrainingQueue(int i, int value) { - buffer.putInt(myOffset + 156 + 4 * 1 * i, value); + buffer.putInt(myOffset + 156 + 4 * 1 * i, value); } - int getTech() { - int offset = myOffset + 176; + int getTech() { + int offset = myOffset + 176; return buffer.getInt(offset); } void setTech(int value) { - buffer.putInt(myOffset + 176, value); + buffer.putInt(myOffset + 176, value); } - int getUpgrade() { - int offset = myOffset + 180; + int getUpgrade() { + int offset = myOffset + 180; return buffer.getInt(offset); } void setUpgrade(int value) { - buffer.putInt(myOffset + 180, value); + buffer.putInt(myOffset + 180, value); } - int getRemainingBuildTime() { - int offset = myOffset + 184; + int getRemainingBuildTime() { + int offset = myOffset + 184; return buffer.getInt(offset); } void setRemainingBuildTime(int value) { - buffer.putInt(myOffset + 184, value); + buffer.putInt(myOffset + 184, value); } - int getRemainingTrainTime() { - int offset = myOffset + 188; + int getRemainingTrainTime() { + int offset = myOffset + 188; return buffer.getInt(offset); } void setRemainingTrainTime(int value) { - buffer.putInt(myOffset + 188, value); + buffer.putInt(myOffset + 188, value); } - int getRemainingResearchTime() { - int offset = myOffset + 192; + int getRemainingResearchTime() { + int offset = myOffset + 192; return buffer.getInt(offset); } void setRemainingResearchTime(int value) { - buffer.putInt(myOffset + 192, value); + buffer.putInt(myOffset + 192, value); } - int getRemainingUpgradeTime() { - int offset = myOffset + 196; + int getRemainingUpgradeTime() { + int offset = myOffset + 196; return buffer.getInt(offset); } void setRemainingUpgradeTime(int value) { - buffer.putInt(myOffset + 196, value); + buffer.putInt(myOffset + 196, value); } - int getBuildUnit() { - int offset = myOffset + 200; + int getBuildUnit() { + int offset = myOffset + 200; return buffer.getInt(offset); } void setBuildUnit(int value) { - buffer.putInt(myOffset + 200, value); + buffer.putInt(myOffset + 200, value); } - int getTarget() { - int offset = myOffset + 204; + int getTarget() { + int offset = myOffset + 204; return buffer.getInt(offset); } void setTarget(int value) { - buffer.putInt(myOffset + 204, value); + buffer.putInt(myOffset + 204, value); } - int getTargetPositionX() { - int offset = myOffset + 208; + int getTargetPositionX() { + int offset = myOffset + 208; return buffer.getInt(offset); } void setTargetPositionX(int value) { - buffer.putInt(myOffset + 208, value); + buffer.putInt(myOffset + 208, value); } - int getTargetPositionY() { - int offset = myOffset + 212; + int getTargetPositionY() { + int offset = myOffset + 212; return buffer.getInt(offset); } void setTargetPositionY(int value) { - buffer.putInt(myOffset + 212, value); + buffer.putInt(myOffset + 212, value); } - int getOrder() { - int offset = myOffset + 216; + int getOrder() { + int offset = myOffset + 216; return buffer.getInt(offset); } void setOrder(int value) { - buffer.putInt(myOffset + 216, value); + buffer.putInt(myOffset + 216, value); } - int getOrderTarget() { - int offset = myOffset + 220; + int getOrderTarget() { + int offset = myOffset + 220; return buffer.getInt(offset); } void setOrderTarget(int value) { - buffer.putInt(myOffset + 220, value); + buffer.putInt(myOffset + 220, value); } - int getOrderTargetPositionX() { - int offset = myOffset + 224; + int getOrderTargetPositionX() { + int offset = myOffset + 224; return buffer.getInt(offset); } void setOrderTargetPositionX(int value) { - buffer.putInt(myOffset + 224, value); + buffer.putInt(myOffset + 224, value); } - int getOrderTargetPositionY() { - int offset = myOffset + 228; + int getOrderTargetPositionY() { + int offset = myOffset + 228; return buffer.getInt(offset); } void setOrderTargetPositionY(int value) { - buffer.putInt(myOffset + 228, value); + buffer.putInt(myOffset + 228, value); } - int getSecondaryOrder() { - int offset = myOffset + 232; + int getSecondaryOrder() { + int offset = myOffset + 232; return buffer.getInt(offset); } void setSecondaryOrder(int value) { - buffer.putInt(myOffset + 232, value); + buffer.putInt(myOffset + 232, value); } - int getRallyPositionX() { - int offset = myOffset + 236; + int getRallyPositionX() { + int offset = myOffset + 236; return buffer.getInt(offset); } void setRallyPositionX(int value) { - buffer.putInt(myOffset + 236, value); + buffer.putInt(myOffset + 236, value); } - int getRallyPositionY() { - int offset = myOffset + 240; + int getRallyPositionY() { + int offset = myOffset + 240; return buffer.getInt(offset); } void setRallyPositionY(int value) { - buffer.putInt(myOffset + 240, value); + buffer.putInt(myOffset + 240, value); } - int getRallyUnit() { - int offset = myOffset + 244; + int getRallyUnit() { + int offset = myOffset + 244; return buffer.getInt(offset); } void setRallyUnit(int value) { - buffer.putInt(myOffset + 244, value); + buffer.putInt(myOffset + 244, value); } - int getAddon() { - int offset = myOffset + 248; + int getAddon() { + int offset = myOffset + 248; return buffer.getInt(offset); } void setAddon(int value) { - buffer.putInt(myOffset + 248, value); + buffer.putInt(myOffset + 248, value); } - int getNydusExit() { - int offset = myOffset + 252; + int getNydusExit() { + int offset = myOffset + 252; return buffer.getInt(offset); } void setNydusExit(int value) { - buffer.putInt(myOffset + 252, value); + buffer.putInt(myOffset + 252, value); } - int getPowerUp() { - int offset = myOffset + 256; + int getPowerUp() { + int offset = myOffset + 256; return buffer.getInt(offset); } void setPowerUp(int value) { - buffer.putInt(myOffset + 256, value); + buffer.putInt(myOffset + 256, value); } - int getTransport() { - int offset = myOffset + 260; + int getTransport() { + int offset = myOffset + 260; return buffer.getInt(offset); } void setTransport(int value) { - buffer.putInt(myOffset + 260, value); + buffer.putInt(myOffset + 260, value); } - int getCarrier() { - int offset = myOffset + 264; + int getCarrier() { + int offset = myOffset + 264; return buffer.getInt(offset); } void setCarrier(int value) { - buffer.putInt(myOffset + 264, value); + buffer.putInt(myOffset + 264, value); } - int getHatchery() { - int offset = myOffset + 268; + int getHatchery() { + int offset = myOffset + 268; return buffer.getInt(offset); } void setHatchery(int value) { - buffer.putInt(myOffset + 268, value); + buffer.putInt(myOffset + 268, value); } - boolean getExists() { - int offset = myOffset + 272; + boolean getExists() { + int offset = myOffset + 272; return buffer.getByte(offset) != 0; } void setExists(boolean value) { - buffer.putByte(myOffset + 272, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 272, (byte) (value ? 1 : 0)); } - boolean getHasNuke() { - int offset = myOffset + 273; + boolean getHasNuke() { + int offset = myOffset + 273; return buffer.getByte(offset) != 0; } void setHasNuke(boolean value) { - buffer.putByte(myOffset + 273, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 273, (byte) (value ? 1 : 0)); } - boolean isAccelerating() { - int offset = myOffset + 274; + boolean isAccelerating() { + int offset = myOffset + 274; return buffer.getByte(offset) != 0; } void setIsAccelerating(boolean value) { - buffer.putByte(myOffset + 274, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 274, (byte) (value ? 1 : 0)); } - boolean isAttacking() { - int offset = myOffset + 275; + boolean isAttacking() { + int offset = myOffset + 275; return buffer.getByte(offset) != 0; } void setIsAttacking(boolean value) { - buffer.putByte(myOffset + 275, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 275, (byte) (value ? 1 : 0)); } - boolean isAttackFrame() { - int offset = myOffset + 276; + boolean isAttackFrame() { + int offset = myOffset + 276; return buffer.getByte(offset) != 0; } void setIsAttackFrame(boolean value) { - buffer.putByte(myOffset + 276, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 276, (byte) (value ? 1 : 0)); } - boolean isBeingGathered() { - int offset = myOffset + 277; + boolean isBeingGathered() { + int offset = myOffset + 277; return buffer.getByte(offset) != 0; } void setIsBeingGathered(boolean value) { - buffer.putByte(myOffset + 277, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 277, (byte) (value ? 1 : 0)); } - boolean isBlind() { - int offset = myOffset + 278; + boolean isBlind() { + int offset = myOffset + 278; return buffer.getByte(offset) != 0; } void setIsBlind(boolean value) { - buffer.putByte(myOffset + 278, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 278, (byte) (value ? 1 : 0)); } - boolean isBraking() { - int offset = myOffset + 279; + boolean isBraking() { + int offset = myOffset + 279; return buffer.getByte(offset) != 0; } void setIsBraking(boolean value) { - buffer.putByte(myOffset + 279, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 279, (byte) (value ? 1 : 0)); } - boolean isBurrowed() { - int offset = myOffset + 280; + boolean isBurrowed() { + int offset = myOffset + 280; return buffer.getByte(offset) != 0; } void setIsBurrowed(boolean value) { - buffer.putByte(myOffset + 280, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 280, (byte) (value ? 1 : 0)); } - int getCarryResourceType() { - int offset = myOffset + 284; + int getCarryResourceType() { + int offset = myOffset + 284; return buffer.getInt(offset); } void setCarryResourceType(int value) { - buffer.putInt(myOffset + 284, value); + buffer.putInt(myOffset + 284, value); } - boolean isCloaked() { - int offset = myOffset + 288; + boolean isCloaked() { + int offset = myOffset + 288; return buffer.getByte(offset) != 0; } void setIsCloaked(boolean value) { - buffer.putByte(myOffset + 288, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 288, (byte) (value ? 1 : 0)); } - boolean isCompleted() { - int offset = myOffset + 289; + boolean isCompleted() { + int offset = myOffset + 289; return buffer.getByte(offset) != 0; } void setIsCompleted(boolean value) { - buffer.putByte(myOffset + 289, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 289, (byte) (value ? 1 : 0)); } - boolean isConstructing() { - int offset = myOffset + 290; + boolean isConstructing() { + int offset = myOffset + 290; return buffer.getByte(offset) != 0; } void setIsConstructing(boolean value) { - buffer.putByte(myOffset + 290, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 290, (byte) (value ? 1 : 0)); } - boolean isDetected() { - int offset = myOffset + 291; + boolean isDetected() { + int offset = myOffset + 291; return buffer.getByte(offset) != 0; } void setIsDetected(boolean value) { - buffer.putByte(myOffset + 291, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 291, (byte) (value ? 1 : 0)); } - boolean isGathering() { - int offset = myOffset + 292; + boolean isGathering() { + int offset = myOffset + 292; return buffer.getByte(offset) != 0; } void setIsGathering(boolean value) { - buffer.putByte(myOffset + 292, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 292, (byte) (value ? 1 : 0)); } - boolean isHallucination() { - int offset = myOffset + 293; + boolean isHallucination() { + int offset = myOffset + 293; return buffer.getByte(offset) != 0; } void setIsHallucination(boolean value) { - buffer.putByte(myOffset + 293, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 293, (byte) (value ? 1 : 0)); } - boolean isIdle() { - int offset = myOffset + 294; + boolean isIdle() { + int offset = myOffset + 294; return buffer.getByte(offset) != 0; } void setIsIdle(boolean value) { - buffer.putByte(myOffset + 294, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 294, (byte) (value ? 1 : 0)); } - boolean isInterruptible() { - int offset = myOffset + 295; + boolean isInterruptible() { + int offset = myOffset + 295; return buffer.getByte(offset) != 0; } void setIsInterruptible(boolean value) { - buffer.putByte(myOffset + 295, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 295, (byte) (value ? 1 : 0)); } - boolean isInvincible() { - int offset = myOffset + 296; + boolean isInvincible() { + int offset = myOffset + 296; return buffer.getByte(offset) != 0; } void setIsInvincible(boolean value) { - buffer.putByte(myOffset + 296, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 296, (byte) (value ? 1 : 0)); } - boolean isLifted() { - int offset = myOffset + 297; + boolean isLifted() { + int offset = myOffset + 297; return buffer.getByte(offset) != 0; } void setIsLifted(boolean value) { - buffer.putByte(myOffset + 297, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 297, (byte) (value ? 1 : 0)); } - boolean isMorphing() { - int offset = myOffset + 298; + boolean isMorphing() { + int offset = myOffset + 298; return buffer.getByte(offset) != 0; } void setIsMorphing(boolean value) { - buffer.putByte(myOffset + 298, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 298, (byte) (value ? 1 : 0)); } - boolean isMoving() { - int offset = myOffset + 299; + boolean isMoving() { + int offset = myOffset + 299; return buffer.getByte(offset) != 0; } void setIsMoving(boolean value) { - buffer.putByte(myOffset + 299, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 299, (byte) (value ? 1 : 0)); } - boolean isParasited() { - int offset = myOffset + 300; + boolean isParasited() { + int offset = myOffset + 300; return buffer.getByte(offset) != 0; } void setIsParasited(boolean value) { - buffer.putByte(myOffset + 300, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 300, (byte) (value ? 1 : 0)); } - boolean isSelected() { - int offset = myOffset + 301; + boolean isSelected() { + int offset = myOffset + 301; return buffer.getByte(offset) != 0; } void setIsSelected(boolean value) { - buffer.putByte(myOffset + 301, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 301, (byte) (value ? 1 : 0)); } - boolean isStartingAttack() { - int offset = myOffset + 302; + boolean isStartingAttack() { + int offset = myOffset + 302; return buffer.getByte(offset) != 0; } void setIsStartingAttack(boolean value) { - buffer.putByte(myOffset + 302, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 302, (byte) (value ? 1 : 0)); } - boolean isStuck() { - int offset = myOffset + 303; + boolean isStuck() { + int offset = myOffset + 303; return buffer.getByte(offset) != 0; } void setIsStuck(boolean value) { - buffer.putByte(myOffset + 303, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 303, (byte) (value ? 1 : 0)); } - boolean isTraining() { - int offset = myOffset + 304; + boolean isTraining() { + int offset = myOffset + 304; return buffer.getByte(offset) != 0; } void setIsTraining(boolean value) { - buffer.putByte(myOffset + 304, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 304, (byte) (value ? 1 : 0)); } - boolean isUnderStorm() { - int offset = myOffset + 305; + boolean isUnderStorm() { + int offset = myOffset + 305; return buffer.getByte(offset) != 0; } void setIsUnderStorm(boolean value) { - buffer.putByte(myOffset + 305, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 305, (byte) (value ? 1 : 0)); } - boolean isUnderDarkSwarm() { - int offset = myOffset + 306; + boolean isUnderDarkSwarm() { + int offset = myOffset + 306; return buffer.getByte(offset) != 0; } void setIsUnderDarkSwarm(boolean value) { - buffer.putByte(myOffset + 306, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 306, (byte) (value ? 1 : 0)); } - boolean isUnderDWeb() { - int offset = myOffset + 307; + boolean isUnderDWeb() { + int offset = myOffset + 307; return buffer.getByte(offset) != 0; } void setIsUnderDWeb(boolean value) { - buffer.putByte(myOffset + 307, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 307, (byte) (value ? 1 : 0)); } - boolean isPowered() { - int offset = myOffset + 308; + boolean isPowered() { + int offset = myOffset + 308; return buffer.getByte(offset) != 0; } void setIsPowered(boolean value) { - buffer.putByte(myOffset + 308, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 308, (byte) (value ? 1 : 0)); } - boolean isVisible(int i) { - int offset = myOffset + 309 + 1 * 1 * i; + boolean isVisible(int i) { + int offset = myOffset + 309 + 1 * 1 * i; return buffer.getByte(offset) != 0; } void setIsVisible(int i, boolean value) { - buffer.putByte(myOffset + 309 + 1 * 1 * i, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 309 + 1 * 1 * i, (byte) (value ? 1 : 0)); } - int getButtonset() { - int offset = myOffset + 320; + int getButtonset() { + int offset = myOffset + 320; return buffer.getInt(offset); } void setButtonset(int value) { - buffer.putInt(myOffset + 320, value); + buffer.putInt(myOffset + 320, value); } - int getLastAttackerPlayer() { - int offset = myOffset + 324; + int getLastAttackerPlayer() { + int offset = myOffset + 324; return buffer.getInt(offset); } void setLastAttackerPlayer(int value) { - buffer.putInt(myOffset + 324, value); + buffer.putInt(myOffset + 324, value); } - boolean getRecentlyAttacked() { - int offset = myOffset + 328; + boolean getRecentlyAttacked() { + int offset = myOffset + 328; return buffer.getByte(offset) != 0; } void setRecentlyAttacked(boolean value) { - buffer.putByte(myOffset + 328, (byte) (value ? 1 : 0)); + buffer.putByte(myOffset + 328, (byte) (value ? 1 : 0)); } - int getReplayID() { - int offset = myOffset + 332; + int getReplayID() { + int offset = myOffset + 332; return buffer.getInt(offset); } void setReplayID(int value) { - buffer.putInt(myOffset + 332, value); + buffer.putInt(myOffset + 332, value); } } } diff --git a/src/main/java/bwapi/EventHandler.java b/src/main/java/bwapi/EventHandler.java index 9f5ecd9d..aad6f8dd 100644 --- a/src/main/java/bwapi/EventHandler.java +++ b/src/main/java/bwapi/EventHandler.java @@ -18,10 +18,10 @@ static void operation(BWEventListener eventListener, Game game, final ClientData break; //case 3: //MenuFrame case SendText: - eventListener.onSendText(game.clientData().gameData().getEventStrings(event.getV1())); + eventListener.onSendText(game.botClientData().gameData().getEventStrings(event.getV1())); break; case ReceiveText: - eventListener.onReceiveText(game.getPlayer(event.getV1()), game.clientData().gameData().getEventStrings(event.getV2())); + eventListener.onReceiveText(game.getPlayer(event.getV1()), game.botClientData().gameData().getEventStrings(event.getV2())); break; case PlayerLeft: eventListener.onPlayerLeft(game.getPlayer(event.getV1())); @@ -30,7 +30,7 @@ static void operation(BWEventListener eventListener, Game game, final ClientData eventListener.onNukeDetect(new Position(event.getV1(), event.getV2())); break; case SaveGame: - eventListener.onSaveGame(game.clientData().gameData().getEventStrings(event.getV1())); + eventListener.onSaveGame(game.botClientData().gameData().getEventStrings(event.getV1())); break; case UnitDiscover: game.unitCreate(event.getV1()); diff --git a/src/main/java/bwapi/Game.java b/src/main/java/bwapi/Game.java index 69757b2b..a8aec781 100644 --- a/src/main/java/bwapi/Game.java +++ b/src/main/java/bwapi/Game.java @@ -103,7 +103,7 @@ public final class Game { clientData = new ClientData(); } - ClientData clientData() { + ClientData botClientData() { return clientData; } diff --git a/src/test/java/DumpToClient.java b/src/test/java/DumpToClient.java index 90d72377..01cd0e27 100644 --- a/src/test/java/DumpToClient.java +++ b/src/test/java/DumpToClient.java @@ -14,7 +14,7 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; -public class DumpToClient { +class DumpToClient { private static final Pattern VAR_DECL = Pattern.compile( "^\\s+(\\d+) \\| +((?:struct )?BWAPI[^:]*::)?(\\S[^\\[]+)\\s([\\[0-9\\]]+)?\\s?(\\S+)$"); @@ -100,14 +100,14 @@ public static void main(String[] args) throws IOException { out.println(" GameData gameData() {"); out.println(" return gameData;"); out.println(" }"); - out.println(" public void setBuffer(ByteBuffer buffer) {"); + out.println(" void setBuffer(ByteBuffer buffer) {"); out.println(" this.buffer = new WrappedBuffer(buffer);"); out.println(" }"); structs.values().forEach(s -> { out.printf(" class %s {\n", s.name); out.printf(" static final int SIZE = %d;\n", s.size); out.println(" private int myOffset;"); - out.printf(" public %s(int myOffset) {\n", s.name); + out.printf(" %s(int myOffset) {\n", s.name); out.println(" this.myOffset = myOffset;"); out.println(" }"); s.variables.forEach(v -> { @@ -271,7 +271,7 @@ public static void main(String[] args) throws IOException { StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); } - public enum Type { + enum Type { STRUCT, BOOLEAN, INT, @@ -282,7 +282,7 @@ public enum Type { ENUM } - public static class Variable { + static class Variable { private final String name; private final Type type; @@ -291,19 +291,19 @@ public static class Variable { private String enumName; private List arraySizes = Collections.emptyList(); - public Variable(String name, Type type) { + Variable(String name, Type type) { this.name = name; this.type = type; } } - public static class Struct { + static class Struct { final String name; int size; List variables = new ArrayList<>(); - public Struct(String name) { + Struct(String name) { this.name = name; } } diff --git a/src/test/java/bwapi/ClientDataBenchmark.java b/src/test/java/bwapi/ClientDataBenchmark.java index ec19d2dd..3954db97 100644 --- a/src/test/java/bwapi/ClientDataBenchmark.java +++ b/src/test/java/bwapi/ClientDataBenchmark.java @@ -20,7 +20,7 @@ public static class EmptyState { @Setup(Level.Invocation) public void setup() { game = new Game(); - game.clientData().setBuffer(ByteBuffer.allocateDirect(ClientData.GameData.SIZE)); + game.botClientData().setBuffer(ByteBuffer.allocateDirect(ClientData.GameData.SIZE)); strings = buildStrings(); } @@ -36,7 +36,7 @@ public static class FilledWithStrings { public void setup() { data = client.clientData().gameData(); game = new Game(); - game.clientData().setBuffer(ByteBuffer.allocateDirect(ClientData.GameData.SIZE)); + game.botClientData().setBuffer(ByteBuffer.allocateDirect(ClientData.GameData.SIZE)); String[] strings = buildStrings(); for (String s : strings) { GameDataUtils.addString(client.clientData().gameData(), s); diff --git a/src/test/java/bwapi/GameBuilder.java b/src/test/java/bwapi/GameBuilder.java index f03d9e73..c8d087f1 100644 --- a/src/test/java/bwapi/GameBuilder.java +++ b/src/test/java/bwapi/GameBuilder.java @@ -2,7 +2,6 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.Paths; @@ -21,7 +20,7 @@ public static Game createGame() throws IOException { public static Game createGame(String mapName) throws IOException { final ByteBuffer buffer = binToBuffer(RESOURCES + mapName + "_frame0_buffer.bin"); final Game game = new Game(); - game.clientData().setBuffer(buffer); + game.botClientData().setBuffer(buffer); game.init(); return game; } diff --git a/src/test/java/bwapi/GameTest.java b/src/test/java/bwapi/GameTest.java index a5202808..7dbd76b4 100644 --- a/src/test/java/bwapi/GameTest.java +++ b/src/test/java/bwapi/GameTest.java @@ -7,8 +7,6 @@ import java.io.IOException; import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; import org.junit.Before; @@ -93,7 +91,7 @@ public void ifReplaySelfAndEnemyShouldBeNull() throws IOException { clientData.gameData().setIsReplay(true); Game game = new Game(); - game.clientData().setBuffer(buffer); + game.botClientData().setBuffer(buffer); game.init(); assertThat(game.isReplay()); diff --git a/src/test/java/bwapi/PointTest.java b/src/test/java/bwapi/PointTest.java index 5aa3dcb5..3f1af51c 100644 --- a/src/test/java/bwapi/PointTest.java +++ b/src/test/java/bwapi/PointTest.java @@ -2,13 +2,11 @@ import org.junit.Test; -import java.nio.ByteBuffer; import java.util.Random; import static org.junit.Assert.*; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; public class PointTest { @@ -57,9 +55,9 @@ public void alternativeConstructorTest() { @Test public void isValidChecks() { Game game = new Game(); - game.clientData().setBuffer(GameBuilder.binToBufferUnchecked(GameBuilder.DEFAULT_BUFFER_PATH)); - game.clientData().gameData().setMapWidth(256); - game.clientData().gameData().setMapHeight(256); + game.botClientData().setBuffer(GameBuilder.binToBufferUnchecked(GameBuilder.DEFAULT_BUFFER_PATH)); + game.botClientData().gameData().setMapWidth(256); + game.botClientData().gameData().setMapHeight(256); game.init(); assertEquals(256, game.mapHeight()); From 26ee6a2e0f8bde76599dc546bc59848e03c968ab Mon Sep 17 00:00:00 2001 From: dgant Date: Mon, 5 Oct 2020 15:59:50 -0400 Subject: [PATCH 46/56] Removed the attempted TimerResolutionThread hack --- src/main/java/bwapi/BWClient.java | 2 -- .../java/bwapi/TimerResolutionThread.java | 34 ------------------- src/test/java/bwapi/PointTest.java | 1 - 3 files changed, 37 deletions(-) delete mode 100644 src/main/java/bwapi/TimerResolutionThread.java diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index 26ca9a7d..7360378f 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -13,12 +13,10 @@ public class BWClient { private BotWrapper botWrapper; private Client client; private PerformanceMetrics performanceMetrics; - private TimerResolutionThread timerResolutionThread; public BWClient(final BWEventListener eventListener) { Objects.requireNonNull(eventListener); this.eventListener = eventListener; - //this.timerResolutionThread = new TimerResolutionThread(); } /** diff --git a/src/main/java/bwapi/TimerResolutionThread.java b/src/main/java/bwapi/TimerResolutionThread.java deleted file mode 100644 index 3d42a7e8..00000000 --- a/src/main/java/bwapi/TimerResolutionThread.java +++ /dev/null @@ -1,34 +0,0 @@ -package bwapi; - -/** - * See https://hazelcast.com/blog/locksupport-parknanos-under-the-hood-and-the-curious-case-of-parking-part-ii-windows/ - * - * "Is this it? Just a single flag and my JVM will use high-resolution timers? - * Well, it turns out it’s not that simple. - * This flag has been reported to be broken since 2006! - * However, the very same bug report suggests a very interesting workaround:" - * - * “Do not use ForceTimeHighResolution but instead, - * at the start of the application create and start - * a daemon Thread that simply sleeps for a very long time - * (that isn’t a multiple of 10ms) - * as this will set the timer period to be 1ms for the duration of that sleep - * – which in this case is the lifetime of the VM" - * - * "What the R$#@#@ have I just read? - * Let me rephrase it: - * “If you want a high-resolution timer then just start a new thread and let it sleep forever”. - * That’s simply hilarious! - */ -class TimerResolutionThread { - TimerResolutionThread() { - Thread t = new Thread(() -> { - try { - Thread.sleep(Long.MAX_VALUE); - } catch (InterruptedException e) { // a delicious interrupt, omm, omm - } - }); - t.setDaemon(true); - t.start(); - } -} diff --git a/src/test/java/bwapi/PointTest.java b/src/test/java/bwapi/PointTest.java index 3f1af51c..946e5046 100644 --- a/src/test/java/bwapi/PointTest.java +++ b/src/test/java/bwapi/PointTest.java @@ -8,7 +8,6 @@ import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; - public class PointTest { @Test From 30d6792c83da8a2a165d2a3c135b43df36fae7ec Mon Sep 17 00:00:00 2001 From: dgant Date: Mon, 5 Oct 2020 16:07:49 -0400 Subject: [PATCH 47/56] Cosmetic changes/cleanup --- src/main/java/bwapi/BWClient.java | 6 ++-- src/main/java/bwapi/BotWrapper.java | 1 - src/main/java/bwapi/Client.java | 2 +- src/main/java/bwapi/FrameBuffer.java | 25 +++++++------ src/test/java/bwapi/ClientDataBenchmark.java | 10 +++--- .../bwapi/SynchronizationEnvironment.java | 36 +++++++++---------- 6 files changed, 42 insertions(+), 38 deletions(-) diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index 7360378f..d3b1b6e4 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -45,7 +45,7 @@ public BWClientConfiguration getConfiguration() { * @return Whether the current frame should be subject to timing. */ boolean doTime() { - return ! configuration.getUnlimitedFrameZero() || (client.isConnected() && client.clientData().gameData().getFrameCount() > 0); + return ! configuration.getUnlimitedFrameZero() || (client.isConnected() && client.liveClientData().gameData().getFrameCount() > 0); } /** @@ -53,7 +53,7 @@ boolean doTime() { * This tracks the size of the frame buffer except when the game is paused (which results in multiple frames arriving with the same count). */ public int framesBehind() { - return botWrapper == null ? 0 : Math.max(0, client.clientData().gameData().getFrameCount() - getGame().getFrameCount()); + return botWrapper == null ? 0 : Math.max(0, client.liveClientData().gameData().getFrameCount() - getGame().getFrameCount()); } /** @@ -107,7 +107,7 @@ public void startGame(BWClientConfiguration gameConfiguration) { client.reconnect(); do { - ClientData.GameData liveGameData = client.clientData().gameData(); + ClientData.GameData liveGameData = client.liveClientData().gameData(); while (!liveGameData.isInGame()) { if (!client.isConnected()) { return; diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java index b76a0ae8..0d4f9fbb 100644 --- a/src/main/java/bwapi/BotWrapper.java +++ b/src/main/java/bwapi/BotWrapper.java @@ -206,7 +206,6 @@ private Thread createBotThread() { if (doUnsafeRead) { configuration.log("Bot: Reading live frame"); setUnsafeReadReady(false); - // TODO: Maybe we should point it at live data from here? } else { configuration.log("Bot: Peeking next frame from buffer"); botGame.botClientData().setBuffer(frameBuffer.peek()); diff --git a/src/main/java/bwapi/Client.java b/src/main/java/bwapi/Client.java index 6dd647bf..17792bb5 100644 --- a/src/main/java/bwapi/Client.java +++ b/src/main/java/bwapi/Client.java @@ -63,7 +63,7 @@ interface MappingKernel extends Kernel32 { clientData.setBuffer(buffer); } - ClientData clientData() { + ClientData liveClientData() { return clientData; } diff --git a/src/main/java/bwapi/FrameBuffer.java b/src/main/java/bwapi/FrameBuffer.java index 4c570757..044e3a5f 100644 --- a/src/main/java/bwapi/FrameBuffer.java +++ b/src/main/java/bwapi/FrameBuffer.java @@ -181,11 +181,12 @@ private boolean tryMemcpyBuffer(ByteBuffer source, ByteBuffer destination, long void copyBuffer(ByteBuffer source, ByteBuffer destination, boolean copyEverything) { /* The speed at which we copy data into the frame buffer is a major cost of JBWAPI's asynchronous operation. - Copy times observed in the wild usually range from 2.6ms - 19ms but are prone to large amounts of variance. + Copy times observed in the wild for the complete buffer usually range from 2.6ms - 19ms + but are prone to large amounts of variance. The normal Java way to execute this copy is via ByteBuffer.put(), which has reasonably good performance characteristics. - Some experiments in 64-bit JRE have shown that using a native memcpy achieves a 35% speedup. - Some experiments in 32-bit JRE show no difference in performance. + Experiments in 64-bit JRE have shown that using a native memcpy achieves a 35% speedup. + Experiments in 32-bit JRE show no difference in performance. So, speculatively, we attempt to do a native memcpy. */ @@ -195,13 +196,17 @@ void copyBuffer(ByteBuffer source, ByteBuffer destination, boolean copyEverythin return; } } else { - int STATICTILES_START = 3447004; // getGroundHeight, isWalkable, isBuildable - int STATICTILES_END = 4823260; - int REGION_START = 5085404; // getMapTileRegionId, ..., getRegions - int REGION_END = 10586480; - int STRINGSSHAPES_START = 10962632; // getStringCount, ... getShapes - int STRINGSHAPES_END = 32242636; - int UNITFINDER_START = 32962644; + // After the buffer has been filled the first time, + // we can omit copying blocks of data which are unused or which don't change after game start. + // These blocks account for *most* of the 33MB shared memory, + // so omitting them drastically reduces the copy duration + final int STATICTILES_START = 3447004; // getGroundHeight, isWalkable, isBuildable + final int STATICTILES_END = 4823260; + final int REGION_START = 5085404; // getMapTileRegionId, ..., getRegions + final int REGION_END = 10586480; + final int STRINGSSHAPES_START = 10962632; // getStringCount, ... getShapes + final int STRINGSHAPES_END = 32242636; + final int UNITFINDER_START = 32962644; if ( tryMemcpyBuffer(source, destination, 0, STATICTILES_START) && tryMemcpyBuffer(source, destination, STATICTILES_END, REGION_START - STATICTILES_END) diff --git a/src/test/java/bwapi/ClientDataBenchmark.java b/src/test/java/bwapi/ClientDataBenchmark.java index 3954db97..92fc409a 100644 --- a/src/test/java/bwapi/ClientDataBenchmark.java +++ b/src/test/java/bwapi/ClientDataBenchmark.java @@ -34,12 +34,12 @@ public static class FilledWithStrings { @Setup(Level.Invocation) public void setup() { - data = client.clientData().gameData(); + data = client.liveClientData().gameData(); game = new Game(); game.botClientData().setBuffer(ByteBuffer.allocateDirect(ClientData.GameData.SIZE)); String[] strings = buildStrings(); for (String s : strings) { - GameDataUtils.addString(client.clientData().gameData(), s); + GameDataUtils.addString(client.liveClientData().gameData(), s); } } } @@ -69,16 +69,16 @@ public int addUnitCommand(EmptyState s) { for (int i = 0; i < GameDataUtils.MAX_COUNT; i++) { s.game.addUnitCommand(0, 1, 2, 3, 4, 5); } - return s.client.clientData().gameData().getCommandCount(); + return s.client.liveClientData().gameData().getCommandCount(); } @Benchmark @OperationsPerInvocation(GameDataUtils.MAX_COUNT) public int addString(EmptyState s) { for (int i = 0; i < GameDataUtils.MAX_COUNT; i++) { - GameDataUtils.addString(s.client.clientData().gameData(), s.strings[i]); + GameDataUtils.addString(s.client.liveClientData().gameData(), s.strings[i]); } - return s.client.clientData().gameData().getStringCount(); + return s.client.liveClientData().gameData().getStringCount(); } @Benchmark diff --git a/src/test/java/bwapi/SynchronizationEnvironment.java b/src/test/java/bwapi/SynchronizationEnvironment.java index 5a13de31..03fe2628 100644 --- a/src/test/java/bwapi/SynchronizationEnvironment.java +++ b/src/test/java/bwapi/SynchronizationEnvironment.java @@ -33,10 +33,10 @@ class SynchronizationEnvironment { onFrames = new HashMap<>(); when(client.mapFile()).thenReturn(GameBuilder.binToBufferUnchecked(GameBuilder.DEFAULT_BUFFER_PATH)); - when(client.clientData()).thenReturn(new ClientData()); - client.clientData().setBuffer(client.mapFile()); - client.clientData().gameData().setFrameCount(-1); - client.clientData().gameData().setIsInGame(false); + when(client.liveClientData()).thenReturn(new ClientData()); + client.liveClientData().setBuffer(client.mapFile()); + client.liveClientData().gameData().setFrameCount(-1); + client.liveClientData().gameData().setIsInGame(false); when(client.isConnected()).thenReturn(true); doAnswer(answer -> { @@ -63,7 +63,7 @@ class SynchronizationEnvironment { } ClientData.GameData liveGameData() { - return client.clientData().gameData(); + return client.liveClientData().gameData(); } PerformanceMetrics metrics() { @@ -102,28 +102,28 @@ void runGame(int onEndFrame) { } private int liveFrame() { - return client.clientData().gameData().getFrameCount(); + return client.liveClientData().gameData().getFrameCount(); } private void clientUpdate() throws InterruptedException{ Thread.sleep(bwapiDelayMs); - client.clientData().gameData().setFrameCount(liveFrame() + 1); + client.liveClientData().gameData().setFrameCount(liveFrame() + 1); configuration.log("Test: clientUpdate() to liveFrame #" + liveFrame()); if (liveFrame() == 0) { - client.clientData().gameData().setIsInGame(true); - client.clientData().gameData().setEventCount(2); - client.clientData().gameData().getEvents(0).setType(EventType.MatchStart); - client.clientData().gameData().getEvents(1).setType(EventType.MatchFrame); + client.liveClientData().gameData().setIsInGame(true); + client.liveClientData().gameData().setEventCount(2); + client.liveClientData().gameData().getEvents(0).setType(EventType.MatchStart); + client.liveClientData().gameData().getEvents(1).setType(EventType.MatchFrame); } else if (liveFrame() < onEndFrame) { - client.clientData().gameData().setIsInGame(true); - client.clientData().gameData().setEventCount(1); - client.clientData().gameData().getEvents(0).setType(EventType.MatchFrame); + client.liveClientData().gameData().setIsInGame(true); + client.liveClientData().gameData().setEventCount(1); + client.liveClientData().gameData().getEvents(0).setType(EventType.MatchFrame); } else if (liveFrame() == onEndFrame) { - client.clientData().gameData().setIsInGame(true); - client.clientData().gameData().getEvents(0).setType(EventType.MatchEnd); + client.liveClientData().gameData().setIsInGame(true); + client.liveClientData().gameData().getEvents(0).setType(EventType.MatchEnd); } else { - client.clientData().gameData().setIsInGame(false); - client.clientData().gameData().setEventCount(0); + client.liveClientData().gameData().setIsInGame(false); + client.liveClientData().gameData().setEventCount(0); } } } From 00b1266275d448aed6d198e78e4d1d2cf19db124 Mon Sep 17 00:00:00 2001 From: dgant Date: Mon, 5 Oct 2020 17:02:15 -0400 Subject: [PATCH 48/56] Fixed use case of copying frames via ByteBuffer API --- src/main/java/bwapi/FrameBuffer.java | 9 +-------- src/main/java/bwapi/WrappedBuffer.java | 6 ++++++ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main/java/bwapi/FrameBuffer.java b/src/main/java/bwapi/FrameBuffer.java index fe32f576..698d7e97 100644 --- a/src/main/java/bwapi/FrameBuffer.java +++ b/src/main/java/bwapi/FrameBuffer.java @@ -219,13 +219,6 @@ && tryMemcpyBuffer(source, destination, STRINGSHAPES_END, UNITFINDER_START - STR // There's no specific case where we expect to fail above, // but this is a safe fallback regardless, // and serves to document the known-good (and cross-platform, for BWAPI 5) way to executing the copy. - - // TODO: This part was written for ByteBuffer. - // Adapt it for WrappedBuffer - /* - source.rewind(); - destination.rewind(); - destination.put(liveData); - */ + destination.copyFrom(source); } } diff --git a/src/main/java/bwapi/WrappedBuffer.java b/src/main/java/bwapi/WrappedBuffer.java index 9752af13..7bf58cc7 100644 --- a/src/main/java/bwapi/WrappedBuffer.java +++ b/src/main/java/bwapi/WrappedBuffer.java @@ -37,6 +37,12 @@ class WrappedBuffer { this.address = Pointer.nativeValue(pointer); } + void copyFrom(WrappedBuffer source) { + source.buffer.rewind(); + buffer.rewind(); + buffer.put(source.buffer); + } + byte getByte(final int offset) { return unsafe.getByte(address + offset); } From 04db7851d76e88edd5ad8d8e77952dee8f8af88e Mon Sep 17 00:00:00 2001 From: dgant Date: Mon, 5 Oct 2020 18:15:38 -0400 Subject: [PATCH 49/56] Disabled latency compensation in async mode. --- .../java/bwapi/BWClientConfiguration.java | 2 ++ src/main/java/bwapi/BotWrapper.java | 1 + src/main/java/bwapi/Game.java | 34 +++++++++++++++---- src/test/java/bwapi/SynchronizationTest.java | 14 ++++++++ 4 files changed, 45 insertions(+), 6 deletions(-) diff --git a/src/main/java/bwapi/BWClientConfiguration.java b/src/main/java/bwapi/BWClientConfiguration.java index b72705aa..af2944ef 100644 --- a/src/main/java/bwapi/BWClientConfiguration.java +++ b/src/main/java/bwapi/BWClientConfiguration.java @@ -75,6 +75,8 @@ int getMaxFrameDurationMs() { * returns control to StarCraft, allowing the game to proceed while the bot continues to step in the background. This increases the likelihood of meeting * 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 * issuing commands later than intended, and a marginally larger memory footprint. + * + * Asynchronous mode is not compatible with latency compensation. Enabling asynchronous mode automatically disables latency compensation. */ public BWClientConfiguration withAsync(boolean value) { throwIfLocked(); diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java index eea9ed46..d94c58e2 100644 --- a/src/main/java/bwapi/BotWrapper.java +++ b/src/main/java/bwapi/BotWrapper.java @@ -36,6 +36,7 @@ void startNewGame(WrappedBuffer liveData, PerformanceMetrics performanceMetrics) } this.performanceMetrics = performanceMetrics; botGame = new Game(); + botGame.setConfiguration(configuration); botGame.botClientData().setBuffer(liveData); liveClientData.setBuffer(liveData); this.liveData = liveData; diff --git a/src/main/java/bwapi/Game.java b/src/main/java/bwapi/Game.java index a8aec781..045b794c 100644 --- a/src/main/java/bwapi/Game.java +++ b/src/main/java/bwapi/Game.java @@ -95,6 +95,7 @@ public final class Game { // USER DEFINED private Text.Size textSize = Text.Size.Default; + private BWClientConfiguration configuration = new BWClientConfiguration(); private boolean latcom = true; final SideEffectQueue sideEffects = new SideEffectQueue(); @@ -103,6 +104,10 @@ public final class Game { clientData = new ClientData(); } + void setConfiguration(BWClientConfiguration configuration) { + this.configuration = configuration; + } + ClientData botClientData() { return clientData; } @@ -270,7 +275,7 @@ void init() { observers = playerSet.stream().filter(p -> !p.equals(self()) && p.isObserver()) .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)); } - setLatCom(true); + setLatCom(!configuration.getAsync()); } void unitCreate(final int id) { @@ -2171,6 +2176,9 @@ final public boolean isLatComEnabled() { * @see #isLatComEnabled */ public void setLatCom(final boolean isEnabled) { + if (isEnabled && configuration.getAsync()) { + throw new IllegalStateException("Latency compensation is not compatible with JBWAPI asynchronous mode."); + } //update shared memory gameData().setHasLatCom(isEnabled); //update internal memory @@ -2654,35 +2662,49 @@ public int getRandomSeed() { * Convenience method for adding a unit command from raw arguments. */ void addUnitCommand(final int type, final int unit, final int target, final int x, final int y, final int extra) { - sideEffects.enqueue(SideEffect.addUnitCommand(type, unit, target, x, y, extra)); + enqueueOrDo(SideEffect.addUnitCommand(type, unit, target, x, y, extra)); } /** * Convenience method for adding a game command from raw arguments. */ void addCommand(final CommandType type, final int value1, final int value2) { - sideEffects.enqueue(SideEffect.addCommand(type, value1, value2)); + enqueueOrDo(SideEffect.addCommand(type, value1, value2)); } /** * Convenience method for adding a game command from raw arguments. */ void addCommand(final CommandType type, final String value1, final int value2) { - sideEffects.enqueue(SideEffect.addCommand(type, value1, value2)); + enqueueOrDo(SideEffect.addCommand(type, value1, value2)); } /** * Convenience method for adding a shape from raw arguments. */ void 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) { - sideEffects.enqueue(SideEffect.addShape(type, coordType, x1, y1, x2, y2, extra1, extra2, color, isSolid)); + enqueueOrDo(SideEffect.addShape(type, coordType, x1, y1, x2, y2, extra1, extra2, color, isSolid)); } /** * Convenience method for adding a shape from raw arguments. */ void 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) { - sideEffects.enqueue(SideEffect.addShape(type, coordType, x1, y1, x2, y2, text, extra2, color, isSolid)); + enqueueOrDo(SideEffect.addShape(type, coordType, x1, y1, x2, y2, text, extra2, color, isSolid)); + } + + /** + * Applies a side effect, either immediately (if operating synchronously) + * or by enqueuing it for later execution (if operating asynchronously). + * + * @param sideEffect + */ + void enqueueOrDo(SideEffect sideEffect) { + if (configuration.getAsync()) { + sideEffects.enqueue(sideEffect); + } else { + sideEffect.apply(gameData()); + } } void setAllUnits(List units) { diff --git a/src/test/java/bwapi/SynchronizationTest.java b/src/test/java/bwapi/SynchronizationTest.java index 23a79227..d652b96b 100644 --- a/src/test/java/bwapi/SynchronizationTest.java +++ b/src/test/java/bwapi/SynchronizationTest.java @@ -284,4 +284,18 @@ public void async_MeasurePerformance_IntentionallyBlocking() { }); environment.runGame(3); } + + @Test + public void async_DisablesLatencyCompensation() { + SynchronizationEnvironment environmentSync = new SynchronizationEnvironment(); + environmentSync.configuration.withAsync(false); + environmentSync.onFrame(1, () -> { assertTrue(environmentSync.bwClient.getGame().isLatComEnabled()); }); + environmentSync.runGame(2); + + SynchronizationEnvironment environmentAsync = new SynchronizationEnvironment(); + environmentAsync.configuration.withAsync(true).withAsyncFrameBufferCapacity(2); + environmentAsync.onFrame(1, () -> { assertFalse(environmentAsync.bwClient.getGame().isLatComEnabled()); }); + environmentAsync.onFrame(2, () -> { assertThrows(IllegalStateException.class, () -> environmentAsync.bwClient.getGame().setLatCom(true)); }); + environmentAsync.runGame(3); + } } From e76bed2a0f0aa0e2af9d0614dd384f1c8892555c Mon Sep 17 00:00:00 2001 From: dgant Date: Mon, 5 Oct 2020 18:31:31 -0400 Subject: [PATCH 50/56] Made configuration accessors public --- src/main/java/bwapi/BWClientConfiguration.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/bwapi/BWClientConfiguration.java b/src/main/java/bwapi/BWClientConfiguration.java index af2944ef..c440fa86 100644 --- a/src/main/java/bwapi/BWClientConfiguration.java +++ b/src/main/java/bwapi/BWClientConfiguration.java @@ -13,7 +13,7 @@ public BWClientConfiguration withDebugConnection(boolean value) { debugConnection = value; return this; } - boolean getDebugConnection() { + public boolean getDebugConnection() { return debugConnection; } private boolean debugConnection; @@ -26,7 +26,7 @@ public BWClientConfiguration withAutoContinue(boolean value) { autoContinue = value; return this; } - boolean getAutoContinue() { + public boolean getAutoContinue() { return autoContinue; } private boolean autoContinue = false; @@ -44,7 +44,7 @@ public BWClientConfiguration withUnlimitedFrameZero(boolean value) { unlimitedFrameZero = value; return this; } - boolean getUnlimitedFrameZero() { + public boolean getUnlimitedFrameZero() { return unlimitedFrameZero; } private boolean unlimitedFrameZero = true; @@ -60,7 +60,7 @@ public BWClientConfiguration withMaxFrameDurationMs(int value) { maxFrameDurationMs = value; return this; } - int getMaxFrameDurationMs() { + public int getMaxFrameDurationMs() { return maxFrameDurationMs; } private int maxFrameDurationMs = 40; @@ -83,7 +83,7 @@ public BWClientConfiguration withAsync(boolean value) { async = value; return this; } - boolean getAsync() { + public boolean getAsync() { return async; } private boolean async = false; @@ -97,7 +97,7 @@ public BWClientConfiguration withAsyncFrameBufferCapacity(int size) { asyncFrameBufferCapacity = size; return this; } - int getAsyncFrameBufferCapacity() { + public int getAsyncFrameBufferCapacity() { return asyncFrameBufferCapacity; } private int asyncFrameBufferCapacity = 10; @@ -114,7 +114,7 @@ public BWClientConfiguration withAsyncUnsafe(boolean value) { asyncUnsafe = value; return this; } - boolean getAsyncUnsafe() { + public boolean getAsyncUnsafe() { return asyncUnsafe; } private boolean asyncUnsafe = false; @@ -127,7 +127,7 @@ public BWClientConfiguration withLogVerbosely(boolean value) { logVerbosely = value; return this; } - boolean getLogVerbosely() { + public boolean getLogVerbosely() { return logVerbosely; } private boolean logVerbosely = false; From c193d9f77d7c894eacab40a5959258a1e8ed70fd Mon Sep 17 00:00:00 2001 From: JasperGeurtz Date: Tue, 6 Oct 2020 09:06:41 +0200 Subject: [PATCH 51/56] remove parallel tests and replace memcpy with unsafe.copyMemory --- pom.xml | 12 +++---- src/main/java/bwapi/FrameBuffer.java | 47 ++++++-------------------- src/main/java/bwapi/MSVCRT.java | 27 --------------- src/main/java/bwapi/UnsafeTools.java | 25 ++++++++++++++ src/main/java/bwapi/WrappedBuffer.java | 21 +----------- 5 files changed, 42 insertions(+), 90 deletions(-) delete mode 100644 src/main/java/bwapi/MSVCRT.java create mode 100644 src/main/java/bwapi/UnsafeTools.java diff --git a/pom.xml b/pom.xml index b3267f06..19fff033 100644 --- a/pom.xml +++ b/pom.xml @@ -60,12 +60,12 @@ org.apache.maven.plugins maven-surefire-plugin 2.22.0 - - all - true - -Xms1g - -Xmx1g - + + + + + + org.codehaus.mojo diff --git a/src/main/java/bwapi/FrameBuffer.java b/src/main/java/bwapi/FrameBuffer.java index 698d7e97..cebe9809 100644 --- a/src/main/java/bwapi/FrameBuffer.java +++ b/src/main/java/bwapi/FrameBuffer.java @@ -1,8 +1,7 @@ package bwapi; -import com.sun.jna.Platform; -import sun.nio.ch.DirectBuffer; -import java.nio.ByteBuffer; +import sun.misc.Unsafe; + import java.util.ArrayList; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; @@ -13,6 +12,7 @@ */ class FrameBuffer { private static final int BUFFER_SIZE = ClientData.GameData.SIZE; + private static final Unsafe unsafe = UnsafeTools.getUnsafe(); private WrappedBuffer liveData; private PerformanceMetrics performanceMetrics; @@ -158,24 +158,11 @@ void dequeue() { * @param source Address to copy from * @param destination Address to copy to * @param size Number of bytes to copy - * @return True if the copy succeeded */ - private boolean tryMemcpyBuffer(WrappedBuffer source, WrappedBuffer destination, long offset, int size) { + private void copyBuffer(WrappedBuffer source, WrappedBuffer destination, long offset, int size) { long addressSource = source.getAddress() + offset; long addressDestination = destination.getAddress() + offset; - try { - if (Platform.isWindows()) { - if (Platform.is64Bit()) { - MSVCRT.INSTANCE.memcpy(addressDestination, addressSource, size); - return true; - } else { - MSVCRT.INSTANCE.memcpy((int) addressDestination, (int) addressSource, size); - return true; - } - } - } - catch(Exception ignored) {} - return false; + unsafe.copyMemory(addressSource, addressDestination, size); } void copyBuffer(WrappedBuffer source, WrappedBuffer destination, boolean copyEverything) { @@ -185,16 +172,10 @@ void copyBuffer(WrappedBuffer source, WrappedBuffer destination, boolean copyEve but are prone to large amounts of variance. The normal Java way to execute this copy is via ByteBuffer.put(), which has reasonably good performance characteristics. - Experiments in 64-bit JRE have shown that using a native memcpy achieves a 35% speedup. - Experiments in 32-bit JRE show no difference in performance. - - So, speculatively, we attempt to do a native memcpy. */ if (copyEverything) { - if (tryMemcpyBuffer(source, destination, 0, FrameBuffer.BUFFER_SIZE)) { - return; - } + copyBuffer(source, destination, 0, FrameBuffer.BUFFER_SIZE); } else { // After the buffer has been filled the first time, // we can omit copying blocks of data which are unused or which don't change after game start. @@ -207,18 +188,10 @@ void copyBuffer(WrappedBuffer source, WrappedBuffer destination, boolean copyEve final int STRINGSSHAPES_START = 10962632; // getStringCount, ... getShapes final int STRINGSHAPES_END = 32242636; final int UNITFINDER_START = 32962644; - if ( - tryMemcpyBuffer(source, destination, 0, STATICTILES_START) - && tryMemcpyBuffer(source, destination, STATICTILES_END, REGION_START - STATICTILES_END) - && tryMemcpyBuffer(source, destination, REGION_END, STRINGSSHAPES_START - REGION_END) - && tryMemcpyBuffer(source, destination, STRINGSHAPES_END, UNITFINDER_START - STRINGSHAPES_END)) { - return; - } + copyBuffer(source, destination, 0, STATICTILES_START); + copyBuffer(source, destination, STATICTILES_END, REGION_START - STATICTILES_END); + copyBuffer(source, destination, REGION_END, STRINGSSHAPES_START - REGION_END); + copyBuffer(source, destination, STRINGSHAPES_END, UNITFINDER_START - STRINGSHAPES_END); } - - // There's no specific case where we expect to fail above, - // but this is a safe fallback regardless, - // and serves to document the known-good (and cross-platform, for BWAPI 5) way to executing the copy. - destination.copyFrom(source); } } diff --git a/src/main/java/bwapi/MSVCRT.java b/src/main/java/bwapi/MSVCRT.java deleted file mode 100644 index e7b1c55b..00000000 --- a/src/main/java/bwapi/MSVCRT.java +++ /dev/null @@ -1,27 +0,0 @@ -package bwapi; - -import com.sun.jna.Library; -import com.sun.jna.Native; - -/** - * JNI interface for accessing native MSVC code. - */ -interface MSVCRT extends Library { - MSVCRT INSTANCE = Native.load("msvcrt.dll", MSVCRT.class); - - /** - * 32-bit implementation of memcpy. - * @param dest A 32-bit address of a memory block (likely from a ByteBuffer) to copy to - * @param src A 32-bit address of a memory block (likely from a ByteBuffer) to copy from - * @param count The number of bytes to copy - */ - long memcpy(int dest, int src, int count); - - /** - * 64-bit implementation of memcpy. - * @param dest A 64-bit address of a memory block (likely from a ByteBuffer) to copy to - * @param src A 64-bit address of a memory block (likely from a ByteBuffer) to copy from - * @param count The number of bytes to copy - */ - long memcpy(long dest, long src, int count); -} \ No newline at end of file diff --git a/src/main/java/bwapi/UnsafeTools.java b/src/main/java/bwapi/UnsafeTools.java new file mode 100644 index 00000000..1e4c1672 --- /dev/null +++ b/src/main/java/bwapi/UnsafeTools.java @@ -0,0 +1,25 @@ +package bwapi; + +import sun.misc.Unsafe; + +import java.lang.reflect.Field; + +class UnsafeTools { + private static Unsafe unsafe; + + static { + try { + final Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); + theUnsafe.setAccessible(true); + unsafe = (Unsafe) theUnsafe.get(null); + + } catch (final Exception e) { + e.printStackTrace(); + System.exit(-1); + } + } + + static Unsafe getUnsafe() { + return unsafe; + } +} diff --git a/src/main/java/bwapi/WrappedBuffer.java b/src/main/java/bwapi/WrappedBuffer.java index 7bf58cc7..d155b80d 100644 --- a/src/main/java/bwapi/WrappedBuffer.java +++ b/src/main/java/bwapi/WrappedBuffer.java @@ -4,7 +4,6 @@ import com.sun.jna.Pointer; import sun.misc.Unsafe; -import java.lang.reflect.Field; import java.nio.ByteBuffer; /** @@ -14,19 +13,7 @@ class WrappedBuffer { private final ByteBuffer buffer; private final long address; - private static Unsafe unsafe; - - static { - try { - final Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); - theUnsafe.setAccessible(true); - unsafe = (Unsafe) theUnsafe.get(null); - - } catch (final Exception e) { - e.printStackTrace(); - System.exit(-1); - } - } + private static final Unsafe unsafe = UnsafeTools.getUnsafe(); WrappedBuffer(final int size) { this(new Memory(size), size); @@ -37,12 +24,6 @@ class WrappedBuffer { this.address = Pointer.nativeValue(pointer); } - void copyFrom(WrappedBuffer source) { - source.buffer.rewind(); - buffer.rewind(); - buffer.put(source.buffer); - } - byte getByte(final int offset) { return unsafe.getByte(address + offset); } From a078758307bc3be492368ccb45240e607f66abb0 Mon Sep 17 00:00:00 2001 From: dgant Date: Tue, 6 Oct 2020 08:45:55 -0400 Subject: [PATCH 52/56] Reenabled memory arguments for unit tests --- pom.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 19fff033..eca7c0ba 100644 --- a/pom.xml +++ b/pom.xml @@ -60,12 +60,12 @@ org.apache.maven.plugins maven-surefire-plugin 2.22.0 - + - - - + -Xms1g + -Xmx1g + org.codehaus.mojo From a7f2f0891382e5aac4a8300826fe953f5691a508 Mon Sep 17 00:00:00 2001 From: dgant Date: Tue, 6 Oct 2020 09:31:02 -0400 Subject: [PATCH 53/56] Removed more Windows-specific JNA calls --- src/main/java/bwapi/BWClient.java | 3 -- src/main/java/bwapi/Client.java | 5 --- src/main/java/bwapi/PerformanceMetric.java | 14 ++------ src/main/java/bwapi/PerformanceMetrics.java | 36 --------------------- 4 files changed, 2 insertions(+), 56 deletions(-) diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index d3b1b6e4..81004a2a 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -119,12 +119,9 @@ public void startGame(BWClientConfiguration gameConfiguration) { } } while (liveGameData.isInGame()) { - long ticksBefore = Kernel32.INSTANCE.GetTickCount(); - botWrapper.onFrame(); performanceMetrics.getFlushSideEffects().time(() -> getGame().sideEffects.flushTo(liveGameData)); performanceMetrics.getFrameDurationReceiveToSend().stopTiming(); - long ticksAfter = Kernel32.INSTANCE.GetTickCount(); client.sendFrameReceiveFrame(); if (!client.isConnected()) { diff --git a/src/main/java/bwapi/Client.java b/src/main/java/bwapi/Client.java index c14adf00..bf2a633a 100644 --- a/src/main/java/bwapi/Client.java +++ b/src/main/java/bwapi/Client.java @@ -246,12 +246,10 @@ void sendFrameReceiveFrame() { } metrics.getCommunicationSendToSent().stopTiming(); metrics.getFrameDurationReceiveToSent().stopTiming(); - metrics.getFrameDurationReceiveToSentGTC().stopTiming(); if (bwClient.doTime()) { final int eventCount = clientData.gameData().getEventCount(); metrics.getNumberOfEvents().record(eventCount); metrics.getNumberOfEventsTimesDurationReceiveToSent().record(eventCount * metrics.getFrameDurationReceiveToSent().getRunningTotal().getLast()); - metrics.getNumberOfEventsTimesDurationReceiveToSentGTC().record(eventCount * metrics.getFrameDurationReceiveToSentGTC().getRunningTotal().getLast()); } // Listen for BWAPI to indicate that a new frame is ready @@ -279,13 +277,10 @@ void sendFrameReceiveFrame() { if (bwClient.doTime()) { metrics.getFrameDurationReceiveToSend().startTiming(); metrics.getFrameDurationReceiveToSent().startTiming(); - metrics.getFrameDurationReceiveToSentGTC().startTiming(); } metrics.getFrameDurationReceiveToReceive().stopTiming(); - metrics.getFrameDurationReceiveToReceiveGTC().stopTiming(); if (bwClient.doTime()) { metrics.getFrameDurationReceiveToReceive().startTiming(); - metrics.getFrameDurationReceiveToReceiveGTC().startTiming(); } } diff --git a/src/main/java/bwapi/PerformanceMetric.java b/src/main/java/bwapi/PerformanceMetric.java index c31451ae..b296c93f 100644 --- a/src/main/java/bwapi/PerformanceMetric.java +++ b/src/main/java/bwapi/PerformanceMetric.java @@ -61,7 +61,6 @@ public String toString() { private final String name; private long timeStarted = 0; private int interrupted = 0; - private boolean usingGetTickCount = false; private final RunningTotal runningTotal = new RunningTotal(); private ArrayList thresholds = new ArrayList<>(); @@ -105,15 +104,6 @@ void timeIf(boolean condition, Runnable runnable) { } } - PerformanceMetric useGetTickCount() { - usingGetTickCount = true; - return this; - } - - long getNanoseconds() { - return usingGetTickCount ? Kernel32.INSTANCE.GetTickCount() * 1000000L: System.nanoTime(); - } - /** * Manually start timing. * The next call to stopTiming() will record the duration in fractional milliseconds. @@ -122,7 +112,7 @@ void startTiming() { if (timeStarted > 0) { ++interrupted; } - timeStarted = getNanoseconds(); + timeStarted = System.nanoTime(); } @@ -133,7 +123,7 @@ void startTiming() { void stopTiming() { if (timeStarted <= 0) return; // Use nanosecond resolution timer, but record in units of milliseconds. - long timeEnded = getNanoseconds(); + long timeEnded = System.nanoTime(); long timeDiff = timeEnded - timeStarted; timeStarted = 0; record(timeDiff / 1000000d); diff --git a/src/main/java/bwapi/PerformanceMetrics.java b/src/main/java/bwapi/PerformanceMetrics.java index 1ea0dc56..473f4351 100644 --- a/src/main/java/bwapi/PerformanceMetrics.java +++ b/src/main/java/bwapi/PerformanceMetrics.java @@ -29,16 +29,6 @@ public PerformanceMetric getFrameDurationReceiveToSent() { } private PerformanceMetric frameDurationReceiveToSent; - /** - * Duration of a frame cycle originating at - * the time when JBWAPI observes a new frame in shared memory. - * Uses GetTickCount() instead of System.nanoRime() - */ - public PerformanceMetric getFrameDurationReceiveToSentGTC() { - return frameDurationReceiveToSentGTC; - } - private PerformanceMetric frameDurationReceiveToSentGTC; - /** * Duration of a frame cycle originating at * the time when JBWAPI observes a new frame in shared memory. @@ -48,16 +38,6 @@ public PerformanceMetric getFrameDurationReceiveToReceive() { } private PerformanceMetric frameDurationReceiveToReceive; - /** - * Duration of a frame cycle originating at - * the time when JBWAPI observes a new frame in shared memory. - * Uses GetTickCount() instead of System.nanoRime() - */ - public PerformanceMetric getFrameDurationReceiveToReceiveGTC() { - return frameDurationReceiveToReceiveGTC; - } - private PerformanceMetric frameDurationReceiveToReceiveGTC; - /** * Time spent copying game data from system pipe shared memory to a frame buffer. * Applicable only in asynchronous mode. @@ -188,19 +168,6 @@ public PerformanceMetric getNumberOfEventsTimesDurationReceiveToSent() { } PerformanceMetric numberOfEventsTimesDurationReceiveToSent; - /** - * The number of events sent by BWAPI each frame, - * multiplied by the duration of time spent on that frame (receive-to-sent), - * and using GetTickCount() instead of System.nanoTime(). - * Helps detect use of broken BWAPI 4.4 tournament modules, with respect to: - * * - https://github.com/bwapi/bwapi/issues/860 - * * - https://github.com/davechurchill/StarcraftAITournamentManager/issues/42 - */ - public PerformanceMetric getNumberOfEventsTimesDurationReceiveToSentGTC() { - return numberOfEventsTimesDurationReceiveToSentGTC; - } - PerformanceMetric numberOfEventsTimesDurationReceiveToSentGTC; - private BWClientConfiguration configuration; private ArrayList performanceMetrics = new ArrayList<>(); @@ -216,9 +183,7 @@ public void reset() { performanceMetrics.clear(); frameDurationReceiveToSend = new PerformanceMetric(this, "Frame duration: After receiving 'frame ready' -> before sending 'frame done'", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); frameDurationReceiveToSent = new PerformanceMetric(this, "Frame duration: After receiving 'frame ready' -> after sending 'frame done'", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); - frameDurationReceiveToSentGTC = new PerformanceMetric(this, "Frame duration: After receiving 'frame ready' -> after sending 'frame done' (Using GetTickCount())", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85).useGetTickCount(); frameDurationReceiveToReceive = new PerformanceMetric(this, "Frame duration: After receiving 'frame ready' -> receiving next 'frame ready'", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); - frameDurationReceiveToReceiveGTC = new PerformanceMetric(this, "Frame duration: After receiving 'frame ready' -> receiving next 'frame ready' (Using GetTickCount())", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85).useGetTickCount(); communicationSendToReceive = new PerformanceMetric(this, "BWAPI duration: Before sending 'frame done' -> After receiving 'frame ready'", 1, 3, 5, 10, 15, 20, 30); communicationSendToSent = new PerformanceMetric(this, "BWAPI duration: Before sending 'frame done' -> After sending 'frame done'", 1, 3, 5, 10, 15, 20, 30); communicationListenToReceive = new PerformanceMetric(this, "BWAPI duration: Before listening for 'frame ready' -> After receiving 'frame ready'", 1, 3, 5, 10, 15, 20, 30); @@ -233,7 +198,6 @@ public void reset() { excessSleep = new PerformanceMetric(this, "Excess duration of client sleep", 1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); numberOfEvents = new PerformanceMetric(this, "Number of events received from BWAPI", 1, 2, 3, 4, 5, 6, 8, 10, 15, 20); numberOfEventsTimesDurationReceiveToSent = new PerformanceMetric(this, "Number of events received from BWAPI, multiplied by the receive-to-sent duration of that frame", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); - numberOfEventsTimesDurationReceiveToSentGTC = new PerformanceMetric(this, "Number of events received from BWAPI, multiplied by the receive-to-sent duration of that frame (Using GetTickCount())", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); } void addMetric(PerformanceMetric performanceMetric) { From 34e5d4cf23b441e3fc5c005ea9f3a8166974dad3 Mon Sep 17 00:00:00 2001 From: dgant Date: Sun, 27 Dec 2020 17:25:45 -0500 Subject: [PATCH 54/56] Added test re-runs. Made classes final to aid inlining --- pom.xml | 1 + src/main/java/bwapi/Bullet.java | 2 +- src/main/java/bwapi/Color.java | 2 +- src/main/java/bwapi/Force.java | 2 +- src/main/java/bwapi/Player.java | 2 +- src/main/java/bwapi/Position.java | 2 +- src/main/java/bwapi/Region.java | 2 +- src/main/java/bwapi/TilePosition.java | 2 +- src/main/java/bwapi/Unit.java | 2 +- src/main/java/bwapi/UnitCommand.java | 2 +- src/test/java/bwapi/SynchronizationEnvironment.java | 3 ++- 11 files changed, 12 insertions(+), 10 deletions(-) diff --git a/pom.xml b/pom.xml index eca7c0ba..e87c58e8 100644 --- a/pom.xml +++ b/pom.xml @@ -63,6 +63,7 @@ + 10 -Xms1g -Xmx1g diff --git a/src/main/java/bwapi/Bullet.java b/src/main/java/bwapi/Bullet.java index 76612209..e462500b 100644 --- a/src/main/java/bwapi/Bullet.java +++ b/src/main/java/bwapi/Bullet.java @@ -27,7 +27,7 @@ * @see Game#getBullets * @see Bullet#exists */ -public class Bullet implements Comparable { +public final class Bullet implements Comparable { private final BulletData bulletData; private final int id; private final Game game; diff --git a/src/main/java/bwapi/Color.java b/src/main/java/bwapi/Color.java index 03b86ada..f37544de 100644 --- a/src/main/java/bwapi/Color.java +++ b/src/main/java/bwapi/Color.java @@ -11,7 +11,7 @@ * Starcraft uses a 256 color palette for rendering. Thus, the colors available are * limited to this palette. */ -public class Color { +public final class Color { /** * The default color for Player 1. */ diff --git a/src/main/java/bwapi/Force.java b/src/main/java/bwapi/Force.java index 9c20e76a..3e01e613 100644 --- a/src/main/java/bwapi/Force.java +++ b/src/main/java/bwapi/Force.java @@ -13,7 +13,7 @@ * It is not called a team because players on the same force do not necessarily need * to be allied at the beginning of a match. */ -public class Force implements Comparable { +public final class Force implements Comparable { private final Game game; private final int id; diff --git a/src/main/java/bwapi/Player.java b/src/main/java/bwapi/Player.java index 8024d75c..474cb658 100644 --- a/src/main/java/bwapi/Player.java +++ b/src/main/java/bwapi/Player.java @@ -18,7 +18,7 @@ * @see PlayerType * @see Race */ -public class Player implements Comparable { +public final class Player implements Comparable { private final PlayerData playerData; private final Game game; private final int id; diff --git a/src/main/java/bwapi/Position.java b/src/main/java/bwapi/Position.java index 9f8187b2..20b71e7b 100644 --- a/src/main/java/bwapi/Position.java +++ b/src/main/java/bwapi/Position.java @@ -1,7 +1,7 @@ package bwapi; -public class Position extends Point { +public final class Position extends Point { public static final int SIZE_IN_PIXELS = 1; public static final Position Invalid = new Position(32000 / SIZE_IN_PIXELS, 32000 / SIZE_IN_PIXELS); diff --git a/src/main/java/bwapi/Region.java b/src/main/java/bwapi/Region.java index 80aec608..a7c20119 100644 --- a/src/main/java/bwapi/Region.java +++ b/src/main/java/bwapi/Region.java @@ -21,7 +21,7 @@ * @see Game#getRegionAt * @see Unit#getRegion */ -public class Region implements Comparable { +public final class Region implements Comparable { private final RegionData regionData; private final Game game; diff --git a/src/main/java/bwapi/TilePosition.java b/src/main/java/bwapi/TilePosition.java index 8c63f3a9..b2dfa767 100644 --- a/src/main/java/bwapi/TilePosition.java +++ b/src/main/java/bwapi/TilePosition.java @@ -1,6 +1,6 @@ package bwapi; -public class TilePosition extends Point { +public final class TilePosition extends Point { public static final int SIZE_IN_PIXELS = 32; public static final TilePosition Invalid = new TilePosition(32000 / SIZE_IN_PIXELS, 32000 / SIZE_IN_PIXELS); diff --git a/src/main/java/bwapi/Unit.java b/src/main/java/bwapi/Unit.java index 50c9114f..6e3c2462 100644 --- a/src/main/java/bwapi/Unit.java +++ b/src/main/java/bwapi/Unit.java @@ -39,7 +39,7 @@ * However for units that were owned by the player, {@link #getPlayer} and {@link #getType} will continue to work for units * that have been destroyed. */ -public class Unit implements Comparable { +public final class Unit implements Comparable { private static final Set gatheringGasOrders = EnumSet.of( Harvest1, Harvest2, MoveToGas, WaitForGas, HarvestGas, ReturnGas, ResetCollision); private static final Set gatheringMineralOrders = EnumSet.of( diff --git a/src/main/java/bwapi/UnitCommand.java b/src/main/java/bwapi/UnitCommand.java index f0c67d8c..4752d231 100644 --- a/src/main/java/bwapi/UnitCommand.java +++ b/src/main/java/bwapi/UnitCommand.java @@ -6,7 +6,7 @@ import static bwapi.TechType.*; import static bwapi.UnitCommandType.*; -public class UnitCommand { +public final class UnitCommand { Unit unit; UnitCommandType type; Unit target = null; diff --git a/src/test/java/bwapi/SynchronizationEnvironment.java b/src/test/java/bwapi/SynchronizationEnvironment.java index 03fe2628..049a8287 100644 --- a/src/test/java/bwapi/SynchronizationEnvironment.java +++ b/src/test/java/bwapi/SynchronizationEnvironment.java @@ -32,7 +32,8 @@ class SynchronizationEnvironment { bwapiDelayMs = 0; onFrames = new HashMap<>(); - when(client.mapFile()).thenReturn(GameBuilder.binToBufferUnchecked(GameBuilder.DEFAULT_BUFFER_PATH)); + WrappedBuffer newGameState = GameBuilder.binToBufferUnchecked(GameBuilder.DEFAULT_BUFFER_PATH); + when(client.mapFile()).thenReturn(newGameState); when(client.liveClientData()).thenReturn(new ClientData()); client.liveClientData().setBuffer(client.mapFile()); client.liveClientData().gameData().setFrameCount(-1); From bcaafb9c16f86edde2def9d1933f8e0cb3e38bbd Mon Sep 17 00:00:00 2001 From: dgant Date: Sun, 27 Dec 2020 21:21:38 -0500 Subject: [PATCH 55/56] De-finalized classes to fix mocking. Widened threshold on synchronization tests --- src/main/java/bwapi/Player.java | 2 +- src/main/java/bwapi/Unit.java | 2 +- src/test/java/bwapi/SynchronizationTest.java | 22 ++++++++++---------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/java/bwapi/Player.java b/src/main/java/bwapi/Player.java index 474cb658..8024d75c 100644 --- a/src/main/java/bwapi/Player.java +++ b/src/main/java/bwapi/Player.java @@ -18,7 +18,7 @@ * @see PlayerType * @see Race */ -public final class Player implements Comparable { +public class Player implements Comparable { private final PlayerData playerData; private final Game game; private final int id; diff --git a/src/main/java/bwapi/Unit.java b/src/main/java/bwapi/Unit.java index 6e3c2462..50c9114f 100644 --- a/src/main/java/bwapi/Unit.java +++ b/src/main/java/bwapi/Unit.java @@ -39,7 +39,7 @@ * However for units that were owned by the player, {@link #getPlayer} and {@link #getType} will continue to work for units * that have been destroyed. */ -public final class Unit implements Comparable { +public class Unit implements Comparable { private static final Set gatheringGasOrders = EnumSet.of( Harvest1, Harvest2, MoveToGas, WaitForGas, HarvestGas, ReturnGas, ResetCollision); private static final Set gatheringMineralOrders = EnumSet.of( diff --git a/src/test/java/bwapi/SynchronizationTest.java b/src/test/java/bwapi/SynchronizationTest.java index d652b96b..1bf111fa 100644 --- a/src/test/java/bwapi/SynchronizationTest.java +++ b/src/test/java/bwapi/SynchronizationTest.java @@ -73,12 +73,12 @@ public void sync_IfDelay_ThenNoBuffer() { public void async_IfBotDelay_ThenClientBuffers() { SynchronizationEnvironment environment = new SynchronizationEnvironment(); environment.configuration - .withAsync(true) - .withMaxFrameDurationMs(10) - .withAsyncFrameBufferCapacity(4); + .withAsync(true) + .withMaxFrameDurationMs(100) + .withAsyncFrameBufferCapacity(4); environment.onFrame(1, () -> { - sleepUnchecked(50); + sleepUnchecked(500); assertEquals("Bot should be observing an old frame", 1, environment.bwClient.getGame().getFrameCount()); assertEquals("Client should be as far ahead as the frame buffer allows", 5, environment.liveGameData().getFrameCount()); assertEquals("Bot should be behind the live game", 4, environment.bwClient.framesBehind()); @@ -97,20 +97,20 @@ public void async_IfBotDelay_ThenClientBuffers() { public void async_IfBotDelay_ThenClientStalls() { SynchronizationEnvironment environment = new SynchronizationEnvironment(); environment.configuration - .withAsync(true) - .withMaxFrameDurationMs(50) - .withAsyncFrameBufferCapacity(5); + .withAsync(true) + .withMaxFrameDurationMs(200) + .withAsyncFrameBufferCapacity(5); environment.onFrame(1, () -> { - sleepUnchecked(125); + sleepUnchecked(500); assertEquals("3: Bot should be observing an old frame", 1, environment.bwClient.getGame().getFrameCount()); assertEquals("3: Client should have progressed as slowly as possible", 3, environment.liveGameData().getFrameCount()); assertEquals("3: Bot should be behind the live game by as little as possible", 2, environment.bwClient.framesBehind()); - sleepUnchecked(50); + sleepUnchecked(200); assertEquals("4: Bot should be observing an old frame", 1, environment.bwClient.getGame().getFrameCount()); assertEquals("4: Client should have progressed as slowly as possible", 4, environment.liveGameData().getFrameCount()); assertEquals("4: Bot should be behind the live game by as little as possible", 3, environment.bwClient.framesBehind()); - sleepUnchecked(50); + sleepUnchecked(200); assertEquals("5: Bot should be observing an old frame", 1, environment.bwClient.getGame().getFrameCount()); assertEquals("5: Client should have progressed as slowly as possible", 5, environment.liveGameData().getFrameCount()); assertEquals("5: Bot should be behind the live game by as little as possible", 4, environment.bwClient.framesBehind()); @@ -211,7 +211,7 @@ public void async_MeasurePerformance_FrameBufferSizeAndFramesBehind() { * Number of milliseconds of leeway to give in potentially noisy performance metrics. * Increase if tests are flaky due to variance in execution speed. */ - private final static long MS_MARGIN = 10; + private final static long MS_MARGIN = 20; @Test public void MeasurePerformance_BotResponse() { From cdb67d5456a0459094b24f45149adfa39f848e14 Mon Sep 17 00:00:00 2001 From: dgant Date: Sun, 27 Dec 2020 21:43:26 -0500 Subject: [PATCH 56/56] Trying to ensure tests are run with adequate memory available --- pom.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 82bcc38e..a7b36e28 100644 --- a/pom.xml +++ b/pom.xml @@ -64,8 +64,7 @@ 10 - -Xms1g - -Xmx1g + -Xms1g -Xmx1g