From cb0c3d5925dcd6b043031d9ee5d847be6f24ffb9 Mon Sep 17 00:00:00 2001 From: ABKQPO <93412322+ABKQPO@users.noreply.github.com> Date: Sat, 3 Oct 2026 23:41:52 +0800 Subject: [PATCH 1/7] Reduce occupied-space memory for large structures --- .../structure/StructureDefinition.java | 72 ++++++++++++++++--- 1 file changed, 63 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureDefinition.java b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureDefinition.java index 6b97a344..00eade2b 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureDefinition.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureDefinition.java @@ -5,6 +5,7 @@ import static com.gtnewhorizon.structurelib.structure.StructureUtility.step; import java.util.Arrays; +import java.util.BitSet; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -19,14 +20,14 @@ public class StructureDefinition implements IStructureDefinition { private final Map> elements; private final Map shapes; private final Map[]> structures; - private final Map> occupiedSpaces; + private final Map occupiedSpaces; public static Builder builder() { return new Builder<>(); } private StructureDefinition(Map> elements, Map shapes, - Map[]> structures, Map> occupiedSpaces) { + Map[]> structures, Map occupiedSpaces) { this.elements = elements; this.shapes = shapes; this.structures = structures; @@ -42,7 +43,7 @@ public static class Builder { private final Map navigates; private final Map> elements; private final Map shapes; - private final Map> occupiedSpaces; + private final Map occupiedSpaces; private Builder() { navigates = new HashMap<>(); @@ -95,7 +96,19 @@ public Builder addShape(String name, String[][] structurePiece) { } builder.setLength(builder.length() - 1); } - Set occupiedSpace = new HashSet<>(); + int sizeA = 0; + int sizeB = 0; + long occupiedCount = 0; + for (String[] strings : structurePiece) { + sizeB = Math.max(sizeB, strings.length); + for (String string : strings) { + sizeA = Math.max(sizeA, string.length()); + for (int i = 0; i < string.length(); i++) { + if (string.charAt(i) != ' ') occupiedCount++; + } + } + } + OccupiedSpace occupiedSpace = new OccupiedSpace(sizeA, sizeB, structurePiece.length, occupiedCount); // these track the global current location int aa = 0, bb = 0, cc = 0; // these track the vec3 towards next meaningful element @@ -109,7 +122,7 @@ public Builder addShape(String name, String[][] structurePiece) { } else if (ch == '~') { builder.setCharAt(i, A); ch = A; - occupiedSpace.add(Vec3Impl.getFromPool(aa, bb, cc)); + occupiedSpace.add(aa, bb, cc); } if (ch == A) { aa++; @@ -135,13 +148,13 @@ public Builder addShape(String name, String[][] structurePiece) { addElement(navigate, step(vec3)); } builder.setCharAt(i - 1, navigate); - occupiedSpace.add(Vec3Impl.getFromPool(aa, bb, cc)); + occupiedSpace.add(aa, bb, cc); aa++; a = 0; b = 0; c = 0; } else { - occupiedSpace.add(Vec3Impl.getFromPool(aa, bb, cc)); + occupiedSpace.add(aa, bb, cc); aa++; } } @@ -259,8 +272,49 @@ public IStructureElement[] getStructureFor(String name) { @Override public boolean isContainedInStructure(String name, int offsetA, int offsetB, int offsetC) { - Set occupiedSpace = occupiedSpaces.get(name); + OccupiedSpace occupiedSpace = occupiedSpaces.get(name); if (occupiedSpace == null) throw new NoSuchElementException(name); - return occupiedSpace.contains(new Vec3Impl(offsetA, offsetB, offsetC)); + return occupiedSpace.contains(offsetA, offsetB, offsetC); + } + + private static class OccupiedSpace { + + private final int sizeA; + private final int sizeB; + private final int sizeC; + private final BitSet occupied; + private final Set sparseOccupied; + + private OccupiedSpace(int sizeA, int sizeB, int sizeC, long occupiedCount) { + this.sizeA = sizeA; + this.sizeB = sizeB; + this.sizeC = sizeC; + long volume = (long) sizeA * sizeB * sizeC; + if (volume <= Integer.MAX_VALUE && volume <= Math.max(1024L, occupiedCount * 256L)) { + occupied = new BitSet((int) volume); + sparseOccupied = null; + } else { + occupied = null; + sparseOccupied = new HashSet<>(); + } + } + + private void add(int a, int b, int c) { + if (occupied != null) { + occupied.set(index(a, b, c)); + } else { + sparseOccupied.add(new Vec3Impl(a, b, c)); + } + } + + private boolean contains(int a, int b, int c) { + if (a < 0 || b < 0 || c < 0 || a >= sizeA || b >= sizeB || c >= sizeC) return false; + if (occupied != null) return occupied.get(index(a, b, c)); + return sparseOccupied.contains(new Vec3Impl(a, b, c)); + } + + private int index(int a, int b, int c) { + return (c * sizeB + b) * sizeA + a; + } } } From b34c6680cc2d09ffc8d6a02281966755084b058d Mon Sep 17 00:00:00 2001 From: ABKQPO <93412322+ABKQPO@users.noreply.github.com> Date: Sat, 3 Oct 2026 23:41:53 +0800 Subject: [PATCH 2/7] Allow standalone structure channel definitions --- .../constructable/ChannelDataAccessor.java | 8 +- .../command/CommandConfigureChannels.java | 4 +- .../gui/GuiScreenConfigureChannels.java | 24 ++-- .../structure/ChannelDefinitions.java | 113 ++++++++++++++++++ .../structure/IChannelAwareElement.java | 16 +++ .../structure/IChannelDefinition.java | 57 +++++++++ .../structure/IStructureDefinition.java | 44 +++++++ .../structure/StructureDefinition.java | 64 +++++++++- .../structure/StructureUtility.java | 10 +- 9 files changed, 320 insertions(+), 20 deletions(-) create mode 100644 src/main/java/com/gtnewhorizon/structurelib/structure/ChannelDefinitions.java create mode 100644 src/main/java/com/gtnewhorizon/structurelib/structure/IChannelAwareElement.java create mode 100644 src/main/java/com/gtnewhorizon/structurelib/structure/IChannelDefinition.java diff --git a/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ChannelDataAccessor.java b/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ChannelDataAccessor.java index 6b12efd3..5ccfe0e1 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ChannelDataAccessor.java +++ b/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ChannelDataAccessor.java @@ -38,7 +38,9 @@ private ChannelDataAccessor() {} * If given subchannel does not exist, masterStack may or may not be returned to save performance. Code within * StructureLib can depend on this behavior, you cannot. * - * @param masterStack trigger item. can be the return value of another {@link #withChannel(ItemStack, String)} call + * @param masterStack trigger item. can be the return value of another + * {@link com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor#withChannel(net.minecraft.item.ItemStack, String)} + * call * @param channel subchannel to use. will use current master channel data instead if this channel does not exist * Note: all channel identifiers are supposed to be lower case and not empty. * @return a trigger item with new master channel @@ -107,13 +109,13 @@ public static int getChannelData(ItemStack masterStack, String channel) { * * @param masterStack trigger stack to check * @param channel channel identifier. Note: all channel identifiers are supposed to be lower case and not empty. - * @param data subchannel data. should always be a positive value + * @param data non-negative subchannel data */ public static void setChannelData(ItemStack masterStack, String channel, int data) { if (StringUtils.isEmpty(channel) || masterStack == null) throw new IllegalArgumentException(); + if (data < 0) throw new IllegalArgumentException("Channel data cannot be negative"); if (StructureLibAPI.isDebugEnabled() && !channel.toLowerCase(Locale.ROOT).equals(channel)) throw new IllegalArgumentException("Channel name can be lower case ONLY"); - if (data <= 0) throw new IllegalArgumentException(); if (masterStack.stackTagCompound == null) masterStack.stackTagCompound = new NBTTagCompound(); NBTTagCompound main = masterStack.stackTagCompound; if (!main.hasKey(SECONDARY_HINT_TAG, TAG_COMPOUND)) main.setTag(SECONDARY_HINT_TAG, new NBTTagCompound()); diff --git a/src/main/java/com/gtnewhorizon/structurelib/command/CommandConfigureChannels.java b/src/main/java/com/gtnewhorizon/structurelib/command/CommandConfigureChannels.java index 8fde0d7d..fdf87347 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/command/CommandConfigureChannels.java +++ b/src/main/java/com/gtnewhorizon/structurelib/command/CommandConfigureChannels.java @@ -26,7 +26,7 @@ public String getCommandName() { public String getCommandUsage(ICommandSender p_71518_1_) { return "/sl_channel \n" + "/sl_channel \n" + "/sl_channel \n" - + " can have space in it. must be positive integer"; + + " can have space in it. must be a non-negative integer"; } @Override @@ -108,7 +108,7 @@ public void processCommand(ICommandSender sender, String[] args) { throw new WrongUsageException("%s not a valid value", args[args.length - 1]); } - if (value <= 0) throw new WrongUsageException("%s not a valid value", args[args.length - 1]); + if (value < 0) throw new WrongUsageException("%s not a valid value", args[args.length - 1]); ChannelDataAccessor.setChannelData(heldItem, channel, value); player.inventoryContainer.detectAndSendChanges(); diff --git a/src/main/java/com/gtnewhorizon/structurelib/gui/GuiScreenConfigureChannels.java b/src/main/java/com/gtnewhorizon/structurelib/gui/GuiScreenConfigureChannels.java index da33f502..6ceb4408 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/gui/GuiScreenConfigureChannels.java +++ b/src/main/java/com/gtnewhorizon/structurelib/gui/GuiScreenConfigureChannels.java @@ -137,9 +137,9 @@ public void setFocused(boolean p_146195_1_) { if (!p_146195_1_ && isFocused() && !StringUtils.isBlank(getText())) { int result; try { - result = Math.max(Integer.parseInt(getText()), 1); + result = Math.max(Integer.parseInt(getText()), 0); } catch (NumberFormatException e) { - result = 1; + result = 0; } setText(String.valueOf(result)); } @@ -239,10 +239,12 @@ public void handleMouseInput() { int delta = Mouse.getEventDWheel(); if (delta != 0) { if (isMouseOverValue()) { + if (!isInteger(value.getText())) return; + int current = getValue(); if (delta > 0) { - value.setText(String.valueOf(getValue() + 1)); + value.setText(String.valueOf(current == Integer.MAX_VALUE ? current : current + 1)); } else { - value.setText(String.valueOf(Math.max(getValue() - 1, 1))); + value.setText(String.valueOf(current == 0 ? 0 : current - 1)); } } else { list.handleDWheel(delta); @@ -282,7 +284,7 @@ protected void keyTyped(char aChar, int aKey) { key.setText(autoCompleteList.getSelectedElement()); key.setFocused(false); value.setFocused(true); - value.setText("1"); + value.setText("0"); value.setSelectionPos(0); return; } @@ -322,17 +324,21 @@ private void updateButtons() { : I18n.format(I18N_PREFIX + "add"); getButtonList().get(ADD_BTN).enabled = !StringUtils.isBlank(key.getText()) && !StringUtils.isBlank(value.getText()) - && Integer.parseInt(value.getText()) > 0; + && isInteger(value.getText()); getButtonList().get(UNSET_BTN).enabled = existing && !StringUtils.isBlank(value.getText()); autoCompleteList.reset(); } private int getValue() { + return Integer.parseInt(value.getText()); + } + + private static boolean isInteger(String text) { try { - return Integer.parseInt(value.getText()); + return Integer.parseInt(text) >= 0; } catch (NumberFormatException e) { - return -1; + return false; } } @@ -342,7 +348,7 @@ protected void actionPerformed(GuiButton btn) { switch (btn.id) { case ADD_BTN: int value = getValue(); - if (value <= 0) return; + if (value < 0) return; ChannelDataAccessor.setChannelData(trigger, key.getText(), value); break; case UNSET_BTN: diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/ChannelDefinitions.java b/src/main/java/com/gtnewhorizon/structurelib/structure/ChannelDefinitions.java new file mode 100644 index 00000000..c667f7ee --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/ChannelDefinitions.java @@ -0,0 +1,113 @@ +package com.gtnewhorizon.structurelib.structure; + +import java.util.Locale; + +import org.apache.commons.lang3.StringUtils; + +/** + * Factory methods for common structure channel policies. + */ +public class ChannelDefinitions { + + /** + * Creates a channel with a bounded inclusive range and clamping normalization. + * + * @param channel channel identifier + * @param minimumValue inclusive minimum value + * @param maximumValue inclusive maximum value + * @return bounded channel definition + */ + public static IChannelDefinition bounded(String channel, int minimumValue, int maximumValue) { + validateChannel(channel); + if (minimumValue < 0 || maximumValue < minimumValue) { + throw new IllegalArgumentException("Invalid channel bounds"); + } + return new BoundedChannelDefinition(channel, minimumValue, maximumValue); + } + + /** + * Creates a channel accepting every non-negative integer. + * + * @param channel channel identifier + * @return unbounded channel definition + */ + public static IChannelDefinition unbounded(String channel) { + validateChannel(channel); + return new UnboundedChannelDefinition(channel); + } + + /** + * Validates a channel definition before it is attached to a structure definition. + * + * @param definition definition to validate + */ + public static void validate(IChannelDefinition definition) { + if (definition == null) throw new IllegalArgumentException("Channel definition cannot be null"); + validateChannel(definition.getChannel()); + int minimum = definition.getMinimumValue(); + int maximum = definition.getMaximumValue(); + if (minimum < 0 || maximum < minimum) { + throw new IllegalArgumentException("Invalid channel definition values"); + } + int normalized = definition.normalizeValue(minimum); + if (normalized < minimum || normalized > maximum) { + throw new IllegalArgumentException( + "Channel normalization must return a value within the definition bounds"); + } + } + + /** + * Validates a channel identifier. + * + * @param channel channel identifier + */ + public static void validateChannel(String channel) { + if (StringUtils.isEmpty(channel) || !channel.toLowerCase(Locale.ROOT).equals(channel)) { + throw new IllegalArgumentException("Channel name must be non-empty and lower case"); + } + } + + public static class BoundedChannelDefinition implements IChannelDefinition { + + private final String channel; + private final int minimumValue; + private final int maximumValue; + + private BoundedChannelDefinition(String channel, int minimumValue, int maximumValue) { + this.channel = channel; + this.minimumValue = minimumValue; + this.maximumValue = maximumValue; + } + + @Override + public String getChannel() { + return channel; + } + + @Override + public int getMinimumValue() { + return minimumValue; + } + + @Override + public int getMaximumValue() { + return maximumValue; + } + + } + + public static class UnboundedChannelDefinition implements IChannelDefinition { + + private final String channel; + + private UnboundedChannelDefinition(String channel) { + this.channel = channel; + } + + @Override + public String getChannel() { + return channel; + } + + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IChannelAwareElement.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IChannelAwareElement.java new file mode 100644 index 00000000..08593d9a --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IChannelAwareElement.java @@ -0,0 +1,16 @@ +package com.gtnewhorizon.structurelib.structure; + +/** + * Identifies the channel selected by a structure element wrapper. + * + * @param structure context type + */ +public interface IChannelAwareElement extends IStructureElement { + + /** + * Returns the channel used by this element. + * + * @return lower-case channel identifier + */ + String getChannel(); +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IChannelDefinition.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IChannelDefinition.java new file mode 100644 index 00000000..e74a6436 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IChannelDefinition.java @@ -0,0 +1,57 @@ +package com.gtnewhorizon.structurelib.structure; + +/** + * Describes a channel used by one structure definition. + * + *

+ * Channel values are integers stored on the constructable trigger item. Implementations may override the default + * validation and normalization policy when a simple bounded range is not sufficient. + *

+ */ +public interface IChannelDefinition { + + /** + * Returns the lower-case channel identifier. + * + * @return channel identifier + */ + String getChannel(); + + /** + * Returns the smallest value advertised by this definition. The default is {@code 0}. + * + * @return minimum value + */ + default int getMinimumValue() { + return 0; + } + + /** + * Returns the largest value advertised by this definition. The default is {@link java.lang.Integer#MAX_VALUE}. + * + * @return maximum value + */ + default int getMaximumValue() { + return Integer.MAX_VALUE; + } + + /** + * Checks whether a value is already valid without normalization. + * + * @param value value to check + * @return true when the value is valid + */ + default boolean isValueValid(int value) { + return value >= getMinimumValue() && value <= getMaximumValue(); + } + + /** + * Converts a stored value to the value used by the structure definition. + * + * @param value stored value + * @return normalized value + */ + default int normalizeValue(int value) { + return Math.max(getMinimumValue(), Math.min(getMaximumValue(), value)); + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureDefinition.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureDefinition.java index c95f8172..51d9d4bd 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureDefinition.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureDefinition.java @@ -3,6 +3,8 @@ import static com.gtnewhorizon.structurelib.structure.IStructureWalker.ignoreBlockUnloaded; import static com.gtnewhorizon.structurelib.structure.IStructureWalker.skipBlockUnloaded; +import java.util.Collection; +import java.util.Collections; import java.util.function.Function; import net.minecraft.entity.player.EntityPlayer; @@ -56,6 +58,48 @@ */ public interface IStructureDefinition { + /** + * Returns channel definitions declared by this structure. + * + * @return an immutable or otherwise read-only channel collection + */ + default Collection getChannelDefinitions() { + return Collections.emptyList(); + } + + /** + * Returns channel definitions explicitly added by the structure builder. + * + * @return explicitly declared channel definitions + */ + default Collection getExplicitChannelDefinitions() { + return getChannelDefinitions(); + } + + /** + * Finds a channel definition by identifier. + * + * @param channel channel identifier + * @return channel definition, or {@code null} when it is not declared + */ + default IChannelDefinition getChannelDefinition(String channel) { + for (IChannelDefinition definition : getChannelDefinitions()) { + if (definition.getChannel().equals(channel)) return definition; + } + return null; + } + + /** + * Reads one channel using the standard trigger-item channel semantics. + * + * @param trigger trigger item containing channel data + * @param channel channel identifier + * @return stored channel data, or the trigger stack size when absent + */ + default int getChannelData(ItemStack trigger, String channel) { + return ChannelDataAccessor.getChannelData(trigger, channel); + } + /** * Used internally *

diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureDefinition.java b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureDefinition.java index 00eade2b..fcd423d6 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureDefinition.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureDefinition.java @@ -6,6 +6,8 @@ import java.util.Arrays; import java.util.BitSet; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -21,17 +23,27 @@ public class StructureDefinition implements IStructureDefinition { private final Map shapes; private final Map[]> structures; private final Map occupiedSpaces; + private final Map channelDefinitions; + private final Set explicitChannels; + private final Collection channelDefinitionValues; + private final Collection explicitChannelDefinitions; public static Builder builder() { return new Builder<>(); } private StructureDefinition(Map> elements, Map shapes, - Map[]> structures, Map occupiedSpaces) { + Map[]> structures, Map occupiedSpaces, + Map channelDefinitions, Set explicitChannels) { this.elements = elements; this.shapes = shapes; this.structures = structures; this.occupiedSpaces = occupiedSpaces; + this.channelDefinitions = Collections.unmodifiableMap(new HashMap<>(channelDefinitions)); + this.explicitChannels = Collections.unmodifiableSet(new HashSet<>(explicitChannels)); + this.channelDefinitionValues = Collections.unmodifiableCollection(this.channelDefinitions.values()); + this.explicitChannelDefinitions = Collections.unmodifiableList( + this.explicitChannels.stream().map(this.channelDefinitions::get).collect(Collectors.toList())); } public static class Builder { @@ -44,12 +56,16 @@ public static class Builder { private final Map> elements; private final Map shapes; private final Map occupiedSpaces; + private final Map channels; + private final Set explicitChannels; private Builder() { navigates = new HashMap<>(); elements = new HashMap<>(); shapes = new HashMap<>(); occupiedSpaces = new HashMap<>(); + channels = new HashMap<>(); + explicitChannels = new HashSet<>(); } public Map> getElements() { @@ -195,22 +211,47 @@ public Builder addShape(String name, String[][] structurePiece) { */ @Deprecated public Builder addElement(Character name, IStructureElement structurePiece) { - elements.putIfAbsent(name, structurePiece); + if (elements.putIfAbsent(name, structurePiece) == null) registerElementChannel(structurePiece); return this; } public Builder addElement(char name, IStructureElement structurePiece) { - elements.putIfAbsent(name, structurePiece); + if (elements.putIfAbsent(name, structurePiece) == null) registerElementChannel(structurePiece); return this; } + public Builder addChannel(IChannelDefinition definition) { + ChannelDefinitions.validate(definition); + channels.put(definition.getChannel(), definition); + explicitChannels.add(definition.getChannel()); + return this; + } + + public Builder addChannel(String channel, int minimumValue, int maximumValue) { + return addChannel(ChannelDefinitions.bounded(channel, minimumValue, maximumValue)); + } + + private void registerElementChannel(IStructureElement structurePiece) { + if (structurePiece instanceof IChannelAwareElement) { + String channel = ((IChannelAwareElement) structurePiece).getChannel(); + if (!explicitChannels.contains(channel)) { + channels.putIfAbsent(channel, ChannelDefinitions.unbounded(channel)); + } + } + } + public IStructureDefinition build() { + for (IStructureElement element : elements.values()) { + registerElementChannel(element); + } Map[]> structures = compileStructureMap(); return new StructureDefinition<>( new HashMap<>(elements), new HashMap<>(shapes), structures, - occupiedSpaces); + occupiedSpaces, + channels, + explicitChannels); } @SuppressWarnings("unchecked") @@ -263,6 +304,21 @@ public Map[]> getStructures() { return structures; } + @Override + public Collection getChannelDefinitions() { + return channelDefinitionValues; + } + + @Override + public Collection getExplicitChannelDefinitions() { + return explicitChannelDefinitions; + } + + @Override + public IChannelDefinition getChannelDefinition(String channel) { + return channelDefinitions.get(channel); + } + @Override public IStructureElement[] getStructureFor(String name) { IStructureElement[] elements = structures.get(name); diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java index a1ac478e..1d74047d 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java @@ -4,6 +4,7 @@ import static com.gtnewhorizon.structurelib.StructureLib.PANIC_MODE; import static java.lang.Integer.MIN_VALUE; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -727,7 +728,7 @@ public static IStructureElement ofBlocksTiered(ITierConverter Function getter) { List descriptions = null; if (allKnownTiers != null) { - descriptions = new java.util.ArrayList<>(); + descriptions = new ArrayList<>(); for (Pair tier : allKnownTiers) { Item item = Item.getItemFromBlock(tier.getLeft()); if (item != null) { @@ -2992,7 +2993,12 @@ public static IStructureElementDeferred partitionBy(Function * See channels.md in docs folder */ public static IStructureElement withChannel(String channel, IStructureElement backing) { - return new IStructureElement() { + return new IChannelAwareElement<>() { + + @Override + public String getChannel() { + return channel; + } public boolean check(T t, World world, int x, int y, int z) { return backing.check(t, world, x, y, z); From 433dc404812ad47f10cc3cc09e3bc3c18572fa0b Mon Sep 17 00:00:00 2001 From: ABKQPO <93412322+ABKQPO@users.noreply.github.com> Date: Sat, 3 Oct 2026 23:43:50 +0800 Subject: [PATCH 3/7] Update overview.html --- src/main/javadoc/overview.html | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/main/javadoc/overview.html b/src/main/javadoc/overview.html index deb993f2..12f6f8c9 100644 --- a/src/main/javadoc/overview.html +++ b/src/main/javadoc/overview.html @@ -145,7 +145,7 @@

Channels

Basically, channels are information stored on the trigger item's NBT tags. One trigger item can carry multiple channels. - Each channel can be identified with lower case strings and has the value being one positive Java int. + Each channel can be identified with lower case strings and has a non-negative integer value. There will be one and only one master channel and 0 or more sub channel. When a non-existent sub channel is queried, master channel will be used instead to fill its place.

@@ -174,6 +174,37 @@

Channels

master channel.

+

Metadata-only channels

+ +

+ A channel does not need to represent a block or an element in a structure shape. Structure definitions can declare + metadata-only channels for values such as a widget selection or a variable structure height. These declarations are + independent of addElement and do not require a fake block or a shape character. +

+ +
+IStructureDefinition.<Controller>builder()
+        .addChannel("height", 3, 100)
+        .addChannel("length", 4, 50)
+        .addChannel(ChannelDefinitions.unbounded("widget"))
+        .addElement('A', ...)
+        .build();
+
+ +

+ Channel values are still stored as integers in the trigger item's channels NBT compound. The + generic channel GUI has no active structure definition, so it edits integers and does not impose one + structure's range on every other structure using the same channel name. Structure-aware code reads values through + IStructureDefinition#getChannelData, which uses the definition's default when the subchannel is absent + and then applies that definition's local normalization policy. +

+ +

+ Existing elements wrapped with StructureUtility.withChannel are discovered automatically. An explicit + channel declaration supplies its local range and normalization policy for that structure. The same channel name may + therefore use different ranges in different structure definitions. +

+

why is this called channel?

Just give me a better name and I'd do the renaming. From f98641b118da07b41feb42f39c9bea8e213b675f Mon Sep 17 00:00:00 2001 From: ABKQPO <93412322+ABKQPO@users.noreply.github.com> Date: Wed, 7 Oct 2026 12:45:49 +0800 Subject: [PATCH 4/7] Simplify structure channel definition API --- .../structure/ChannelDefinitions.java | 113 ------------------ .../structure/IChannelAwareElement.java | 10 +- .../structure/IChannelDefinition.java | 41 +++++++ .../structure/StructureDefinition.java | 27 ++++- .../structure/StructureUtility.java | 16 ++- src/main/javadoc/overview.html | 2 +- 6 files changed, 84 insertions(+), 125 deletions(-) delete mode 100644 src/main/java/com/gtnewhorizon/structurelib/structure/ChannelDefinitions.java diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/ChannelDefinitions.java b/src/main/java/com/gtnewhorizon/structurelib/structure/ChannelDefinitions.java deleted file mode 100644 index c667f7ee..00000000 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/ChannelDefinitions.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.gtnewhorizon.structurelib.structure; - -import java.util.Locale; - -import org.apache.commons.lang3.StringUtils; - -/** - * Factory methods for common structure channel policies. - */ -public class ChannelDefinitions { - - /** - * Creates a channel with a bounded inclusive range and clamping normalization. - * - * @param channel channel identifier - * @param minimumValue inclusive minimum value - * @param maximumValue inclusive maximum value - * @return bounded channel definition - */ - public static IChannelDefinition bounded(String channel, int minimumValue, int maximumValue) { - validateChannel(channel); - if (minimumValue < 0 || maximumValue < minimumValue) { - throw new IllegalArgumentException("Invalid channel bounds"); - } - return new BoundedChannelDefinition(channel, minimumValue, maximumValue); - } - - /** - * Creates a channel accepting every non-negative integer. - * - * @param channel channel identifier - * @return unbounded channel definition - */ - public static IChannelDefinition unbounded(String channel) { - validateChannel(channel); - return new UnboundedChannelDefinition(channel); - } - - /** - * Validates a channel definition before it is attached to a structure definition. - * - * @param definition definition to validate - */ - public static void validate(IChannelDefinition definition) { - if (definition == null) throw new IllegalArgumentException("Channel definition cannot be null"); - validateChannel(definition.getChannel()); - int minimum = definition.getMinimumValue(); - int maximum = definition.getMaximumValue(); - if (minimum < 0 || maximum < minimum) { - throw new IllegalArgumentException("Invalid channel definition values"); - } - int normalized = definition.normalizeValue(minimum); - if (normalized < minimum || normalized > maximum) { - throw new IllegalArgumentException( - "Channel normalization must return a value within the definition bounds"); - } - } - - /** - * Validates a channel identifier. - * - * @param channel channel identifier - */ - public static void validateChannel(String channel) { - if (StringUtils.isEmpty(channel) || !channel.toLowerCase(Locale.ROOT).equals(channel)) { - throw new IllegalArgumentException("Channel name must be non-empty and lower case"); - } - } - - public static class BoundedChannelDefinition implements IChannelDefinition { - - private final String channel; - private final int minimumValue; - private final int maximumValue; - - private BoundedChannelDefinition(String channel, int minimumValue, int maximumValue) { - this.channel = channel; - this.minimumValue = minimumValue; - this.maximumValue = maximumValue; - } - - @Override - public String getChannel() { - return channel; - } - - @Override - public int getMinimumValue() { - return minimumValue; - } - - @Override - public int getMaximumValue() { - return maximumValue; - } - - } - - public static class UnboundedChannelDefinition implements IChannelDefinition { - - private final String channel; - - private UnboundedChannelDefinition(String channel) { - this.channel = channel; - } - - @Override - public String getChannel() { - return channel; - } - - } -} diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IChannelAwareElement.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IChannelAwareElement.java index 08593d9a..37010ecc 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/IChannelAwareElement.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IChannelAwareElement.java @@ -1,16 +1,18 @@ package com.gtnewhorizon.structurelib.structure; +import java.util.Collection; + /** - * Identifies the channel selected by a structure element wrapper. + * Identifies the channels selected by structure element wrappers. * * @param structure context type */ public interface IChannelAwareElement extends IStructureElement { /** - * Returns the channel used by this element. + * Returns the channels used by this element. * - * @return lower-case channel identifier + * @return lower-case channel identifiers */ - String getChannel(); + Collection getChannels(); } diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IChannelDefinition.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IChannelDefinition.java index e74a6436..dd6a66b0 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/IChannelDefinition.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IChannelDefinition.java @@ -10,6 +10,47 @@ */ public interface IChannelDefinition { + /** + * Creates a channel with a bounded inclusive range and clamping normalization. + * + * @param channel channel identifier + * @param minimumValue inclusive minimum value + * @param maximumValue inclusive maximum value + * @return bounded channel definition + */ + static IChannelDefinition bounded(String channel, int minimumValue, int maximumValue) { + if (minimumValue < 0 || maximumValue < minimumValue) { + throw new IllegalArgumentException("Invalid channel bounds"); + } + return new IChannelDefinition() { + + @Override + public String getChannel() { + return channel; + } + + @Override + public int getMinimumValue() { + return minimumValue; + } + + @Override + public int getMaximumValue() { + return maximumValue; + } + }; + } + + /** + * Creates a channel accepting every non-negative integer. + * + * @param channel channel identifier + * @return unbounded channel definition + */ + static IChannelDefinition unbounded(String channel) { + return () -> channel; + } + /** * Returns the lower-case channel identifier. * diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureDefinition.java b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureDefinition.java index fcd423d6..a2f7a0c6 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureDefinition.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureDefinition.java @@ -10,6 +10,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.Locale; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; @@ -221,25 +222,41 @@ public Builder addElement(char name, IStructureElement structurePiece) { } public Builder addChannel(IChannelDefinition definition) { - ChannelDefinitions.validate(definition); + validateChannelDefinition(definition); channels.put(definition.getChannel(), definition); explicitChannels.add(definition.getChannel()); return this; } public Builder addChannel(String channel, int minimumValue, int maximumValue) { - return addChannel(ChannelDefinitions.bounded(channel, minimumValue, maximumValue)); + return addChannel(IChannelDefinition.bounded(channel, minimumValue, maximumValue)); } private void registerElementChannel(IStructureElement structurePiece) { if (structurePiece instanceof IChannelAwareElement) { - String channel = ((IChannelAwareElement) structurePiece).getChannel(); - if (!explicitChannels.contains(channel)) { - channels.putIfAbsent(channel, ChannelDefinitions.unbounded(channel)); + for (String channel : ((IChannelAwareElement) structurePiece).getChannels()) { + validateChannelName(channel); + if (!explicitChannels.contains(channel)) { + channels.putIfAbsent(channel, IChannelDefinition.unbounded(channel)); + } } } } + private void validateChannelDefinition(IChannelDefinition definition) { + if (definition == null) throw new IllegalArgumentException("Channel definition cannot be null"); + validateChannelName(definition.getChannel()); + if (definition.getMinimumValue() < 0 || definition.getMaximumValue() < definition.getMinimumValue()) { + throw new IllegalArgumentException("Invalid channel definition values"); + } + } + + private void validateChannelName(String channel) { + if (channel == null || channel.isEmpty() || !channel.toLowerCase(Locale.ROOT).equals(channel)) { + throw new IllegalArgumentException("Channel name must be non-empty and lower case"); + } + } + public IStructureDefinition build() { for (IStructureElement element : elements.values()) { registerElementChannel(element); diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java index 1d74047d..492f8b3a 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java @@ -11,6 +11,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -2995,9 +2996,11 @@ public static IStructureElementDeferred partitionBy(Function public static IStructureElement withChannel(String channel, IStructureElement backing) { return new IChannelAwareElement<>() { + private final Collection channels = channelsFor(backing, channel); + @Override - public String getChannel() { - return channel; + public Collection getChannels() { + return channels; } public boolean check(T t, World world, int x, int y, int z) { @@ -3071,6 +3074,15 @@ public List getDescription(T context) { } }; } + + private static Collection channelsFor(IStructureElement backing, String channel) { + Set channels = new LinkedHashSet<>(); + channels.add(channel); + if (backing instanceof IChannelAwareElement) { + channels.addAll(((IChannelAwareElement) backing).getChannels()); + } + return Collections.unmodifiableSet(channels); + } // endregion /** diff --git a/src/main/javadoc/overview.html b/src/main/javadoc/overview.html index 12f6f8c9..38cc53af 100644 --- a/src/main/javadoc/overview.html +++ b/src/main/javadoc/overview.html @@ -186,7 +186,7 @@

Metadata-only channels

IStructureDefinition.<Controller>builder() .addChannel("height", 3, 100) .addChannel("length", 4, 50) - .addChannel(ChannelDefinitions.unbounded("widget")) + .addChannel(IChannelDefinition.unbounded("widget")) .addElement('A', ...) .build(); From 4dc4339f2ed23a6d90c78451394d3fe0c4895ae3 Mon Sep 17 00:00:00 2001 From: ABKQPO <93412322+ABKQPO@users.noreply.github.com> Date: Sun, 11 Oct 2026 11:52:47 +0800 Subject: [PATCH 5/7] Revert minimum channel value modification --- .../constructable/ChannelDataAccessor.java | 4 ++-- .../command/CommandConfigureChannels.java | 13 ++++++++----- .../gui/GuiScreenConfigureChannels.java | 12 ++++++++---- .../structurelib/structure/IChannelDefinition.java | 8 ++++---- .../structurelib/structure/StructureDefinition.java | 2 +- src/main/javadoc/overview.html | 2 +- 6 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ChannelDataAccessor.java b/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ChannelDataAccessor.java index 5ccfe0e1..98e501ab 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ChannelDataAccessor.java +++ b/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ChannelDataAccessor.java @@ -109,11 +109,11 @@ public static int getChannelData(ItemStack masterStack, String channel) { * * @param masterStack trigger stack to check * @param channel channel identifier. Note: all channel identifiers are supposed to be lower case and not empty. - * @param data non-negative subchannel data + * @param data positive subchannel data */ public static void setChannelData(ItemStack masterStack, String channel, int data) { if (StringUtils.isEmpty(channel) || masterStack == null) throw new IllegalArgumentException(); - if (data < 0) throw new IllegalArgumentException("Channel data cannot be negative"); + if (data < 1) throw new IllegalArgumentException("Channel data must be positive"); if (StructureLibAPI.isDebugEnabled() && !channel.toLowerCase(Locale.ROOT).equals(channel)) throw new IllegalArgumentException("Channel name can be lower case ONLY"); if (masterStack.stackTagCompound == null) masterStack.stackTagCompound = new NBTTagCompound(); diff --git a/src/main/java/com/gtnewhorizon/structurelib/command/CommandConfigureChannels.java b/src/main/java/com/gtnewhorizon/structurelib/command/CommandConfigureChannels.java index fdf87347..1fd46618 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/command/CommandConfigureChannels.java +++ b/src/main/java/com/gtnewhorizon/structurelib/command/CommandConfigureChannels.java @@ -26,7 +26,7 @@ public String getCommandName() { public String getCommandUsage(ICommandSender p_71518_1_) { return "/sl_channel \n" + "/sl_channel \n" + "/sl_channel \n" - + " can have space in it. must be a non-negative integer"; + + " can have space in it. Values below 1 clear the channel"; } @Override @@ -108,11 +108,14 @@ public void processCommand(ICommandSender sender, String[] args) { throw new WrongUsageException("%s not a valid value", args[args.length - 1]); } - if (value < 0) throw new WrongUsageException("%s not a valid value", args[args.length - 1]); - - ChannelDataAccessor.setChannelData(heldItem, channel, value); + if (value < 1) { + ChannelDataAccessor.unsetChannelData(heldItem, channel); + } else { + ChannelDataAccessor.setChannelData(heldItem, channel, value); + } player.inventoryContainer.detectAndSendChanges(); - sender.addChatMessage(new ChatComponentText(channel + " value: " + value)); + sender.addChatMessage( + new ChatComponentText(value < 1 ? channel + " no value" : channel + " value: " + value)); break; } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/gui/GuiScreenConfigureChannels.java b/src/main/java/com/gtnewhorizon/structurelib/gui/GuiScreenConfigureChannels.java index 6ceb4408..8fa10748 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/gui/GuiScreenConfigureChannels.java +++ b/src/main/java/com/gtnewhorizon/structurelib/gui/GuiScreenConfigureChannels.java @@ -244,7 +244,7 @@ public void handleMouseInput() { if (delta > 0) { value.setText(String.valueOf(current == Integer.MAX_VALUE ? current : current + 1)); } else { - value.setText(String.valueOf(current == 0 ? 0 : current - 1)); + value.setText(String.valueOf(current == Integer.MIN_VALUE ? current : current - 1)); } } else { list.handleDWheel(delta); @@ -336,7 +336,8 @@ private int getValue() { private static boolean isInteger(String text) { try { - return Integer.parseInt(text) >= 0; + Integer.parseInt(text); + return true; } catch (NumberFormatException e) { return false; } @@ -348,8 +349,11 @@ protected void actionPerformed(GuiButton btn) { switch (btn.id) { case ADD_BTN: int value = getValue(); - if (value < 0) return; - ChannelDataAccessor.setChannelData(trigger, key.getText(), value); + if (value < 1) { + ChannelDataAccessor.unsetChannelData(trigger, key.getText()); + } else { + ChannelDataAccessor.setChannelData(trigger, key.getText(), value); + } break; case UNSET_BTN: ChannelDataAccessor.unsetChannelData(trigger, key.getText()); diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IChannelDefinition.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IChannelDefinition.java index dd6a66b0..bd1aa34a 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/IChannelDefinition.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IChannelDefinition.java @@ -19,7 +19,7 @@ public interface IChannelDefinition { * @return bounded channel definition */ static IChannelDefinition bounded(String channel, int minimumValue, int maximumValue) { - if (minimumValue < 0 || maximumValue < minimumValue) { + if (minimumValue < 1 || maximumValue < minimumValue) { throw new IllegalArgumentException("Invalid channel bounds"); } return new IChannelDefinition() { @@ -42,7 +42,7 @@ public int getMaximumValue() { } /** - * Creates a channel accepting every non-negative integer. + * Creates a channel accepting every positive integer. * * @param channel channel identifier * @return unbounded channel definition @@ -59,12 +59,12 @@ static IChannelDefinition unbounded(String channel) { String getChannel(); /** - * Returns the smallest value advertised by this definition. The default is {@code 0}. + * Returns the smallest value advertised by this definition. The default is {@code 1}. * * @return minimum value */ default int getMinimumValue() { - return 0; + return 1; } /** diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureDefinition.java b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureDefinition.java index a2f7a0c6..a327172f 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureDefinition.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureDefinition.java @@ -246,7 +246,7 @@ private void registerElementChannel(IStructureElement structurePiece) { private void validateChannelDefinition(IChannelDefinition definition) { if (definition == null) throw new IllegalArgumentException("Channel definition cannot be null"); validateChannelName(definition.getChannel()); - if (definition.getMinimumValue() < 0 || definition.getMaximumValue() < definition.getMinimumValue()) { + if (definition.getMinimumValue() < 1 || definition.getMaximumValue() < definition.getMinimumValue()) { throw new IllegalArgumentException("Invalid channel definition values"); } } diff --git a/src/main/javadoc/overview.html b/src/main/javadoc/overview.html index 38cc53af..f50f33ff 100644 --- a/src/main/javadoc/overview.html +++ b/src/main/javadoc/overview.html @@ -145,7 +145,7 @@

Channels

Basically, channels are information stored on the trigger item's NBT tags. One trigger item can carry multiple channels. - Each channel can be identified with lower case strings and has a non-negative integer value. + Each channel can be identified with lower case strings and has the value being one positive Java int. There will be one and only one master channel and 0 or more sub channel. When a non-existent sub channel is queried, master channel will be used instead to fill its place.

From 54817bd3e7206a8eafa7d6c32a6bbf300f4cfc7a Mon Sep 17 00:00:00 2001 From: ABKQPO <93412322+ABKQPO@users.noreply.github.com> Date: Wed, 14 Oct 2026 23:17:15 +0800 Subject: [PATCH 6/7] remove IChannelAwareElement --- .../structure/IChannelAwareElement.java | 18 ------------------ .../structure/IStructureElement.java | 10 ++++++++++ .../structure/StructureDefinition.java | 10 ++++------ .../structure/StructureUtility.java | 6 ++---- 4 files changed, 16 insertions(+), 28 deletions(-) delete mode 100644 src/main/java/com/gtnewhorizon/structurelib/structure/IChannelAwareElement.java diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IChannelAwareElement.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IChannelAwareElement.java deleted file mode 100644 index 37010ecc..00000000 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/IChannelAwareElement.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.gtnewhorizon.structurelib.structure; - -import java.util.Collection; - -/** - * Identifies the channels selected by structure element wrappers. - * - * @param structure context type - */ -public interface IChannelAwareElement extends IStructureElement { - - /** - * Returns the channels used by this element. - * - * @return lower-case channel identifiers - */ - Collection getChannels(); -} diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElement.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElement.java index f3486a44..da8a5ec4 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElement.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElement.java @@ -4,6 +4,7 @@ import static com.gtnewhorizon.structurelib.StructureLib.PANIC_MODE; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.function.Consumer; @@ -33,6 +34,15 @@ public interface IStructureElement { boolean check(T t, World world, int x, int y, int z); + /** + * Returns the channels used by this element. + * + * @return lower-case channel identifiers + */ + default Collection getChannels() { + return Collections.emptySet(); + } + /** * Pure (stateless and side effect free) function to test if current block could be valid. Used to give user hints * about incorrectly placed blocks given a controller and trigger item. If couldBeValid(...) == false for a set of diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureDefinition.java b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureDefinition.java index a327172f..8980ad5f 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureDefinition.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureDefinition.java @@ -233,12 +233,10 @@ public Builder addChannel(String channel, int minimumValue, int maximumValue) } private void registerElementChannel(IStructureElement structurePiece) { - if (structurePiece instanceof IChannelAwareElement) { - for (String channel : ((IChannelAwareElement) structurePiece).getChannels()) { - validateChannelName(channel); - if (!explicitChannels.contains(channel)) { - channels.putIfAbsent(channel, IChannelDefinition.unbounded(channel)); - } + for (String channel : structurePiece.getChannels()) { + validateChannelName(channel); + if (!explicitChannels.contains(channel)) { + channels.putIfAbsent(channel, IChannelDefinition.unbounded(channel)); } } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java index 492f8b3a..039e120a 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java @@ -2994,7 +2994,7 @@ public static IStructureElementDeferred partitionBy(Function * See channels.md in docs folder */ public static IStructureElement withChannel(String channel, IStructureElement backing) { - return new IChannelAwareElement<>() { + return new IStructureElement<>() { private final Collection channels = channelsFor(backing, channel); @@ -3078,9 +3078,7 @@ public List getDescription(T context) { private static Collection channelsFor(IStructureElement backing, String channel) { Set channels = new LinkedHashSet<>(); channels.add(channel); - if (backing instanceof IChannelAwareElement) { - channels.addAll(((IChannelAwareElement) backing).getChannels()); - } + channels.addAll(backing.getChannels()); return Collections.unmodifiableSet(channels); } // endregion From 0b99b210ebe5be154e9740ad6ab2935e57c3c0f0 Mon Sep 17 00:00:00 2001 From: ABKQPO <93412322+ABKQPO@users.noreply.github.com> Date: Wed, 14 Oct 2026 23:17:25 +0800 Subject: [PATCH 7/7] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20GuiScreenConfigureChan?= =?UTF-8?q?nels.java?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gui/GuiScreenConfigureChannels.java | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/gtnewhorizon/structurelib/gui/GuiScreenConfigureChannels.java b/src/main/java/com/gtnewhorizon/structurelib/gui/GuiScreenConfigureChannels.java index 8fa10748..da33f502 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/gui/GuiScreenConfigureChannels.java +++ b/src/main/java/com/gtnewhorizon/structurelib/gui/GuiScreenConfigureChannels.java @@ -137,9 +137,9 @@ public void setFocused(boolean p_146195_1_) { if (!p_146195_1_ && isFocused() && !StringUtils.isBlank(getText())) { int result; try { - result = Math.max(Integer.parseInt(getText()), 0); + result = Math.max(Integer.parseInt(getText()), 1); } catch (NumberFormatException e) { - result = 0; + result = 1; } setText(String.valueOf(result)); } @@ -239,12 +239,10 @@ public void handleMouseInput() { int delta = Mouse.getEventDWheel(); if (delta != 0) { if (isMouseOverValue()) { - if (!isInteger(value.getText())) return; - int current = getValue(); if (delta > 0) { - value.setText(String.valueOf(current == Integer.MAX_VALUE ? current : current + 1)); + value.setText(String.valueOf(getValue() + 1)); } else { - value.setText(String.valueOf(current == Integer.MIN_VALUE ? current : current - 1)); + value.setText(String.valueOf(Math.max(getValue() - 1, 1))); } } else { list.handleDWheel(delta); @@ -284,7 +282,7 @@ protected void keyTyped(char aChar, int aKey) { key.setText(autoCompleteList.getSelectedElement()); key.setFocused(false); value.setFocused(true); - value.setText("0"); + value.setText("1"); value.setSelectionPos(0); return; } @@ -324,22 +322,17 @@ private void updateButtons() { : I18n.format(I18N_PREFIX + "add"); getButtonList().get(ADD_BTN).enabled = !StringUtils.isBlank(key.getText()) && !StringUtils.isBlank(value.getText()) - && isInteger(value.getText()); + && Integer.parseInt(value.getText()) > 0; getButtonList().get(UNSET_BTN).enabled = existing && !StringUtils.isBlank(value.getText()); autoCompleteList.reset(); } private int getValue() { - return Integer.parseInt(value.getText()); - } - - private static boolean isInteger(String text) { try { - Integer.parseInt(text); - return true; + return Integer.parseInt(value.getText()); } catch (NumberFormatException e) { - return false; + return -1; } } @@ -349,11 +342,8 @@ protected void actionPerformed(GuiButton btn) { switch (btn.id) { case ADD_BTN: int value = getValue(); - if (value < 1) { - ChannelDataAccessor.unsetChannelData(trigger, key.getText()); - } else { - ChannelDataAccessor.setChannelData(trigger, key.getText(), value); - } + if (value <= 0) return; + ChannelDataAccessor.setChannelData(trigger, key.getText(), value); break; case UNSET_BTN: ChannelDataAccessor.unsetChannelData(trigger, key.getText());