@@ -27,6 +27,9 @@ of this software and associated documentation files (the "Software"), to deal
2727
2828import java .nio .ByteBuffer ;
2929import java .util .ArrayList ;
30+ import java .util .concurrent .locks .Condition ;
31+ import java .util .concurrent .locks .Lock ;
32+ import java .util .concurrent .locks .ReentrantLock ;
3033
3134/**
3235 * Circular buffer of game states.
@@ -40,7 +43,11 @@ class FrameBuffer {
4043 private ArrayList <ByteBuffer > dataBuffer = new ArrayList <>();
4144
4245 // Synchronization locks
43- private Object stepCount = new Object ();
46+ private final Lock writeLock = new ReentrantLock ();
47+ private final Lock readLock = writeLock ;
48+ final Lock lockCapacity = new ReentrantLock ();
49+ final Condition conditionFull = lockCapacity .newCondition ();
50+ final Condition conditionEmpty = lockCapacity .newCondition ();
4451
4552 FrameBuffer (int size , ByteBuffer source ) {
4653 this .size = size ;
@@ -60,47 +67,103 @@ synchronized int framesBuffered() {
6067 /**
6168 * @return Whether the frame buffer is empty and has no frames available for the bot to consume.
6269 */
63- synchronized boolean empty () {
64- return framesBuffered () <= 0 ;
70+ boolean empty () {
71+ lockCapacity .lock ();
72+ try {
73+ return framesBuffered () <= 0 ;
74+ } finally {
75+ lockCapacity .unlock ();
76+ }
6577 }
6678
6779 /**
6880 * @return Whether the frame buffer is full and can not buffer any additional frames.
6981 * When the frame buffer is full, JBWAPI must wait for the bot to complete a frame before returning control to StarCraft.
7082 */
71- synchronized boolean full () {
72- return framesBuffered () >= size - 1 ;
83+ boolean full () {
84+ lockCapacity .lock ();
85+ try {
86+ return framesBuffered () >= size - 1 ;
87+ } finally {
88+ lockCapacity .unlock ();
89+ }
7390 }
7491
75- synchronized private int indexGame () {
92+ private int indexGame () {
7693 return stepGame % size ;
7794 }
7895
79- synchronized private int indexBot () {
96+ private int indexBot () {
8097 return stepBot % size ;
8198 }
8299
83100 /**
84101 * Copy dataBuffer from shared memory into the head of the frame buffer.
85102 */
86103 void enqueueFrame () {
87- while (full ()) try { Thread .sleep (0 , 100 ); } catch (InterruptedException ignored ) {}
88- System .out .println ("FrameBuffer: Enqueuing buffer " + indexGame () + " on game step #" + stepGame + " with " + framesBuffered () + " frames buffered." );
89- ByteBuffer dataTarget = dataBuffer .get (indexGame ());
90- dataSource .rewind ();
91- dataTarget .rewind ();
92- dataTarget .put (dataSource );
93- ++stepGame ;
104+ // In practice we don't particularly expect multiple threads to write to this, but support it just to be safe.
105+ writeLock .lock ();
106+ try {
107+ // Wait for the buffer to have space to enqueue
108+ lockCapacity .lock ();
109+ try {
110+ while (full ()) {
111+ conditionFull .awaitUninterruptibly ();
112+ }
113+ } finally {
114+ lockCapacity .unlock ();
115+ }
116+
117+ System .out .println ("FrameBuffer: Enqueuing buffer " + indexGame () + " on game step #" + stepGame + " with " + framesBuffered () + " frames buffered." );
118+ ByteBuffer dataTarget = dataBuffer .get (indexGame ());
119+ dataSource .rewind ();
120+ dataTarget .rewind ();
121+ dataTarget .put (dataSource );
122+ ++stepGame ;
123+
124+ // Notify anyone waiting for something to dequeue
125+ lockCapacity .lock ();
126+ try {
127+ conditionEmpty .signalAll ();
128+ } finally {
129+ lockCapacity .unlock ();
130+ }
131+ } finally {
132+ writeLock .unlock ();
133+ }
94134 }
95135
96136 /**
97137 * Points the bot to the next frame in the buffer.
98138 */
99139 ByteBuffer dequeueFrame () {
100- while (empty ()) try { Thread .sleep (0 , 100 ); } catch (InterruptedException ignored ) {}
101- System .out .println ("FrameBuffer: Dequeuing buffer " + indexBot () + " on bot step #" + stepBot );
102- ByteBuffer output = dataBuffer .get (indexBot ());
103- ++stepBot ;
104- return output ;
140+ // In practice we don't particularly expect multiple threads to read from this, but support it just to be safe.
141+ readLock .lock ();
142+ try {
143+ // Wait for the buffer to have something to dequeue
144+ lockCapacity .lock ();
145+ try {
146+ while (empty ()) {
147+ conditionEmpty .awaitUninterruptibly ();
148+ }
149+ } finally {
150+ lockCapacity .unlock ();
151+ }
152+
153+ System .out .println ("FrameBuffer: Dequeuing buffer " + indexBot () + " on bot step #" + stepBot );
154+ ByteBuffer output = dataBuffer .get (indexBot ());
155+ ++stepBot ;
156+
157+ // Notify anyone waiting for capacity to enqueue
158+ lockCapacity .lock ();
159+ try {
160+ conditionFull .signalAll ();
161+ return output ;
162+ } finally {
163+ lockCapacity .unlock ();
164+ }
165+ } finally {
166+ readLock .unlock ();
167+ }
105168 }
106169}
0 commit comments