Skip to content

Commit dcc7537

Browse files
author
Jasper Geurtz
committed
Reduce FrameBuffer size and update copyBuffer to packed dynamic layout
1 parent e34d5c2 commit dcc7537

1 file changed

Lines changed: 178 additions & 197 deletions

File tree

Lines changed: 178 additions & 197 deletions
Original file line numberDiff line numberDiff line change
@@ -1,197 +1,178 @@
1-
package bwapi;
2-
3-
import sun.misc.Unsafe;
4-
5-
import java.util.ArrayList;
6-
import java.util.concurrent.locks.Condition;
7-
import java.util.concurrent.locks.Lock;
8-
import java.util.concurrent.locks.ReentrantLock;
9-
10-
/**
11-
* Circular buffer of game states.
12-
*/
13-
class FrameBuffer {
14-
private static final int BUFFER_SIZE = ClientData.GameData.SIZE;
15-
private static final Unsafe unsafe = UnsafeTools.getUnsafe();
16-
17-
private WrappedBuffer liveData;
18-
private PerformanceMetrics performanceMetrics;
19-
private final BWClientConfiguration configuration;
20-
private final int capacity;
21-
private int stepGame = 0;
22-
private int stepBot = 0;
23-
private final ArrayList<WrappedBuffer> dataBuffer = new ArrayList<>();
24-
25-
private final Lock lockWrite = new ReentrantLock();
26-
final Lock lockSize = new ReentrantLock();
27-
final Condition conditionSize = lockSize.newCondition();
28-
29-
FrameBuffer(BWClientConfiguration configuration) {
30-
this.capacity = configuration.getAsyncFrameBufferCapacity();
31-
this.configuration = configuration;
32-
while(dataBuffer.size() < capacity) {
33-
dataBuffer.add(new WrappedBuffer(BUFFER_SIZE));
34-
}
35-
}
36-
37-
/**
38-
* Resets for a new game
39-
*/
40-
void initialize(WrappedBuffer liveData, PerformanceMetrics performanceMetrics) {
41-
this.liveData = liveData;
42-
this.performanceMetrics = performanceMetrics;
43-
stepGame = 0;
44-
stepBot = 0;
45-
}
46-
47-
/**
48-
* @return The number of frames currently buffered ahead of the bot's current frame
49-
*/
50-
synchronized int framesBuffered() {
51-
return stepGame - stepBot;
52-
}
53-
54-
/**
55-
* @return Number of frames currently stored in the buffer
56-
*/
57-
int size() {
58-
lockSize.lock();
59-
try {
60-
return framesBuffered();
61-
} finally {
62-
lockSize.unlock();
63-
}
64-
}
65-
66-
/**
67-
* @return Whether the frame buffer is empty and has no frames available for the bot to consume.
68-
*/
69-
boolean empty() {
70-
return size() <= 0;
71-
}
72-
73-
/**
74-
* @return Whether the frame buffer is full and can not buffer any additional frames.
75-
* When the frame buffer is full, JBWAPI must wait for the bot to complete a frame before returning control to StarCraft.
76-
*/
77-
boolean full() {
78-
lockSize.lock();
79-
try {
80-
return framesBuffered() >= capacity;
81-
} finally {
82-
lockSize.unlock();
83-
}
84-
}
85-
86-
private int indexGame() {
87-
return stepGame % capacity;
88-
}
89-
90-
private int indexBot() {
91-
return stepBot % capacity;
92-
}
93-
94-
/**
95-
* Copy dataBuffer from shared memory into the head of the frame buffer.
96-
*/
97-
void enqueueFrame() {
98-
lockWrite.lock();
99-
try {
100-
lockSize.lock();
101-
try {
102-
while (full()) {
103-
configuration.log("Main: Waiting for frame buffer capacity");
104-
performanceMetrics.getIntentionallyBlocking().startTiming();
105-
conditionSize.awaitUninterruptibly();
106-
}
107-
performanceMetrics.getIntentionallyBlocking().stopTiming();
108-
} finally { lockSize.unlock(); };
109-
110-
// For the first frame of the game, populate all buffers completely
111-
// This is to ensure all buffers have access to immutable data like regions/walkability/buildability
112-
// Afterwards, we want to shorten this process by only copying important and mutable data
113-
if (stepGame == 0) {
114-
for (WrappedBuffer frameBuffer : dataBuffer) {
115-
copyBuffer(liveData, frameBuffer, true);
116-
}
117-
} else {
118-
performanceMetrics.getCopyingToBuffer().time(() -> {
119-
WrappedBuffer dataTarget = dataBuffer.get(indexGame());
120-
copyBuffer(liveData, dataTarget, false);
121-
});
122-
}
123-
124-
lockSize.lock();
125-
try {
126-
performanceMetrics.getFrameBufferSize().record(framesBuffered());
127-
++stepGame;
128-
conditionSize.signalAll();
129-
} finally { lockSize.unlock(); }
130-
} finally { lockWrite.unlock(); }
131-
}
132-
133-
/**
134-
* Peeks the front-most value in the buffer.
135-
*/
136-
WrappedBuffer peek() {
137-
lockSize.lock();
138-
try {
139-
while(empty()) conditionSize.awaitUninterruptibly();
140-
return dataBuffer.get(indexBot());
141-
} finally { lockSize.unlock(); }
142-
}
143-
144-
/**
145-
* Removes the front-most frame in the buffer.
146-
*/
147-
void dequeue() {
148-
lockSize.lock();
149-
try {
150-
while(empty()) conditionSize.awaitUninterruptibly();
151-
++stepBot;
152-
conditionSize.signalAll();
153-
} finally { lockSize.unlock(); }
154-
}
155-
156-
/**
157-
*
158-
* @param source Address to copy from
159-
* @param destination Address to copy to
160-
* @param size Number of bytes to copy
161-
*/
162-
private void copyBuffer(WrappedBuffer source, WrappedBuffer destination, long offset, int size) {
163-
long addressSource = source.getAddress() + offset;
164-
long addressDestination = destination.getAddress() + offset;
165-
unsafe.copyMemory(addressSource, addressDestination, size);
166-
}
167-
168-
void copyBuffer(WrappedBuffer source, WrappedBuffer destination, boolean copyEverything) {
169-
/*
170-
The speed at which we copy data into the frame buffer is a major cost of JBWAPI's asynchronous operation.
171-
Copy times observed in the wild for the complete buffer usually range from 2.6ms - 19ms
172-
but are prone to large amounts of variance.
173-
174-
The normal Java way to execute this copy is via ByteBuffer.put(), which has reasonably good performance characteristics.
175-
*/
176-
177-
if (copyEverything) {
178-
copyBuffer(source, destination, 0, FrameBuffer.BUFFER_SIZE);
179-
} else {
180-
// After the buffer has been filled the first time,
181-
// we can omit copying blocks of data which are unused or which don't change after game start.
182-
// These blocks account for *most* of the 33MB shared memory,
183-
// so omitting them drastically reduces the copy duration
184-
final int STATICTILES_START = 3447004; // getGroundHeight, isWalkable, isBuildable
185-
final int STATICTILES_END = 4823260;
186-
final int REGION_START = 5085404; // getMapTileRegionId, ..., getRegions
187-
final int REGION_END = 10586480;
188-
final int STRINGSSHAPES_START = 10962632; // getStringCount, ... getShapes
189-
final int STRINGSHAPES_END = 32242636;
190-
final int UNITFINDER_START = 32962644;
191-
copyBuffer(source, destination, 0, STATICTILES_START);
192-
copyBuffer(source, destination, STATICTILES_END, REGION_START - STATICTILES_END);
193-
copyBuffer(source, destination, REGION_END, STRINGSSHAPES_START - REGION_END);
194-
copyBuffer(source, destination, STRINGSHAPES_END, UNITFINDER_START - STRINGSHAPES_END);
195-
}
196-
}
197-
}
1+
package bwapi;
2+
3+
import sun.misc.Unsafe;
4+
5+
import java.util.ArrayList;
6+
import java.util.concurrent.locks.Condition;
7+
import java.util.concurrent.locks.Lock;
8+
import java.util.concurrent.locks.ReentrantLock;
9+
10+
/**
11+
* Circular buffer of game states.
12+
*/
13+
class FrameBuffer {
14+
static final int STATICTILES_START = 3447004; // getGroundHeight, isWalkable, isBuildable
15+
static final int STATICTILES_END = 4823260;
16+
static final int REGION_START = 5085404; // getMapTileRegionId, ..., getRegions
17+
static final int REGION_END = 10586480;
18+
static final int STRINGSSHAPES_START = 10962632; // getStringCount, ... getShapes
19+
static final int STRINGSHAPES_END = 32242636;
20+
static final int UNITFINDER_START = 32962644;
21+
22+
static final int DYNAMIC_SLICE1_SIZE = STATICTILES_START; // 3447004
23+
static final int DYNAMIC_SLICE2_SIZE = REGION_START - STATICTILES_END; // 262144
24+
static final int DYNAMIC_SLICE3_SIZE = STRINGSSHAPES_START - REGION_END; // 376152
25+
static final int DYNAMIC_SLICE4_SIZE = UNITFINDER_START - STRINGSHAPES_END; // 720008
26+
27+
static final int DEST_SLICE1_OFFSET = 0;
28+
static final int DEST_SLICE2_OFFSET = DYNAMIC_SLICE1_SIZE; // 3447004
29+
static final int DEST_SLICE3_OFFSET = DEST_SLICE2_OFFSET + DYNAMIC_SLICE2_SIZE; // 3709148
30+
static final int DEST_SLICE4_OFFSET = DEST_SLICE3_OFFSET + DYNAMIC_SLICE3_SIZE; // 4085300
31+
32+
static final int BUFFER_SIZE = DEST_SLICE4_OFFSET + DYNAMIC_SLICE4_SIZE; // 4805308
33+
private static final Unsafe unsafe = UnsafeTools.getUnsafe();
34+
35+
private WrappedBuffer liveData;
36+
private PerformanceMetrics performanceMetrics;
37+
private final BWClientConfiguration configuration;
38+
private final int capacity;
39+
private int stepGame = 0;
40+
private int stepBot = 0;
41+
private final ArrayList<WrappedBuffer> dataBuffer = new ArrayList<>();
42+
43+
private final Lock lockWrite = new ReentrantLock();
44+
final Lock lockSize = new ReentrantLock();
45+
final Condition conditionSize = lockSize.newCondition();
46+
47+
FrameBuffer(BWClientConfiguration configuration) {
48+
this.capacity = configuration.getAsyncFrameBufferCapacity();
49+
this.configuration = configuration;
50+
while(dataBuffer.size() < capacity) {
51+
dataBuffer.add(new WrappedBuffer(BUFFER_SIZE, true));
52+
}
53+
}
54+
55+
/**
56+
* Resets for a new game
57+
*/
58+
void initialize(WrappedBuffer liveData, PerformanceMetrics performanceMetrics) {
59+
this.liveData = liveData;
60+
this.performanceMetrics = performanceMetrics;
61+
stepGame = 0;
62+
stepBot = 0;
63+
}
64+
65+
/**
66+
* @return The number of frames currently buffered ahead of the bot's current frame
67+
*/
68+
synchronized int framesBuffered() {
69+
return stepGame - stepBot;
70+
}
71+
72+
/**
73+
* @return Number of frames currently stored in the buffer
74+
*/
75+
int size() {
76+
lockSize.lock();
77+
try {
78+
return framesBuffered();
79+
} finally {
80+
lockSize.unlock();
81+
}
82+
}
83+
84+
/**
85+
* @return Whether the frame buffer is empty and has no frames available for the bot to consume.
86+
*/
87+
boolean empty() {
88+
return size() <= 0;
89+
}
90+
91+
/**
92+
* @return Whether the frame buffer is full and can not buffer any additional frames.
93+
* When the frame buffer is full, JBWAPI must wait for the bot to complete a frame before returning control to StarCraft.
94+
*/
95+
boolean full() {
96+
lockSize.lock();
97+
try {
98+
return framesBuffered() >= capacity;
99+
} finally {
100+
lockSize.unlock();
101+
}
102+
}
103+
104+
private int indexGame() {
105+
return stepGame % capacity;
106+
}
107+
108+
private int indexBot() {
109+
return stepBot % capacity;
110+
}
111+
112+
/**
113+
* Copy dataBuffer from shared memory into the head of the frame buffer.
114+
*/
115+
void enqueueFrame() {
116+
lockWrite.lock();
117+
try {
118+
lockSize.lock();
119+
try {
120+
while (full()) {
121+
configuration.log("Main: Waiting for frame buffer capacity");
122+
performanceMetrics.getIntentionallyBlocking().startTiming();
123+
conditionSize.awaitUninterruptibly();
124+
}
125+
performanceMetrics.getIntentionallyBlocking().stopTiming();
126+
} finally { lockSize.unlock(); };
127+
128+
// Copy dynamic game information into the current frame buffer slot.
129+
// Static information (e.g. walkability/buildability/regions) is accessed
130+
// directly from the main shared memory buffer by AsyncClientData.
131+
if (stepGame == 0) {
132+
copyBuffer(liveData, dataBuffer.get(indexGame()));
133+
} else {
134+
performanceMetrics.getCopyingToBuffer().time(() -> {
135+
WrappedBuffer dataTarget = dataBuffer.get(indexGame());
136+
copyBuffer(liveData, dataTarget);
137+
});
138+
}
139+
140+
lockSize.lock();
141+
try {
142+
performanceMetrics.getFrameBufferSize().record(framesBuffered());
143+
++stepGame;
144+
conditionSize.signalAll();
145+
} finally { lockSize.unlock(); }
146+
} finally { lockWrite.unlock(); }
147+
}
148+
149+
/**
150+
* Peeks the front-most value in the buffer.
151+
*/
152+
WrappedBuffer peek() {
153+
lockSize.lock();
154+
try {
155+
while(empty()) conditionSize.awaitUninterruptibly();
156+
return dataBuffer.get(indexBot());
157+
} finally { lockSize.unlock(); }
158+
}
159+
160+
/**
161+
* Removes the front-most frame in the buffer.
162+
*/
163+
void dequeue() {
164+
lockSize.lock();
165+
try {
166+
while(empty()) conditionSize.awaitUninterruptibly();
167+
++stepBot;
168+
conditionSize.signalAll();
169+
} finally { lockSize.unlock(); }
170+
}
171+
172+
void copyBuffer(WrappedBuffer source, WrappedBuffer destination) {
173+
unsafe.copyMemory(source.getAddress() + 0, destination.getAddress() + DEST_SLICE1_OFFSET, DYNAMIC_SLICE1_SIZE);
174+
unsafe.copyMemory(source.getAddress() + STATICTILES_END, destination.getAddress() + DEST_SLICE2_OFFSET, DYNAMIC_SLICE2_SIZE);
175+
unsafe.copyMemory(source.getAddress() + REGION_END, destination.getAddress() + DEST_SLICE3_OFFSET, DYNAMIC_SLICE3_SIZE);
176+
unsafe.copyMemory(source.getAddress() + STRINGSHAPES_END, destination.getAddress() + DEST_SLICE4_OFFSET, DYNAMIC_SLICE4_SIZE);
177+
}
178+
}

0 commit comments

Comments
 (0)