Skip to content

[1.21.1] Allow the use of linear-time, threadsafe regex-based matching as an alternative to globs #8281

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: mc1.21.1/dev
Choose a base branch
from
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,13 @@ repositories {
includeGroup "mysticdrew"
}
}
maven { url = "https://oss.sonatype.org" } // RE2J
}

dependencies {
jarJar(implementation("com.google.re2j:re2j:1.8"))
additionalRuntimeClasspath "com.google.re2j:re2j:1.8"

jarJar(implementation("com.tterrag.registrate:Registrate:${registrate_version}"))
jarJar("net.createmod.ponder:Ponder-NeoForge-${minecraft_version}:${ponder_version}")

Expand Down
13 changes: 12 additions & 1 deletion src/generated/resources/assets/create/lang/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -1259,6 +1259,9 @@
"create.gui.filter.ignore_data.description": "Items match regardless of their attributes.",
"create.gui.filter.respect_data": "Respect Data",
"create.gui.filter.respect_data.description": "Items only match if their durability, enchantments, and other attributes match as well.",
"create.gui.package_filter.use_glob_patterns": "Use glob patterns for address pattern matching",
"create.gui.package_filter.use_regex": "Use regex for address pattern matching",
"create.gui.package_filter.use_regex_advanced": "(ADVANCED)",
"create.gui.gauge.info_header": "Gauge Information:",
"create.gui.goggles.at_current_speed": "at current speed",
"create.gui.goggles.basin_contents": "Basin Contents:",
Expand All @@ -1270,9 +1273,14 @@
"create.gui.package_port.catch_packages": "Catch packages addressed to...",
"create.gui.package_port.catch_packages_empty": "Leave empty to match non-addressed",
"create.gui.package_port.catch_packages_wildcard": "Use * as a text wildcard",
"create.gui.package_port.catch_packages_regex": "Regex: Escape special characters with \\",
"create.gui.package_port.catch_packages_wildcard_regex": "Use .* as a text wildcard",
"create.gui.package_port.not_targeting_anything": "No target selected",
"create.gui.package_port.send_and_receive": "Send and receive packages",
"create.gui.package_port.send_only": "Only send packages",
"create.gui.package_port.use_glob_patterns": "Use glob patterns for address pattern matching",
"create.gui.package_port.use_regex": "(ADVANCED) Use regex for address pattern matching",
"create.gui.package_port.use_regex_disabled": "Enable regex in gameplay settings",
"create.gui.redstone_requester.allow_partial": "Allow partial orders",
"create.gui.redstone_requester.dont_allow_partial": "Must send all items",
"create.gui.redstone_requester.requester_address": "Send order to...",
Expand All @@ -1288,6 +1296,9 @@
"create.gui.schedule.move_up": "Move up",
"create.gui.schedule.remove_entry": "Remove Action",
"create.gui.schedule.rmb_remove": "Right-Click to Remove",
"create.gui.schedule.fetch_packages.use_glob": "Glob",
"create.gui.schedule.fetch_packages.use_regex": "Regex",
"create.gui.schedule.fetch_packages.address_matching": "Match Address by...",
"create.gui.schematicTable.availableSchematics": "Available Schematics",
"create.gui.schematicTable.finished": "Upload Finished!",
"create.gui.schematicTable.noSchematics": "No Schematics Saved",
Expand Down Expand Up @@ -3365,4 +3376,4 @@
"item.create.zinc_nugget": "Zinc Nugget",
"itemGroup.create.base": "Create",
"itemGroup.create.palettes": "Create's Building Blocks"
}
}
5 changes: 5 additions & 0 deletions src/main/java/com/simibubi/create/AllDataComponents.java
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,11 @@ public class AllDataComponents {
builder -> builder.persistent(Codec.STRING).networkSynchronized(ByteBufCodecs.STRING_UTF8)
);

public static final DataComponentType<Boolean> FILTER_BY_REGEX = register(
"filter_by_regex",
builder -> builder.persistent(Codec.BOOL).networkSynchronized(ByteBufCodecs.BOOL)
);

