Skip to content

Commit 4f4d41b

Browse files
committed
*Really* fix the OreDict exporter filter parsing now.
Pattern Syntax: [!][@~]<pattern> Filter syntax: <pattern> [&|] <pattern2> [&|] ... Parentheses NOT supported. @ prefix for resource namespace matching (i.e. @minecraft @AppliedEnergistics) ~ prefix for resource path matching ! for negation (use before @ and ~ if applicable) * can be used in any position, although usage of * that isn't beginning or end may result in performance hits due to usage of regex matching. Usage with pure oredict matches (no @ and ~) will result in querying the oredict once and building a whitelist that is saved and the filter is only evaluated once. Using @ and ~ will result in the filter being evaluated for every item in AE until it finds a match. Examples tested: @minecraft & ore* (All vanilla ores) @mine* | @thermal* (All blocks from mods with namespaces that starts with mine or thermal) oreGold | oreIron (Gold and iron ore only)
1 parent 0c777c2 commit 4f4d41b

File tree

1 file changed

+158
-26
lines changed

1 file changed

+158
-26
lines changed

src/main/scala/extracells/part/PartOreDictExporter.java

Lines changed: 158 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import appeng.api.parts.PartItemStack;
1616
import appeng.api.storage.IMEMonitor;
1717
import appeng.api.storage.data.IAEItemStack;
18+
import appeng.api.storage.data.IItemList;
1819
import appeng.api.util.AECableType;
1920
import appeng.api.util.DimensionalCoord;
2021
import appeng.util.item.AEItemStack;
@@ -43,13 +44,24 @@
4344
import org.apache.commons.lang3.StringUtils;
4445

4546
import java.util.ArrayList;
47+
import java.util.Arrays;
4648
import java.util.List;
49+
import java.util.Optional;
50+
import java.util.function.Predicate;
4751
import java.util.regex.Pattern;
52+
import java.util.stream.IntStream;
4853

