Skip to content

Commit c53a41c

Browse files
pupnewfsterthiakil
andauthored
Improve remainder distribution to try and split the remainder as evenly as possible between the various destinations before falling back to sending to the first one it will fit in (#6617) (#8062)
Co-authored-by: Thiakil <thiakil@users.noreply.github.com>
1 parent 6b9da80 commit c53a41c

File tree

7 files changed

+274
-24
lines changed

7 files changed

+274
-24
lines changed

src/main/java/mekanism/common/lib/distribution/FloatingLongSplitInfo.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,18 @@ public void send(FloatingLong amountNeeded) {
2020
//If we are giving it, then lower the amount we are checking/splitting
2121
boolean recalculate;
2222
if (amountNeeded.isZero()) {
23+
if (!decrementTargets) {
24+
//If we are not decrementing targets, then don't remove that as a valid target, or update how much there is per target
25+
return;
26+
}
2327
recalculate = true;
2428
} else {
2529
amountToSplit = amountToSplit.minusEqual(amountNeeded);
2630
sentSoFar = sentSoFar.plusEqual(amountNeeded);
31+
if (!decrementTargets) {
32+
//If we are not decrementing targets, then don't remove that as a valid target, or update how much there is per target
33+
return;
34+
}
2735
recalculate = !amountNeeded.equals(amountPerTarget);
2836
}
2937
toSplitAmong--;
@@ -50,6 +58,16 @@ public FloatingLong getRemainderAmount() {
5058
return amountPerTarget;
5159
}
5260

61+
@Override
62+
public FloatingLong getUnsent() {
63+
return amountToSplit;
64+
}
65+
66+
@Override
67+
public boolean isZero(FloatingLong value) {
68+
return value.isZero();
69+
}
70+
5371
@Override
5472
public FloatingLong getTotalSent() {
5573
return sentSoFar;

src/main/java/mekanism/common/lib/distribution/IntegerSplitInfo.java

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,36 @@ public class IntegerSplitInfo extends SplitInfo<Integer> {
55
private int amountToSplit;
66
private int amountPerTarget;
77
private int sentSoFar;
8+
private int remainder;
89

910
public IntegerSplitInfo(int amountToSplit, int totalTargets) {
1011
super(totalTargets);
1112
this.amountToSplit = amountToSplit;
1213
amountPerTarget = toSplitAmong == 0 ? 0 : amountToSplit / toSplitAmong;
14+
remainder = toSplitAmong == 0 ? 0 : amountToSplit % toSplitAmong;
1315
}
1416

1517
@Override
1618
public void send(Integer amountNeeded) {
1719
//If we are giving it, then lower the amount we are checking/splitting
1820
amountToSplit -= amountNeeded;
1921
sentSoFar += amountNeeded;
22+
if (!decrementTargets) {
23+
//If we are not decrementing targets, then don't remove that as a valid target, or update how much there is per target
24+
int difference = amountNeeded - amountPerTarget;
25+
if (difference > 0) {
26+
//If we removed more than we have per target, we need to remove the excess from our remainder
27+
remainder -= difference;
28+
}
29+
return;
30+
}
2031
toSplitAmong--;
2132
//Only recalculate it if it is not willing to accept/doesn't want the
2233
// full per side split
2334
if (amountNeeded != amountPerTarget && toSplitAmong != 0) {
2435
int amountPerLast = amountPerTarget;
2536
amountPerTarget = amountToSplit / toSplitAmong;
37+
remainder = amountToSplit % toSplitAmong;
2638
if (!amountPerChanged && amountPerTarget != amountPerLast) {
2739
amountPerChanged = true;
2840
}
@@ -31,15 +43,29 @@ public void send(Integer amountNeeded) {
3143

3244
@Override
3345
public Integer getShareAmount() {
46+
//TODO: Should we make this return a + 1 if there is a remainder, so that we can factor out those cases that can accept exactly amountPerTarget + 1
47+
// while doing our initial loop rather than handling it via getRemainderAmount?
3448
return amountPerTarget;
3549
}
3650

3751
@Override
3852
public Integer getRemainderAmount() {
39-
//Add to the remainder amount the entire remainder so that we try to use it up if we can
40-
// The remainder then if it cannot be fully accepted slowly shrinks across each target we are distributing to
41-
//TODO: Evaluate making a more even distribution of the remainder
42-
return toSplitAmong == 0 ? amountPerTarget : amountPerTarget + (amountToSplit % toSplitAmong);
53+
if (toSplitAmong != 0 && remainder > 0) {
54+
//If we have a remainder, be willing to provide a single unit as the remainder
55+
// so that we split the remainder more evenly across the targets.
56+
return amountPerTarget + 1;
57+
}
58+
return amountPerTarget;
59+
}
60+
61+
@Override
62+
public Integer getUnsent() {
63+
return amountToSplit;
64+
}
65+
66+
@Override
67+
public boolean isZero(Integer value) {
68+
return value == 0;
4369
}
4470

4571
@Override

src/main/java/mekanism/common/lib/distribution/LongSplitInfo.java

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,36 @@ public class LongSplitInfo extends SplitInfo<Long> {
55
private long amountToSplit;
66
private long amountPerTarget;
77
private long sentSoFar;
8+
private long remainder;
89

910
public LongSplitInfo(long amountToSplit, int totalTargets) {
1011
super(totalTargets);
1112
this.amountToSplit = amountToSplit;
1213
amountPerTarget = toSplitAmong == 0 ? 0 : amountToSplit / toSplitAmong;
14+
remainder = toSplitAmong == 0 ? 0 : amountToSplit % toSplitAmong;
1315
}
1416

1517
@Override
1618
public void send(Long amountNeeded) {
1719
//If we are giving it, then lower the amount we are checking/splitting
1820
amountToSplit -= amountNeeded;
1921
sentSoFar += amountNeeded;
22+
if (!decrementTargets) {
23+
//If we are not decrementing targets, then don't remove that as a valid target, or update how much there is per target
24+
long difference = amountNeeded - amountPerTarget;
25+
if (difference > 0) {
26+
//If we removed more than we have per target, we need to remove the excess from our remainder
27+
remainder -= difference;
28+
}
29+
return;
30+
}
2031
toSplitAmong--;
2132
//Only recalculate it if it is not willing to accept/doesn't want the
2233
// full per side split
2334
if (amountNeeded != amountPerTarget && toSplitAmong != 0) {
2435
long amountPerLast = amountPerTarget;
2536
amountPerTarget = amountToSplit / toSplitAmong;
37+
remainder = amountToSplit % toSplitAmong;
2638
if (!amountPerChanged && amountPerTarget != amountPerLast) {
2739
amountPerChanged = true;
2840
}
@@ -36,10 +48,22 @@ public Long getShareAmount() {
3648

3749
@Override
3850
public Long getRemainderAmount() {
39-
//Add to the remainder amount the entire remainder so that we try to use it up if we can
40-
// The remainder then if it cannot be fully accepted slowly shrinks across each target we are distributing to
41-
//TODO: Evaluate making a more even distribution of the remainder
42-
return toSplitAmong == 0 ? amountPerTarget : amountPerTarget + (amountToSplit % toSplitAmong);
51+
if (toSplitAmong != 0 && remainder > 0) {
52+
//If we have a remainder, be willing to provide a single unit as the remainder
53+
// so that we split the remainder more evenly across the targets.
54+
return amountPerTarget + 1;
55+
}
56+
return amountPerTarget;
57+
}
58+
59+
@Override
60+
public Long getUnsent() {
61+
return remainder;
62+
}
63+
64+
@Override
65+
public boolean isZero(Long value) {
66+
return value == 0;
4367
}
4468

4569
@Override

src/main/java/mekanism/common/lib/distribution/SplitInfo.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,63 @@
22

33
public abstract class SplitInfo<TYPE extends Number & Comparable<TYPE>> {
44

5+
/**
6+
* Number of targets to split the contents among.
7+
*/
58
protected int toSplitAmong;
9+
/**
10+
* Represents whether the amount per target distribution has changed. This may happen if a target doesn't need as much as we are willing to offer it in the split.
11+
*/
612
public boolean amountPerChanged = false;
13+
/**
14+
* Determines whether the number of targets to split amount should be decreased.
15+
*
16+
* @implNote This is only set to false briefly when handling accepting contents with remainders to allow them to accept some of the contents without being marked as
17+
* fully accounted for.
18+
*/
19+
protected boolean decrementTargets = true;
720

821
protected SplitInfo(int totalTargets) {
922
this.toSplitAmong = totalTargets;
1023
}
1124

25+
/**
26+
* Marks the given amount as being accounted for and "sent". Decrements {@link #getUnsent() how much we have left to send} and increments
27+
* {@link #getTotalSent() how much we have sent}. If {@link #decrementTargets} is true, this also will reduce the number of targets to split among, and recalculate
28+
* how much we can provide each target.
29+
*
30+
* @param amountNeeded Amount needed by the target and that we are accounting as having been sent to the target.
31+
*/
1232
public abstract void send(TYPE amountNeeded);
1333

34+
/**
35+
* {@return the "share" each target should get when distributing in an even split}
36+
*/
1437
public abstract TYPE getShareAmount();
1538

39+
/**
40+
* Gets the "share" including a potential remainder that targets should get when handling remainders. This is used for actually sending providing the split share to
41+
* any targets that can accept more than we are able to offer in an even split. In general this number will either be equal to {@link #getShareAmount()} or greater
42+
* than it by one while we still have an excess remainder.
43+
*
44+
* @return the "share" plus any potential remainder.
45+
*/
1646
public abstract TYPE getRemainderAmount();
1747

48+
/**
49+
* {@return the amount of contents that has not been sent anywhere yet}
50+
*/
51+
public abstract TYPE getUnsent();
52+
53+
/**
54+
* {@return true if the value is equal to zero}
55+
*
56+
* @param value Value to check
57+
*/
58+
public abstract boolean isZero(TYPE value);
59+
60+
/**
61+
* {@return the total amount of contents that have been sent}
62+
*/
1863
public abstract TYPE getTotalSent();
1964
}

src/main/java/mekanism/common/lib/distribution/Target.java

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,43 @@ public int getHandlerCount() {
6161
*/
6262
public void sendRemainingSplit(SplitInfo<TYPE> splitInfo) {
6363
//If needed is not empty then we default it to the given calculated fair split amount of remaining energy
64-
for (HandlerType<HANDLER, TYPE> recipient : needed) {
65-
acceptAmount(recipient.handler(), splitInfo, splitInfo.getRemainderAmount());
64+
if (!needed.isEmpty() && !splitInfo.isZero(splitInfo.getRemainderAmount())) {
65+
Iterator<HandlerType<HANDLER, TYPE>> iterator = needed.iterator();
66+
while (iterator.hasNext()) {
67+
TYPE remainderAmount = splitInfo.getRemainderAmount();
68+
if (splitInfo.isZero(remainderAmount)) {
69+
//We finished inserting everything we wanted to, we can just exit
70+
return;
71+
}
72+
HandlerType<HANDLER, TYPE> needInfo = iterator.next();
73+
//Accept the remaining amount
74+
TYPE amountNeeded = needInfo.amount();
75+
if (amountNeeded.compareTo(remainderAmount) <= 0) {
76+
acceptAmount(needInfo.handler(), splitInfo, amountNeeded);
77+
//If the amount we needed was the less than or the same as our remaining amount
78+
// we can remove the value as it has now been sent
79+
iterator.remove();
80+
} else {
81+
splitInfo.decrementTargets = false;
82+
acceptAmount(needInfo.handler(), splitInfo, remainderAmount);
83+
splitInfo.decrementTargets = true;
84+
}
85+
}
86+
//TODO: If we remove buffers maybe we should evaluate not caring if we don't actually send the full excess remainder?
87+
// Given ideally we wouldn't attempting to insert the excess remainder to handlers as a second call to the handler on the same tick
88+
if (!splitInfo.isZero(splitInfo.getUnsent())) {
89+
//If we still have some of a remainder after trying to evenly distribute the remainder just send it to the first target willing to accept it
90+
// This might happen if one of the destinations was only able to accept part of the remaining amount, though in general that case will be
91+
// covered by shifting the needed values
92+
for (HandlerType<HANDLER, TYPE> recipient : needed) {
93+
TYPE remaining = splitInfo.getUnsent();
94+
if (splitInfo.isZero(remaining)) {
95+
//We finished, exit
96+
return;
97+
}
98+
acceptAmount(recipient.handler(), splitInfo, remaining);
99+
}
100+
}
66101
}
67102
}
68103

@@ -96,14 +131,27 @@ public void sendRemainingSplit(SplitInfo<TYPE> splitInfo) {
96131
* @param splitInfo Information about current overall split.
97132
*/
98133
public void sendPossible(EXTRA toSend, SplitInfo<TYPE> splitInfo) {
99-
for (HANDLER entry : handlers) {
100-
TYPE amountNeeded = simulate(entry, toSend);
101-
if (amountNeeded.compareTo(splitInfo.getShareAmount()) <= 0) {
102-
//Add the amount, in case something changed from simulation only mark actual sent amount
103-
// in split info
104-
acceptAmount(entry, splitInfo, amountNeeded);
105-
} else {
106-
needed.add(new HandlerType<>(entry, amountNeeded));
134+
if (splitInfo.isZero(splitInfo.getShareAmount())) {
135+
//We are all remainder, just calculate how much each can accept
136+
for (HANDLER entry : handlers) {
137+
TYPE amountNeeded = simulate(entry, toSend);
138+
if (!splitInfo.isZero(amountNeeded)) {
139+
needed.add(new HandlerType<>(entry, amountNeeded));
140+
}
141+
}
142+
} else {
143+
for (HANDLER entry : handlers) {
144+
TYPE amountNeeded = simulate(entry, toSend);
145+
if (amountNeeded.compareTo(splitInfo.getShareAmount()) <= 0) {
146+
//Add the amount, in case something changed from simulation only mark actual sent amount
147+
// in split info
148+
if (!splitInfo.isZero(amountNeeded)) {
149+
//Note: We can skip actually running it if it doesn't need anything
150+
acceptAmount(entry, splitInfo, amountNeeded);
151+
}
152+
} else {
153+
needed.add(new HandlerType<>(entry, amountNeeded));
154+
}
107155
}
108156
}
109157
}
@@ -114,6 +162,9 @@ public void sendPossible(EXTRA toSend, SplitInfo<TYPE> splitInfo) {
114162
* @param splitInfo The new split to (re)check.
115163
*/
116164
public void shiftNeeded(SplitInfo<TYPE> splitInfo) {
165+
if (splitInfo.isZero(splitInfo.getShareAmount())) {
166+
return;
167+
}
117168
Iterator<HandlerType<HANDLER, TYPE>> iterator = needed.iterator();
118169
//Use an iterator rather than a copy of the keySet of the needed subMap
119170
// This allows for us to remove it once we find it without having to

0 commit comments

Comments
 (0)