public static final DataComponentType<ItemContainerContents> PACKAGE_CONTENTS = register(
"package_contents",
builder -> builder.persistent(ItemContainerContents.CODEC).networkSynchronized(ItemContainerContents.STREAM_CODEC)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public class ChainConveyorBlockEntity extends KineticBlockEntity implements Tran
public record ConnectionStats(float tangentAngle, float chainLength, Vec3 start, Vec3 end) {
}

public record ConnectedPort(float chainPosition, @Nullable BlockPos connection, String filter) {
public record ConnectedPort(float chainPosition, @Nullable BlockPos connection, String filter, boolean usesRegex) {
}

public Set<BlockPos> connections = new HashSet<>();
Expand Down Expand Up @@ -105,11 +105,9 @@ public boolean canAcceptMorePackages() {
public boolean canAcceptPackagesFor(@Nullable BlockPos connection) {
if (connection == null && !canAcceptMorePackages())
return false;
if (connection != null
&& (!(level.getBlockEntity(worldPosition.offset(connection)) instanceof ChainConveyorBlockEntity otherClbe)
|| !otherClbe.canAcceptMorePackages()))
return false;
return true;
return connection == null
|| (level.getBlockEntity(worldPosition.offset(connection)) instanceof ChainConveyorBlockEntity otherClbe
&& otherClbe.canAcceptMorePackages());
}

public boolean canAcceptMorePackagesFromOtherConveyor() {
Expand Down Expand Up @@ -206,9 +204,9 @@ public void tick() {
box.chainPosition += serverSpeed * distancePerTick;
box.chainPosition = Math.min(stats.chainLength, box.chainPosition);

float anticipatePosition = box.chainPosition;
anticipatePosition += serverSpeed * distancePerTick * 4;
anticipatePosition = Math.min(stats.chainLength, anticipatePosition);
float anticipatedPosition = box.chainPosition;
anticipatedPosition += serverSpeed * distancePerTick * 4;
anticipatedPosition = Math.min(stats.chainLength, anticipatedPosition);

if (level.isClientSide() && !isVirtual())
continue;
Expand All @@ -223,9 +221,9 @@ public void tick() {
continue;

boolean notAtPositionYet = box.chainPosition < chainPosition;
if (notAtPositionYet && anticipatePosition < chainPosition)
if (notAtPositionYet && anticipatedPosition < chainPosition)
continue;
if (!PackageItem.matchAddress(box.item, port.filter()))
if (!PackageItem.matchAddress(box.item, port.filter(), port.usesRegex()))
continue;
if (notAtPositionYet) {
notifyPortToAnticipate(portEntry.getKey());
Expand Down Expand Up @@ -276,7 +274,7 @@ public void tick() {
boolean notAtPositionYet = !loopThresholdCrossed(box.chainPosition, prevChainPosition, offBranchAngle);
if (notAtPositionYet && !loopThresholdCrossed(anticipatePosition, prevChainPosition, offBranchAngle))
continue;
if (!PackageItem.matchAddress(box.item, port.filter()))
if (!PackageItem.matchAddress(box.item, port.filter(), port.usesRegex()))
continue;
if (notAtPositionYet) {
notifyPortToAnticipate(portEntry.getKey());
Expand Down Expand Up @@ -793,8 +791,7 @@ public void transform(BlockEntity be, StructureTransform transform) {
.toList());

HashMap<BlockPos, List<ChainConveyorPackage>> newMap = new HashMap<>();
travellingPackages.entrySet()
.forEach(e -> newMap.put(transform.applyWithoutOffset(e.getKey()), e.getValue()));
travellingPackages.forEach((key, value) -> newMap.put(transform.applyWithoutOffset(key), value));
travellingPackages = newMap;

connectionStats = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.apache.commons.lang3.mutable.MutableInt;

import com.simibubi.create.content.logistics.box.PackageItem;
import com.simibubi.create.foundation.utility.LogisticParser;

import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
Expand All @@ -17,7 +18,7 @@ public class ChainConveyorRoutingTable {
public static final int ENTRY_TIMEOUT = 100;
public static final int PORT_ENTRY_TIMEOUT = 20;

public record RoutingTableEntry(String port, int distance, BlockPos nextConnection, MutableInt timeout,
public record RoutingTableEntry(String port, boolean useRegex, int distance, BlockPos nextConnection, MutableInt timeout,
boolean endOfRoute) {

public void tick() {
Expand All @@ -29,7 +30,7 @@ public boolean invalid() {
}

public RoutingTableEntry copyForNeighbour(BlockPos connection) {
return new RoutingTableEntry(port, distance + 1, connection.multiply(-1), new MutableInt(ENTRY_TIMEOUT),
return new RoutingTableEntry(port, useRegex, distance + 1, connection.multiply(-1), new MutableInt(ENTRY_TIMEOUT),
false);
}

Expand All @@ -49,13 +50,13 @@ public boolean shouldAdvertise() {
return changed || lastUpdate > ENTRY_TIMEOUT - 20;
}

public void receivePortInfo(String filter, BlockPos connection) {
insert(new RoutingTableEntry(filter, "*".equals(filter) ? 1000 : 0, connection, new MutableInt(PORT_ENTRY_TIMEOUT), true));
public void receivePortInfo(String filter, boolean usesRegex, BlockPos connection) {
insert(new RoutingTableEntry(filter, usesRegex, LogisticParser.matchesAll(filter, usesRegex) ? Integer.MAX_VALUE : 0, connection, new MutableInt(PORT_ENTRY_TIMEOUT), true));
}

public BlockPos getExitFor(ItemStack box) {
for (RoutingTableEntry entry : entriesByDistance)
if (PackageItem.matchAddress(box, entry.port()))
if (PackageItem.matchAddress(box, entry.port(), entry.useRegex()))
return entry.nextConnection();
return BlockPos.ZERO;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import javax.annotation.Nullable;

import com.google.re2j.Pattern;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import com.simibubi.create.AllDataComponents;
Expand All @@ -16,8 +17,9 @@
import com.simibubi.create.content.logistics.stockTicker.PackageOrderWithCrafts;
import com.simibubi.create.foundation.item.ItemHelper;

import com.simibubi.create.foundation.utility.LogisticParser;

import net.createmod.catnip.codecs.stream.CatnipStreamCodecBuilders;
import net.createmod.catnip.data.Glob;
import net.createmod.catnip.math.VecHelper;
import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos;
Expand Down Expand Up @@ -139,20 +141,21 @@ public static void addOrderContext(ItemStack box, PackageOrderWithCrafts orderCo
box.set(AllDataComponents.PACKAGE_ORDER_CONTEXT, orderContext);
}

public static boolean matchAddress(ItemStack box, String address) {
return matchAddress(getAddress(box), address);
public static boolean matchAddress(ItemStack box, String address, boolean useRegex) {
return matchAddress(getAddress(box), address, useRegex);
}

public static boolean matchAddress(String boxAddress, String address) {
public static boolean matchAddress(String boxAddress, String address, boolean useRegex) {
if (address.isBlank())
return boxAddress.isBlank();
if (address.equals("*") || boxAddress.equals("*"))
return true;
String matcher = Glob.toRegexPattern(address, "");
String boxMatcher = Glob.toRegexPattern(boxAddress, "");
return address.matches(boxMatcher) || boxAddress.matches(matcher);
Pattern addressMatcher = LogisticParser.dynamicToRegex(address, "", useRegex);
Pattern boxAddressMatcher = LogisticParser.dynamicToRegex(boxAddress, "", useRegex);

return LogisticParser.anyMatches(addressMatcher, boxAddress) || LogisticParser.anyMatches(boxAddressMatcher, address);
}

// public static boolean

public static String getAddress(ItemStack box) {
return box.getOrDefault(AllDataComponents.PACKAGE_ADDRESS, "");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,16 +239,18 @@ public boolean test(Level world, ItemStack stack, boolean matchNBT) {
public static class PackageFilterItemStack extends FilterItemStack {

public String filterString;
public boolean useRegex;

protected PackageFilterItemStack(ItemStack filter) {
super(filter);
filterString = PackageItem.getAddress(filter);
useRegex = filter.getOrDefault(AllDataComponents.FILTER_BY_REGEX, false);
}

@Override
public boolean test(Level world, ItemStack stack, boolean matchNBT) {
return (filterString.isBlank() && super.test(world, stack, matchNBT))
|| PackageItem.isPackage(stack) && PackageItem.matchAddress(stack, filterString);
|| PackageItem.isPackage(stack) && PackageItem.matchAddress(stack, filterString, useRegex);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,13 @@ public void handle(ServerPlayer player) {
if (player.containerMenu instanceof PackageFilterMenu c) {
if (option == Option.UPDATE_ADDRESS)
c.address = tag.getString("Address");
if (option == Option.UPDATE_MATCH_TYPE)
c.useRegex = tag.getBoolean("UseRegex");
}
}

public enum Option {
WHITELIST, WHITELIST2, BLACKLIST, RESPECT_DATA, IGNORE_DATA, UPDATE_FILTER_ITEM, ADD_TAG, ADD_INVERTED_TAG, UPDATE_ADDRESS;
WHITELIST, WHITELIST2, BLACKLIST, RESPECT_DATA, IGNORE_DATA, UPDATE_FILTER_ITEM, ADD_TAG, ADD_INVERTED_TAG, UPDATE_ADDRESS, UPDATE_MATCH_TYPE;

public static final StreamCodec<ByteBuf, Option> STREAM_CODEC = CatnipStreamCodecBuilders.ofEnum(Option.class);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import com.simibubi.create.AllDataComponents;
import com.simibubi.create.AllMenuTypes;

import com.simibubi.create.infrastructure.config.AllConfigs;

import net.minecraft.client.gui.components.EditBox;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.world.entity.player.Inventory;
Expand All @@ -14,6 +16,7 @@
public class PackageFilterMenu extends AbstractFilterMenu {

String address;
boolean useRegex;
EditBox addressInput;

public PackageFilterMenu(MenuType<?> type, int id, Inventory inv, RegistryFriendlyByteBuf extraData) {
Expand Down Expand Up @@ -55,17 +58,23 @@ public void clearContents() {
protected void initAndReadInventory(ItemStack filterItem) {
super.initAndReadInventory(filterItem);
address = filterItem.getOrDefault(AllDataComponents.PACKAGE_ADDRESS, "");
useRegex = filterItem.getOrDefault(AllDataComponents.FILTER_BY_REGEX, false);
}

public boolean usingRegex() {
return AllConfigs.server().extras.enableAdvancedRegex.get() && useRegex;
}

@Override
protected void saveData(ItemStack filterItem) {
super.saveData(filterItem);
filterItem.set(AllDataComponents.FILTER_BY_REGEX, useRegex);
if (address.isBlank())
filterItem.remove(AllDataComponents.PACKAGE_ADDRESS);
else
filterItem.set(AllDataComponents.PACKAGE_ADDRESS, address);
}

@Override
public ItemStack quickMoveStack(Player playerIn, int index) {
return ItemStack.EMPTY;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package com.simibubi.create.content.logistics.filter;

import com.simibubi.create.foundation.gui.AllIcons;

import com.simibubi.create.foundation.utility.CreateLang;

import net.minecraft.ChatFormatting;

import org.lwjgl.glfw.GLFW;

import com.mojang.blaze3d.vertex.PoseStack;
Expand All @@ -16,9 +22,12 @@
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Inventory;

import java.util.concurrent.atomic.AtomicBoolean;

public class PackageFilterScreen extends AbstractFilterScreen<PackageFilterMenu> {

private AddressEditBox addressBox;
private IconButton useGlobPatternButton, useRegexButton;
private boolean deferFocus;

public PackageFilterScreen(PackageFilterMenu menu, Inventory inv, Component title) {
Expand All @@ -37,6 +46,7 @@ protected void containerTick() {

@Override
protected void init() {
AtomicBoolean regexState = new AtomicBoolean(menu.useRegex);
setWindowOffset(-11, 7);
super.init();

Expand All @@ -49,6 +59,31 @@ protected void init() {
addressBox.setResponder(this::onAddressEdited);
addRenderableWidget(addressBox);

useGlobPatternButton = new IconButton(x + 18, y + 28 + 36, AllIcons.I_GLOB_PATTERN);
useGlobPatternButton.withCallback(() -> {
useGlobPatternButton.green = true;
useRegexButton.green = false;
regexState.set(false);
onRegexToggled(regexState);
});
useGlobPatternButton.setToolTip(CreateLang.translate("gui.package_filter.use_glob_patterns")
.style(ChatFormatting.WHITE)
.component());
addRenderableWidget(useGlobPatternButton);

useRegexButton = new IconButton(x + 18 + 18, y + 28 + 36, AllIcons.I_REGEX);
useRegexButton.active = menu.usingRegex();
useRegexButton.withCallback(() -> {
useGlobPatternButton.green = false;
useRegexButton.green = true;
regexState.set(true);
onRegexToggled(regexState);
});
useRegexButton.setToolTip(CreateLang.translate("gui.package_filter.use_regex")
.style(ChatFormatting.GOLD)
.component());
addRenderableWidget(useRegexButton);

setFocused(addressBox);
}

Expand All @@ -71,6 +106,13 @@ public void onAddressEdited(String s) {
CatnipServices.NETWORK.sendToServer(new FilterScreenPacket(Option.UPDATE_ADDRESS, tag));
}

public void onRegexToggled(AtomicBoolean b) {
menu.useRegex = b.get();
CompoundTag tag = new CompoundTag();
tag.putBoolean("UseRegex", b.get());
CatnipServices.NETWORK.sendToServer(new FilterScreenPacket(Option.UPDATE_MATCH_TYPE, tag));
}

@Override
public boolean mouseClicked(double pMouseX, double pMouseY, int pButton) {
return super.mouseClicked(pMouseX, pMouseY, pButton);
Expand Down
Loading