4954
public class PartOreDictExporter extends PartECBase implements IGridTickable {
5055

5156
private String filter = "";
52-
private ItemStack[] filteredItems = new ItemStack[0];
57+
/**
58+
* Used when there are ModID(@) and path (~) matchers. Tick-time matching.
59+
*/
60+
private Predicate<ItemStack> filterPredicate = null;
61+
/**
62+
* White list of itemstacks to extract. OreDict only mode.
63+
*/
64+
private ItemStack[] oreDictFilteredItems;
5365

5466
@Override
5567
public float getCableConnectionLength(AECableType aeCableType) {
@@ -62,27 +74,127 @@ public String getFilter() {
6274

6375
public void setFilter(String filter) {
6476
this.filter = filter;
65-
updateFilteredItems();
77+
updateFilter();
6678
saveData();
6779
}
6880

69-
private void updateFilteredItems() {
81+
/**
82+
* Call when the filter string has changed to parse and recompile the filter.
83+
*/
84+
private void updateFilter() {
7085
if (!this.filter.trim().isEmpty()) {
71-
String filterRegexFragment = this.filter.replace("*", ".*").replace("+", ".+");
72-
String regexPattern = "^" + filterRegexFragment + "$";
73-
Pattern pattern = Pattern.compile(regexPattern, Pattern.CASE_INSENSITIVE & Pattern.CANON_EQ);
7486
ArrayList<String> matchingNames = new ArrayList<>();
75-
for (String oreName : OreDictionary.getOreNames()) {
76-
if (pattern.matcher(oreName).matches())
77-
matchingNames.add(oreName);
87+
Predicate<ItemStack> matcher = null;
88+
89+
String[] filters = this.filter.split("[&|]");
90+
String lastFilter = null;
91+
92+
for (String filter : filters) {
93+
filter = filter.trim();
94+
boolean negated = filter.startsWith("!");
95+
if (negated)
96+
filter = filter.substring(1);
97+
98+
Predicate<ItemStack> test = filterToItemStackPredicate(filter);
99+
100+
if (negated)
101+
test = test.negate();
102+
103+
if (matcher == null) {
104+
matcher = test;
105+
lastFilter = filter;
106+
} else {
107+
int endLast = this.filter.indexOf(lastFilter) + lastFilter.length();
108+
int startThis = this.filter.indexOf(filter);
109+
boolean or = this.filter.substring(endLast, startThis).contains("|");
110+
111+
if (or) {
112+
matcher = matcher.or(test);
113+
} else {
114+
matcher = matcher.and(test);
115+
}
116+
}
117+
}
118+
119+
if (matcher == null) {
120+
filterPredicate = null;
121+
oreDictFilteredItems = new ItemStack[0];
122+
return;
123+
}
124+
125+
//Mod name and path evaluation can only be done during tick, can't precompile whitelist for this.
126+
if (!this.filter.contains("@") && !this.filter.contains("~")) {
127+
//Precompiled whitelist of oredict itemstacks.
128+
this.oreDictFilteredItems = Arrays.stream(OreDictionary.getOreNames())
129+
.flatMap(name -> OreDictionary.getOres(name).stream())
130+
.filter(matcher)
131+
.toArray(ItemStack[]::new);
132+
this.filterPredicate = null;
133+
} else {
134+
//Runtime evaluation of filter.
135+
filterPredicate = matcher;
136+
this.oreDictFilteredItems = new ItemStack[0];
78137
}
138+
} else {
139+
this.filterPredicate = null;
140+
this.oreDictFilteredItems = new ItemStack[0];
141+
}
142+
}
143+
144+
/**
145+
* Given a filter string, returns a predicate that matches a given ItemStack
146+
* @param filter Filter string.
147+
* @return Predicate for filter string.
148+
*/
149+
private Predicate<ItemStack> filterToItemStackPredicate(String filter) {
150+
if (filter.startsWith("@")) {
151+
final Predicate<String> test = filterToPredicate(filter.substring(1));
152+
return (is) ->
153+
Optional.ofNullable(is.getItem().getRegistryName())
154+
.map(ResourceLocation::getNamespace)
155+
.map(test::test)
156+
.orElse(false);
157+
} else if (filter.startsWith("~")) {
158+
final Predicate<String> test = filterToPredicate(filter.substring(1));
159+
return (is) ->
160+
Optional.ofNullable(is.getItem().getRegistryName())
161+
.map(ResourceLocation::getPath)
162+
.map(test::test)
163+
.orElse(false);
164+
} else {
165+
final Predicate<String> test = filterToPredicate(filter);
166+
return (is) ->
167+
IntStream.of(OreDictionary.getOreIDs(is))
168+
.mapToObj(OreDictionary::getOreName)
169+
.anyMatch(test);
170+
}
171+
}
79172

80-
this.filteredItems = matchingNames.stream()
81-
.map(OreDictionary::getOres)
82-
.flatMap(List::stream)
83-
.toArray(ItemStack[]::new);
173+
/**
174+
* Given a filter string, returns a Predicate that matches a string.
175+
* @param filter Filter string
176+
* @return Predicate for filter string.
177+
*/
178+
private Predicate<String> filterToPredicate(String filter) {
179+
int numStars = StringUtils.countMatches(filter, '*');
180+
if (numStars == filter.length()) {
181+
return (str) -> true;
182+
} else if (filter.length() > 2 && filter.startsWith("*") && filter.endsWith("*") && numStars == 2) {
183+
final String pattern = filter.substring(1, filter.length() - 1);
184+
return (str) -> str.contains(pattern);
185+
} else if (filter.length() >= 2 && filter.startsWith("*") && numStars == 1) {
186+
final String pattern = filter.substring(1);
187+
return (str) -> str.endsWith(pattern);
188+
} else if (filter.length() >= 2 && filter.endsWith("*") && numStars == 1) {
189+
final String pattern = filter.substring(0, filter.length() - 1);
190+
return (str) -> str.startsWith(pattern);
191+
} else if (numStars == 0) {
192+
return (str) -> str.equals(filter);
84193
} else {
85-
this.filteredItems = new ItemStack[0];
194+
String filterRegexFragment = filter.replace("*", ".*");
195+
String regexPattern = "^" + filterRegexFragment + "$";
196+
final Pattern pattern = Pattern.compile(regexPattern);
197+
return pattern.asPredicate();
86198
}
87199
}
88200

@@ -187,22 +299,42 @@ public boolean doWork(int rate, int ticksSinceLastCall) {
187299
IMEMonitor<IAEItemStack> inv = storage.getInventory(StorageChannels.ITEM());
188300
MachineSource src = new MachineSource(this);
189301

190-
boolean exportedStuff = false;
302+
if (this.filterPredicate != null) {
303+
//Tick-time filter evaluation.
304+
IItemList<IAEItemStack> items = inv.getStorageList();
305+
for (IAEItemStack stack : items) {
306+
if (this.filterPredicate.test(stack.createItemStack())) {
307+
IAEItemStack toExtract = stack.copy();
308+
toExtract.setStackSize(amount);
191309

192-
for (ItemStack is : this.filteredItems) {
193-
ItemStack toExtract = is.copy();
194-
toExtract.setCount(amount);
310+
IAEItemStack extracted = inv.extractItems(toExtract, Actionable.SIMULATE, src);
311+
if (extracted != null) {
312+
IAEItemStack exported = exportStack(extracted.copy());
313+
if (exported != null) {
314+
inv.extractItems(exported, Actionable.MODULATE, src);
315+
return true;
316+
}
317+
}
318+
}
319+
}
320+
return false;
321+
} else {
322+
//Precompiled oredict whitelist
323+
for (ItemStack is : this.oreDictFilteredItems) {
324+
ItemStack toExtract = is.copy();
325+
toExtract.setCount(amount);
195326

196-
IAEItemStack extracted = inv.extractItems(AEItemStack.fromItemStack(toExtract), Actionable.SIMULATE, src);
197-
if (extracted != null) {
198-
IAEItemStack exported = exportStack(extracted.copy());
199-
if (exported != null) {
200-
inv.extractItems(exported, Actionable.MODULATE, src);
201-
return true;
327+
IAEItemStack extracted = inv.extractItems(AEItemStack.fromItemStack(toExtract), Actionable.SIMULATE, src);
328+
if (extracted != null) {
329+
IAEItemStack exported = exportStack(extracted.copy());
330+
if (exported != null) {
331+
inv.extractItems(exported, Actionable.MODULATE, src);
332+
return true;
333+
}
202334
}
203335
}
336+
return false;
204337
}
205-
return false;
206338
}
207339

208340
public IAEItemStack exportStack(IAEItemStack stack0) {
@@ -387,7 +519,7 @@ public void readFromNBT(NBTTagCompound data) {
387519
super.readFromNBT(data);
388520
if (data.hasKey("filter")) {
389521
this.filter = data.getString("filter");
390-
updateFilteredItems();
522+
updateFilter();
391523
}
392524
}
393525

0 commit comments

Comments
 (0)