Skip to content

Commit ff5590c

Browse files
authored
Fixes some issues with turn cost times. (graphhopper#1586)
* Fixes turn cost time conversion from seconds to milliseconds in Weighting#calcMillis. * Fixes turn cost time evaluation at meeting node for bidir algos.
1 parent 197ecfd commit ff5590c

File tree

8 files changed

+213
-99
lines changed

8 files changed

+213
-99
lines changed

core/src/main/java/com/graphhopper/routing/PathBidirRef.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public Path extract() {
8383
setFromNode(currEdge.adjNode);
8484
reverseOrder();
8585
currEdge = edgeTo;
86-
int prevEdge = nextEdgeValid ? sptEntry.edge : EdgeIterator.NO_EDGE;
86+
int prevEdge = EdgeIterator.Edge.isValid(sptEntry.edge) ? sptEntry.edge : EdgeIterator.NO_EDGE;
8787
int tmpEdge = currEdge.edge;
8888
while (EdgeIterator.Edge.isValid(tmpEdge)) {
8989
currEdge = currEdge.parent;

core/src/main/java/com/graphhopper/routing/util/TurnCostEncoder.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
package com.graphhopper.routing.util;
1919

2020
/**
21-
* Encodes and decodes a turn restriction and turn costs within a integer flag
21+
* Encodes and decodes a turn restriction or turn costs within an integer flag
2222
*
2323
* @author Karl Hübner
2424
*/
@@ -35,16 +35,15 @@ public interface TurnCostEncoder {
3535
double getTurnCost(long flags);
3636

3737
/**
38-
* @param restricted true if restricted turn, equivalent to specifying of costs
39-
* Double.POSITIVE_INFINITY
38+
* @param restricted true if restricted turn, equivalent to specifying costs = Double.POSITIVE_INFINITY
4039
* @param costs the turn costs, specify 0 or Double.POSITIVE_INFINITY if restricted == true.
4140
* Only used if restricted == false.
4241
* @return the encoded flags
4342
*/
4443
long getTurnFlags(boolean restricted, double costs);
4544

4645
/**
47-
* No turn costs will be enabled by this encoder, should be used for pedestrians
46+
* No turn costs will be enabled by this encoder, should be used for e.g. pedestrians
4847
*/
4948
class NoTurnCostsEncoder implements TurnCostEncoder {
5049

core/src/main/java/com/graphhopper/routing/weighting/TurnWeighting.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,14 @@ public long calcMillis(EdgeIteratorState edgeState, boolean reverse, int prevOrN
8989
if (prevOrNextEdgeId == EdgeIterator.NO_EDGE)
9090
return millis;
9191

92-
// TODO for now assume turn costs are returned in milliseconds?
9392
// should we also separate weighting vs. time for turn? E.g. a fast but dangerous turn - is this common?
94-
long turnCostsInMillis;
93+
long turnCostsInSeconds;
9594
if (reverse)
96-
turnCostsInMillis = (long) calcTurnWeight(edgeState.getEdge(), edgeState.getBaseNode(), prevOrNextEdgeId);
95+
turnCostsInSeconds = (long) calcTurnWeight(edgeState.getEdge(), edgeState.getBaseNode(), prevOrNextEdgeId);
9796
else
98-
turnCostsInMillis = (long) calcTurnWeight(prevOrNextEdgeId, edgeState.getBaseNode(), edgeState.getEdge());
97+
turnCostsInSeconds = (long) calcTurnWeight(prevOrNextEdgeId, edgeState.getBaseNode(), edgeState.getEdge());
9998

100-
return millis + turnCostsInMillis;
99+
return millis + 1000 * turnCostsInSeconds;
101100
}
102101

103102
/**

core/src/main/java/com/graphhopper/storage/GraphBuilder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public CHGraph chGraphCreate(Weighting singleCHWeighting) {
9595
/**
9696
* Default graph is a {@link GraphHopperStorage} with an in memory directory and disabled storing on flush.
9797
* Afterwards you'll need to call {@link GraphHopperStorage#create} to have a usable object. Better use
98-
* {@link GraphHopperStorage#create} directly.
98+
* {@link #create} directly.
9999
*/
100100
public GraphHopperStorage build() {
101101
Directory dir = mmap ?

core/src/main/java/com/graphhopper/util/PathMerger.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@
2929
import java.util.List;
3030

3131
/**
32-
* This class merges multiple {@link Path} objects into one continues object that
32+
* This class merges multiple {@link Path} objects into one continuous object that
3333
* can be used in the {@link PathWrapper}. There will be a Path between every waypoint.
3434
* So for two waypoints there will be only one Path object. For three waypoints there will be
3535
* two Path objects.
3636
* <p>
37-
* The instructions are generated per Path object and are merged into one continues InstructionList.
37+
* The instructions are generated per Path object and are merged into one continuous InstructionList.
3838
* The PointList per Path object are merged and optionally simplified.
3939
*
4040
* @author Peter Karich

core/src/test/java/com/graphhopper/routing/EdgeBasedRoutingAlgorithmTest.java

Lines changed: 112 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@
1919

2020
import com.carrotsearch.hppc.IntArrayList;
2121
import com.carrotsearch.hppc.cursors.IntCursor;
22-
import com.graphhopper.routing.util.*;
22+
import com.graphhopper.routing.util.CarFlagEncoder;
23+
import com.graphhopper.routing.util.EncodingManager;
24+
import com.graphhopper.routing.util.FlagEncoder;
25+
import com.graphhopper.routing.util.TraversalMode;
2326
import com.graphhopper.routing.weighting.FastestWeighting;
2427
import com.graphhopper.routing.weighting.TurnWeighting;
2528
import com.graphhopper.routing.weighting.Weighting;
@@ -28,6 +31,7 @@
2831
import com.graphhopper.storage.GraphHopperStorage;
2932
import com.graphhopper.storage.TurnCostExtension;
3033
import com.graphhopper.util.EdgeIteratorState;
34+
import org.junit.Assume;
3135
import org.junit.Test;
3236
import org.junit.runner.RunWith;
3337
import org.junit.runners.Parameterized;
@@ -55,6 +59,8 @@ public EdgeBasedRoutingAlgorithmTest(String algo) {
5559
@Parameters(name = "{0}")
5660
public static Collection<Object[]> configs() {
5761
return Arrays.asList(new Object[][]{
62+
// todo: make this test run also for edge-based CH or otherwise make sure time calculation is tested also for edge-based CH (at the moment it will fail!)
63+
// todo: make this test run also for ALT or otherwise make sure time calculation is tested also for ALT (at the moment it will fail?!)
5864
{DIJKSTRA},
5965
{DIJKSTRA_BI},
6066
{ASTAR},
@@ -81,7 +87,7 @@ public static void initGraph(Graph g) {
8187
g.edge(6, 7, 1, true);
8288
}
8389

84-
EncodingManager createEncodingManager(boolean restrictedOnly) {
90+
private EncodingManager createEncodingManager(boolean restrictedOnly) {
8591
if (restrictedOnly)
8692
carEncoder = new CarFlagEncoder(5, 5, 1);
8793
else
@@ -95,38 +101,36 @@ public RoutingAlgorithm createAlgo(Graph g, AlgorithmOptions opts) {
95101
return new RoutingAlgorithmFactorySimple().createAlgo(g, opts);
96102
}
97103

98-
protected GraphHopperStorage createStorage(EncodingManager em) {
104+
private GraphHopperStorage createStorage(EncodingManager em) {
99105
return new GraphBuilder(em).create();
100106
}
101107

102-
private void initTurnRestrictions(Graph g, TurnCostExtension tcs, TurnCostEncoder tEncoder) {
103-
long tflags = tEncoder.getTurnFlags(true, 0);
104-
108+
private void initTurnRestrictions(Graph g, TurnCostExtension tcs) {
105109
// only forward from 2-3 to 3-4 => limit 2,3->3,6 and 2,3->3,1
106-
tcs.addTurnInfo(getEdge(g, 2, 3).getEdge(), 3, getEdge(g, 3, 6).getEdge(), tflags);
107-
tcs.addTurnInfo(getEdge(g, 2, 3).getEdge(), 3, getEdge(g, 3, 1).getEdge(), tflags);
110+
addTurnRestriction(g, tcs, 2, 3, 6);
111+
addTurnRestriction(g, tcs, 2, 3, 1);
108112

109113
// only right from 5-2 to 2-3 => limit 5,2->2,0
110-
tcs.addTurnInfo(getEdge(g, 5, 2).getEdge(), 2, getEdge(g, 2, 0).getEdge(), tflags);
114+
addTurnRestriction(g, tcs, 5, 2, 0);
111115

112116
// only right from 7-6 to 6-3 => limit 7,6->6,5
113-
tcs.addTurnInfo(getEdge(g, 7, 6).getEdge(), 6, getEdge(g, 6, 5).getEdge(), tflags);
117+
addTurnRestriction(g, tcs, 7, 6, 5);
114118

115119
// no 5-6 to 6-3
116-
tcs.addTurnInfo(getEdge(g, 5, 6).getEdge(), 6, getEdge(g, 6, 3).getEdge(), tflags);
120+
addTurnRestriction(g, tcs, 5, 6, 3);
117121
// no 4-3 to 3-1
118-
tcs.addTurnInfo(getEdge(g, 4, 3).getEdge(), 3, getEdge(g, 3, 1).getEdge(), tflags);
122+
addTurnRestriction(g, tcs, 4, 3, 1);
119123
// no 4-3 to 3-2
120-
tcs.addTurnInfo(getEdge(g, 4, 3).getEdge(), 3, getEdge(g, 3, 2).getEdge(), tflags);
124+
addTurnRestriction(g, tcs, 4, 3, 2);
121125

122126
// no u-turn at 6-7
123-
tcs.addTurnInfo(getEdge(g, 6, 7).getEdge(), 7, getEdge(g, 7, 6).getEdge(), tflags);
127+
addTurnRestriction(g, tcs, 6, 7, 6);
124128

125129
// no u-turn at 3-6
126-
tcs.addTurnInfo(getEdge(g, 3, 6).getEdge(), 6, getEdge(g, 6, 3).getEdge(), tflags);
130+
addTurnRestriction(g, tcs, 3, 6, 3);
127131
}
128132

129-
Weighting createWeighting(FlagEncoder encoder, TurnCostExtension tcs, double uTurnCosts) {
133+
private Weighting createWeighting(FlagEncoder encoder, TurnCostExtension tcs, double uTurnCosts) {
130134
return new TurnWeighting(new FastestWeighting(encoder), tcs).setDefaultUTurnCost(uTurnCosts);
131135
}
132136

@@ -135,52 +139,93 @@ public void testBasicTurnRestriction() {
135139
GraphHopperStorage g = createStorage(createEncodingManager(true));
136140
initGraph(g);
137141
TurnCostExtension tcs = (TurnCostExtension) g.getExtension();
138-
initTurnRestrictions(g, tcs, carEncoder);
142+
initTurnRestrictions(g, tcs);
139143
Path p = createAlgo(g, AlgorithmOptions.start().
140144
weighting(createWeighting(carEncoder, tcs, 40)).
141145
traversalMode(TraversalMode.EDGE_BASED_2DIR).build()).
142146
calcPath(5, 1);
143-
assertEquals(IntArrayList.from(new int[]{5, 2, 3, 4, 7, 6, 3, 1}), p.calcNodes());
147+
assertEquals(IntArrayList.from(5, 2, 3, 4, 7, 6, 3, 1), p.calcNodes());
144148

145149
// test 7-6-5 and reverse
146150
p = createAlgo(g, AlgorithmOptions.start().
147151
weighting(createWeighting(carEncoder, tcs, 40)).
148152
traversalMode(TraversalMode.EDGE_BASED_1DIR).build()).
149153
calcPath(5, 7);
150-
assertEquals(IntArrayList.from(new int[]{5, 6, 7}), p.calcNodes());
154+
assertEquals(IntArrayList.from(5, 6, 7), p.calcNodes());
151155

152156
p = createAlgo(g, AlgorithmOptions.start().
153157
weighting(createWeighting(carEncoder, tcs, 40)).
154158
traversalMode(TraversalMode.EDGE_BASED_1DIR).build()).
155159
calcPath(7, 5);
156-
assertEquals(IntArrayList.from(new int[]{7, 6, 3, 2, 5}), p.calcNodes());
160+
assertEquals(IntArrayList.from(7, 6, 3, 2, 5), p.calcNodes());
161+
}
162+
163+
@Test
164+
public void testTurnCosts_timeCalculation() {
165+
// 0 - 1 - 2 - 3 - 4
166+
GraphHopperStorage g = createStorage(createEncodingManager(false));
167+
TurnCostExtension tcs = (TurnCostExtension) g.getExtension();
168+
final int distance = 100;
169+
final int turnCosts = 2;
170+
g.edge(0, 1, distance, true);
171+
g.edge(1, 2, distance, true);
172+
g.edge(2, 3, distance, true);
173+
g.edge(3, 4, distance, true);
174+
addTurnCost(g, tcs, turnCosts, 1, 2, 3);
175+
176+
AlgorithmOptions opts = AlgorithmOptions.start()
177+
.weighting(createWeighting(carEncoder, tcs, 40))
178+
.traversalMode(TraversalMode.EDGE_BASED_2DIR)
179+
.build();
180+
181+
{
182+
// simple case where turn cost is encountered during forward search
183+
Path p14 = createAlgo(g, opts).calcPath(1, 4);
184+
assertDistTimeWeight(p14, 3, distance, 6, turnCosts);
185+
assertEquals(20, p14.getWeight(), 1.e-6);
186+
assertEquals(20000, p14.getTime());
187+
}
188+
189+
{
190+
// this test is more involved for bidir algos: the turn costs have to be taken into account also at the
191+
// node where fwd and bwd searches meet
192+
Path p04 = createAlgo(g, opts).calcPath(0, 4);
193+
assertDistTimeWeight(p04, 4, distance, 6, turnCosts);
194+
assertEquals(26, p04.getWeight(), 1.e-6);
195+
assertEquals(26000, p04.getTime());
196+
}
197+
}
198+
199+
private void assertDistTimeWeight(Path path, int numEdges, double distPerEdge, double weightPerEdge, int turnCost) {
200+
assertEquals("wrong distance", numEdges * distPerEdge, path.getDistance(), 1.e-6);
201+
assertEquals("wrong weight", numEdges * weightPerEdge + turnCost, path.getWeight(), 1.e-6);
202+
assertEquals("wrong time", 1000 * (numEdges * weightPerEdge + turnCost), path.getTime(), 1.e-6);
157203
}
158204

159205

160-
private void blockNode3(Graph g, TurnCostExtension tcs, TurnCostEncoder tEncoder) {
206+
private void blockNode3(Graph g, TurnCostExtension tcs) {
161207
// Totally block this node (all 9 turn relations)
162-
final long BLOCK = tEncoder.getTurnFlags(true, 0);
163-
tcs.addTurnInfo(getEdge(g, 2, 3).getEdge(), 3, getEdge(g, 3, 1).getEdge(), BLOCK);
164-
tcs.addTurnInfo(getEdge(g, 2, 3).getEdge(), 3, getEdge(g, 3, 4).getEdge(), BLOCK);
165-
tcs.addTurnInfo(getEdge(g, 4, 3).getEdge(), 3, getEdge(g, 3, 1).getEdge(), BLOCK);
166-
tcs.addTurnInfo(getEdge(g, 4, 3).getEdge(), 3, getEdge(g, 3, 2).getEdge(), BLOCK);
167-
tcs.addTurnInfo(getEdge(g, 6, 3).getEdge(), 3, getEdge(g, 3, 1).getEdge(), BLOCK);
168-
tcs.addTurnInfo(getEdge(g, 6, 3).getEdge(), 3, getEdge(g, 3, 4).getEdge(), BLOCK);
169-
tcs.addTurnInfo(getEdge(g, 1, 3).getEdge(), 3, getEdge(g, 3, 6).getEdge(), BLOCK);
170-
tcs.addTurnInfo(getEdge(g, 1, 3).getEdge(), 3, getEdge(g, 3, 2).getEdge(), BLOCK);
171-
tcs.addTurnInfo(getEdge(g, 1, 3).getEdge(), 3, getEdge(g, 3, 4).getEdge(), BLOCK);
208+
addTurnRestriction(g, tcs, 2, 3, 1);
209+
addTurnRestriction(g, tcs, 2, 3, 4);
210+
addTurnRestriction(g, tcs, 4, 3, 1);
211+
addTurnRestriction(g, tcs, 4, 3, 2);
212+
addTurnRestriction(g, tcs, 6, 3, 1);
213+
addTurnRestriction(g, tcs, 6, 3, 4);
214+
addTurnRestriction(g, tcs, 1, 3, 6);
215+
addTurnRestriction(g, tcs, 1, 3, 2);
216+
addTurnRestriction(g, tcs, 1, 3, 4);
172217
}
173218

174219
@Test
175220
public void testBlockANode() {
176221
GraphHopperStorage g = createStorage(createEncodingManager(true));
177222
initGraph(g);
178223
TurnCostExtension tcs = (TurnCostExtension) g.getExtension();
179-
blockNode3(g, tcs, carEncoder);
180-
for (int i=0; i<=7; i++) {
181-
if (i==3) continue;
182-
for (int j=0; j<=7; j++) {
183-
if (j==3) continue;
224+
blockNode3(g, tcs);
225+
for (int i = 0; i <= 7; i++) {
226+
if (i == 3) continue;
227+
for (int j = 0; j <= 7; j++) {
228+
if (j == 3) continue;
184229
Path p = createAlgo(g, AlgorithmOptions.start().
185230
weighting(createWeighting(carEncoder, tcs, 40)).
186231
traversalMode(TraversalMode.EDGE_BASED_2DIR).build()).
@@ -199,31 +244,29 @@ public void testUTurns() {
199244
initGraph(g);
200245
TurnCostExtension tcs = (TurnCostExtension) g.getExtension();
201246

202-
long tflags = carEncoder.getTurnFlags(true, 0);
203-
204247
// force u-turn via lowering the cost for it
205248
EdgeIteratorState e3_6 = getEdge(g, 3, 6);
206249
e3_6.setDistance(0.1);
207250
getEdge(g, 3, 2).setDistance(864);
208251
getEdge(g, 1, 0).setDistance(864);
209252

210-
tcs.addTurnInfo(getEdge(g, 7, 6).getEdge(), 6, getEdge(g, 6, 5).getEdge(), tflags);
211-
tcs.addTurnInfo(getEdge(g, 4, 3).getEdge(), 3, e3_6.getEdge(), tflags);
253+
addTurnRestriction(g, tcs, 7, 6, 5);
254+
addTurnRestriction(g, tcs, 4, 3, 6);
212255
AlgorithmOptions opts = AlgorithmOptions.start().
213256
weighting(createWeighting(carEncoder, tcs, 50)).
214257
traversalMode(TraversalMode.EDGE_BASED_2DIR_UTURN).build();
215258
Path p = createAlgo(g, opts).calcPath(7, 5);
216259

217-
assertEquals(IntArrayList.from(new int[]{7, 6, 3, 6, 5}), p.calcNodes());
260+
assertEquals(IntArrayList.from(7, 6, 3, 6, 5), p.calcNodes());
218261

219262
// no u-turn for 6-3
220263
opts = AlgorithmOptions.start().
221264
weighting(createWeighting(carEncoder, tcs, 100)).
222265
traversalMode(TraversalMode.EDGE_BASED_2DIR_UTURN).build();
223-
tcs.addTurnInfo(getEdge(g, 6, 3).getEdge(), 3, getEdge(g, 3, 6).getEdge(), tflags);
266+
addTurnRestriction(g, tcs, 6, 3, 6);
224267
p = createAlgo(g, opts).calcPath(7, 5);
225268

226-
assertEquals(IntArrayList.from(new int[]{7, 6, 3, 2, 5}), p.calcNodes());
269+
assertEquals(IntArrayList.from(7, 6, 3, 2, 5), p.calcNodes());
227270
}
228271

229272
@Test
@@ -237,19 +280,17 @@ public void testBasicTurnCosts() {
237280
calcPath(5, 1);
238281

239282
// no restriction and costs
240-
EdgeIteratorState e3_6 = getEdge(g, 5, 6);
241-
e3_6.setDistance(2);
242-
assertEquals(IntArrayList.from(new int[]{5, 2, 3, 1}), p.calcNodes());
283+
assertEquals(IntArrayList.from(5, 2, 3, 1), p.calcNodes());
243284

244285
// now introduce some turn costs
245-
long tflags = carEncoder.getTurnFlags(false, 2);
246-
tcs.addTurnInfo(getEdge(g, 5, 2).getEdge(), 2, getEdge(g, 2, 3).getEdge(), tflags);
286+
getEdge(g, 5, 6).setDistance(2);
287+
addTurnCost(g, tcs, 2, 5, 2, 3);
247288

248289
p = createAlgo(g, AlgorithmOptions.start().
249290
weighting(createWeighting(carEncoder, tcs, 40)).
250291
traversalMode(TraversalMode.EDGE_BASED_1DIR).build()).
251292
calcPath(5, 1);
252-
assertEquals(IntArrayList.from(new int[]{5, 6, 3, 1}), p.calcNodes());
293+
assertEquals(IntArrayList.from(5, 6, 3, 1), p.calcNodes());
253294
}
254295

255296
@Test
@@ -258,13 +299,10 @@ public void testTurnCostsBug_991() {
258299
initGraph(g);
259300
TurnCostExtension tcs = (TurnCostExtension) g.getExtension();
260301

261-
long tflags = carEncoder.getTurnFlags(false, 2);
262-
tcs.addTurnInfo(getEdge(g, 5, 2).getEdge(), 2, getEdge(g, 2, 3).getEdge(), tflags);
263-
tcs.addTurnInfo(getEdge(g, 2, 0).getEdge(), 0, getEdge(g, 0, 1).getEdge(), tflags);
264-
tcs.addTurnInfo(getEdge(g, 5, 6).getEdge(), 6, getEdge(g, 6, 3).getEdge(), tflags);
265-
266-
tflags = carEncoder.getTurnFlags(false, 1);
267-
tcs.addTurnInfo(getEdge(g, 6, 7).getEdge(), 7, getEdge(g, 7, 4).getEdge(), tflags);
302+
addTurnCost(g, tcs, 2, 5, 2, 3);
303+
addTurnCost(g, tcs, 2, 2, 0, 1);
304+
addTurnCost(g, tcs, 2, 5, 6, 3);
305+
addTurnCost(g, tcs, 1, 6, 7, 4);
268306

269307
Path p = createAlgo(g, AlgorithmOptions.start().
270308
weighting(new TurnWeighting(new FastestWeighting(carEncoder), tcs) {
@@ -279,7 +317,23 @@ public double calcTurnWeight(int edgeFrom, int nodeVia, int edgeTo) {
279317
}.setDefaultUTurnCost(40)).
280318
traversalMode(TraversalMode.EDGE_BASED_2DIR).build()).
281319
calcPath(5, 1);
282-
assertEquals(IntArrayList.from(new int[]{5, 6, 7, 4, 3, 1}), p.calcNodes());
283-
assertEquals(301, p.getTime(), .1);
320+
assertEquals(IntArrayList.from(5, 6, 7, 4, 3, 1), p.calcNodes());
321+
assertEquals(5 * 0.06 + 1, p.getWeight(), 1.e-6);
322+
assertEquals(1300, p.getTime(), .1);
323+
}
324+
325+
private void addTurnRestriction(Graph g, TurnCostExtension tcs, int from, int via, int to) {
326+
long turnFlags = carEncoder.getTurnFlags(true, 0);
327+
addTurnFlags(g, tcs, from, via, to, turnFlags);
284328
}
329+
330+
private void addTurnCost(Graph g, TurnCostExtension tcs, int costs, int from, int via, int to) {
331+
long turnFlags = carEncoder.getTurnFlags(false, costs);
332+
addTurnFlags(g, tcs, from, via, to, turnFlags);
333+
}
334+
335+
private void addTurnFlags(Graph g, TurnCostExtension tcs, int from, int via, int to, long turnFlags) {
336+
tcs.addTurnInfo(getEdge(g, from, via).getEdge(), via, getEdge(g, via, to).getEdge(), turnFlags);
337+
}
338+
285339
}

0 commit comments

Comments
 (0)