From 49ef1c225dd2b33024bc9372896a599ef5871630 Mon Sep 17 00:00:00 2001 From: beholderface Date: Sat, 8 Feb 2025 12:59:27 -0500 Subject: [PATCH 01/31] Stop fake players from bloodcasting --- .../hexcasting/api/casting/eval/env/PlayerBasedCastEnv.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/PlayerBasedCastEnv.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/PlayerBasedCastEnv.java index 750a48fa23..edf64f2cbb 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/PlayerBasedCastEnv.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/PlayerBasedCastEnv.java @@ -175,6 +175,10 @@ protected long extractMediaFromInventory(long costLeft, boolean allowOvercast, b } protected boolean canOvercast() { + //bad deployer, no more infinite bloodcasting for you + if (this.caster.getClass() != ServerPlayer.class){ + return false; + } var adv = this.world.getServer().getAdvancements().getAdvancement(modLoc("y_u_no_cast_angy")); var advs = this.caster.getAdvancements(); return advs.getOrStartProgress(adv).isDone(); From 96f5b693641a3fedc1486cccff663d79b32133d5 Mon Sep 17 00:00:00 2001 From: Luxof Date: Fri, 30 Jan 2026 07:34:31 +0500 Subject: [PATCH 02/31] globals my beloved --- .../hexcasting/fabric/FabricHexClientInitializer.kt | 6 +++++- .../at/petrak/hexcasting/fabric/FabricHexInitializer.kt | 7 ++++++- .../hexcasting/forge/ForgeHexClientInitializer.java | 8 ++++++-- .../at/petrak/hexcasting/forge/ForgeHexInitializer.java | 8 ++++++-- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexClientInitializer.kt b/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexClientInitializer.kt index 2ad2ec1d4a..0f1d69f6e0 100644 --- a/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexClientInitializer.kt +++ b/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexClientInitializer.kt @@ -27,6 +27,7 @@ import net.minecraft.world.level.block.entity.BlockEntityType import java.util.function.Function object FabricHexClientInitializer : ClientModInitializer { + private var patternRegistryIsProcessed: Boolean = false override fun onInitializeClient() { FabricPacketHandler.initClient() @@ -41,7 +42,10 @@ object FabricHexClientInitializer : ClientModInitializer { } TooltipComponentCallback.EVENT.register(PatternTooltipComponent::tryConvert) ClientPlayConnectionEvents.JOIN.register { _, _, _ -> - PatternRegistryManifest.processRegistry(null) + if (!patternRegistryIsProcessed) { + PatternRegistryManifest.processRegistry(null) + patternRegistryIsProcessed = true + } } MouseScrollCallback.EVENT.register(ShiftScrollListener::onScrollInGameplay) diff --git a/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexInitializer.kt b/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexInitializer.kt index 19e676136c..28dd71d250 100644 --- a/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexInitializer.kt +++ b/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexInitializer.kt @@ -71,6 +71,7 @@ object FabricHexInitializer : ModInitializer { RegisterMisc.register() } + private var patternRegistryIsProcessed: Boolean = false fun initListeners() { UseEntityCallback.EVENT.register(BrainsweepingEvents::interactWithBrainswept) VillagerConversionCallback.EVENT.register(BrainsweepingEvents::copyBrainsweepPostTransformation) @@ -85,8 +86,12 @@ object FabricHexInitializer : ModInitializer { } } + ServerLifecycleEvents.SERVER_STARTED.register { server -> - PatternRegistryManifest.processRegistry(server.overworld()) + if (!patternRegistryIsProcessed) { + PatternRegistryManifest.processRegistry(server.overworld()) + patternRegistryIsProcessed = true + } } ServerTickEvents.END_WORLD_TICK.register(PlayerPositionRecorder::updateAllPlayers) diff --git a/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexClientInitializer.java b/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexClientInitializer.java index 30e0e2a53e..3dbad22e63 100644 --- a/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexClientInitializer.java +++ b/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexClientInitializer.java @@ -39,6 +39,7 @@ public class ForgeHexClientInitializer { public static ItemColors GLOBAL_ITEM_COLORS; public static BlockColors GLOBAL_BLOCK_COLORS; + private static boolean patternRegistryIsProcessed = false; @SubscribeEvent public static void clientInit(FMLClientSetupEvent evt) { evt.enqueueWork(() -> { @@ -50,8 +51,11 @@ public static void clientInit(FMLClientSetupEvent evt) { var evBus = MinecraftForge.EVENT_BUS; - evBus.addListener((ClientPlayerNetworkEvent.LoggingIn e) -> - PatternRegistryManifest.processRegistry(null)); + evBus.addListener((ClientPlayerNetworkEvent.LoggingIn e) -> { + if (patternRegistryIsProcessed) return; + PatternRegistryManifest.processRegistry(null); + patternRegistryIsProcessed = true; + }); evBus.addListener((RenderLevelStageEvent e) -> { if (e.getStage().equals(RenderLevelStageEvent.Stage.AFTER_PARTICLES)) { diff --git a/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexInitializer.java b/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexInitializer.java index 39bd896580..2226f6d796 100644 --- a/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexInitializer.java +++ b/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexInitializer.java @@ -159,6 +159,7 @@ private static void bind(ResourceKey> registry, }); } + private static boolean patternRegistryIsProcessed = false; private static void initListeners() { var modBus = getModEventBus(); var evBus = MinecraftForge.EVENT_BUS; @@ -225,8 +226,11 @@ private static void initListeners() { } }); - evBus.addListener((ServerStartedEvent evt) -> - PatternRegistryManifest.processRegistry(evt.getServer().overworld())); + evBus.addListener((ServerStartedEvent evt) -> { + if (patternRegistryIsProcessed) return; + PatternRegistryManifest.processRegistry(evt.getServer().overworld()); + patternRegistryIsProcessed = true; + }); evBus.addListener((RegisterCommandsEvent evt) -> HexCommands.register(evt.getDispatcher())); From d25d681150f9f5c000029725a1a3d25465fde1ce Mon Sep 17 00:00:00 2001 From: Luxof Date: Fri, 30 Jan 2026 08:00:24 +0500 Subject: [PATCH 03/31] ah right --- .../petrak/hexcasting/fabric/FabricHexClientInitializer.kt | 4 +++- .../java/at/petrak/hexcasting/fabric/FabricHexInitializer.kt | 4 +++- .../petrak/hexcasting/forge/ForgeHexClientInitializer.java | 5 ++--- .../java/at/petrak/hexcasting/forge/ForgeHexInitializer.java | 4 +++- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexClientInitializer.kt b/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexClientInitializer.kt index 0f1d69f6e0..65fbd55ad2 100644 --- a/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexClientInitializer.kt +++ b/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexClientInitializer.kt @@ -1,5 +1,7 @@ package at.petrak.hexcasting.fabric +import at.petrak.hexcasting.api.HexAPI + import at.petrak.hexcasting.client.ClientTickCounter import at.petrak.hexcasting.client.RegisterClientStuff import at.petrak.hexcasting.client.ShiftScrollListener @@ -42,7 +44,7 @@ object FabricHexClientInitializer : ClientModInitializer { } TooltipComponentCallback.EVENT.register(PatternTooltipComponent::tryConvert) ClientPlayConnectionEvents.JOIN.register { _, _, _ -> - if (!patternRegistryIsProcessed) { + if (patternRegistryIsProcessed) { PatternRegistryManifest.processRegistry(null) patternRegistryIsProcessed = true } diff --git a/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexInitializer.kt b/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexInitializer.kt index 28dd71d250..9dd280c0cf 100644 --- a/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexInitializer.kt +++ b/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexInitializer.kt @@ -71,7 +71,9 @@ object FabricHexInitializer : ModInitializer { RegisterMisc.register() } - private var patternRegistryIsProcessed: Boolean = false + // this global is for both server and client because PatternRegistryManifest.processRegistry + // doesn't have separate processing for each + internal var patternRegistryIsProcessed: Boolean = false fun initListeners() { UseEntityCallback.EVENT.register(BrainsweepingEvents::interactWithBrainswept) VillagerConversionCallback.EVENT.register(BrainsweepingEvents::copyBrainsweepPostTransformation) diff --git a/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexClientInitializer.java b/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexClientInitializer.java index 3dbad22e63..da195a217f 100644 --- a/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexClientInitializer.java +++ b/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexClientInitializer.java @@ -39,7 +39,6 @@ public class ForgeHexClientInitializer { public static ItemColors GLOBAL_ITEM_COLORS; public static BlockColors GLOBAL_BLOCK_COLORS; - private static boolean patternRegistryIsProcessed = false; @SubscribeEvent public static void clientInit(FMLClientSetupEvent evt) { evt.enqueueWork(() -> { @@ -52,9 +51,9 @@ public static void clientInit(FMLClientSetupEvent evt) { var evBus = MinecraftForge.EVENT_BUS; evBus.addListener((ClientPlayerNetworkEvent.LoggingIn e) -> { - if (patternRegistryIsProcessed) return; + if (ForgeHexInitializer.patternRegistryIsProcessed) return; PatternRegistryManifest.processRegistry(null); - patternRegistryIsProcessed = true; + ForgeHexInitializer.patternRegistryIsProcessed = true; }); evBus.addListener((RenderLevelStageEvent e) -> { diff --git a/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexInitializer.java b/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexInitializer.java index 2226f6d796..638f573d22 100644 --- a/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexInitializer.java +++ b/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexInitializer.java @@ -159,7 +159,9 @@ private static void bind(ResourceKey> registry, }); } - private static boolean patternRegistryIsProcessed = false; + // this global is for both server and client because PatternRegistryManifest.processRegistry + // doesn't have separate processing for each + protected static boolean patternRegistryIsProcessed = false; private static void initListeners() { var modBus = getModEventBus(); var evBus = MinecraftForge.EVENT_BUS; From 06079811c8fac55efddcf917298872ecbeffcd26 Mon Sep 17 00:00:00 2001 From: Luxof Date: Fri, 30 Jan 2026 08:11:40 +0500 Subject: [PATCH 04/31] make the log msg make sense --- .../hexcasting/common/casting/PatternRegistryManifest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/PatternRegistryManifest.java b/Common/src/main/java/at/petrak/hexcasting/common/casting/PatternRegistryManifest.java index 5d15f8fe42..bf659f7218 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/PatternRegistryManifest.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/PatternRegistryManifest.java @@ -57,9 +57,9 @@ public static void processRegistry(@Nullable ServerLevel overworld) { } } - HexAPI.LOGGER.info(("We're on the %s! " + + HexAPI.LOGGER.info(("We've loaded the pattern registry! " + "Loaded %d regular actions, %d per-world actions, and %d special handlers").formatted( - (overworld == null) ? "client" : "server", NORMAL_ACTION_LOOKUP.size(), perWorldActionCount, + NORMAL_ACTION_LOOKUP.size(), perWorldActionCount, IXplatAbstractions.INSTANCE.getSpecialHandlerRegistry().size() )); } From 5b3bb0ad755ad0813d1358f704637d0cb31f6469 Mon Sep 17 00:00:00 2001 From: Luxof Date: Fri, 30 Jan 2026 08:20:33 +0500 Subject: [PATCH 05/31] re-add the server or client mention --- .../hexcasting/common/casting/PatternRegistryManifest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/PatternRegistryManifest.java b/Common/src/main/java/at/petrak/hexcasting/common/casting/PatternRegistryManifest.java index bf659f7218..f94921b120 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/PatternRegistryManifest.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/PatternRegistryManifest.java @@ -57,9 +57,9 @@ public static void processRegistry(@Nullable ServerLevel overworld) { } } - HexAPI.LOGGER.info(("We've loaded the pattern registry! " + + HexAPI.LOGGER.info(("We've loaded the pattern registry on the %s first! " + "Loaded %d regular actions, %d per-world actions, and %d special handlers").formatted( - NORMAL_ACTION_LOOKUP.size(), perWorldActionCount, + (overworld == null) ? "client" : "server", NORMAL_ACTION_LOOKUP.size(), perWorldActionCount, IXplatAbstractions.INSTANCE.getSpecialHandlerRegistry().size() )); } From fe4813085ec6cc86452fb3c484ea5eba10cc2dfd Mon Sep 17 00:00:00 2001 From: beholderface <13290805+beholderface@users.noreply.github.com> Date: Wed, 18 Feb 2026 09:02:32 -0500 Subject: [PATCH 06/31] deprecate the original matchpattern --- .../at/petrak/hexcasting/api/casting/iota/PatternIota.java | 2 +- .../hexcasting/common/casting/PatternRegistryManifest.java | 5 +++++ .../petrak/hexcasting/interop/inline/InlinePatternData.java | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/PatternIota.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/PatternIota.java index 74db0a95fb..097fae52ae 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/PatternIota.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/PatternIota.java @@ -71,7 +71,7 @@ public boolean toleratesOther(Iota that) { public @NotNull CastResult execute(CastingVM vm, ServerLevel world, SpellContinuation continuation) { Supplier<@Nullable Component> castedName = () -> null; try { - var lookup = PatternRegistryManifest.matchPattern(this.getPattern(), vm.getEnv(), false); + var lookup = PatternRegistryManifest.matchPattern(this.getPattern(), vm.getEnv()); vm.getEnv().precheckAction(lookup); Action action; diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/PatternRegistryManifest.java b/Common/src/main/java/at/petrak/hexcasting/common/casting/PatternRegistryManifest.java index 5d15f8fe42..2aad053352 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/PatternRegistryManifest.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/PatternRegistryManifest.java @@ -90,6 +90,7 @@ public static Pair>> match * order * for a per-world pattern. */ + @Deprecated public static PatternShapeMatch matchPattern(HexPattern pat, CastingEnvironment environment, boolean checkForAlternateStrokeOrders) { // I am PURPOSELY checking normal actions before special handlers @@ -119,6 +120,10 @@ public static PatternShapeMatch matchPattern(HexPattern pat, CastingEnvironment return new PatternShapeMatch.Nothing(); } + public static PatternShapeMatch matchPattern(HexPattern pattern, CastingEnvironment environment){ + return matchPattern(pattern, environment, false); + } + @Nullable public static HexPattern getCanonicalStrokesPerWorld(ResourceKey key, ServerLevel overworld) { var perWorldPatterns = ScrungledPatternsSave.open(overworld); diff --git a/Common/src/main/java/at/petrak/hexcasting/interop/inline/InlinePatternData.java b/Common/src/main/java/at/petrak/hexcasting/interop/inline/InlinePatternData.java index 5dc2dba2ff..e19bb21571 100644 --- a/Common/src/main/java/at/petrak/hexcasting/interop/inline/InlinePatternData.java +++ b/Common/src/main/java/at/petrak/hexcasting/interop/inline/InlinePatternData.java @@ -50,7 +50,7 @@ public Style getExtraStyle() { public static Component getPatternName(HexPattern pattern){ try { - PatternShapeMatch shapeMatch = PatternRegistryManifest.matchPattern(pattern, null, false); + PatternShapeMatch shapeMatch = PatternRegistryManifest.matchPattern(pattern, null); if(shapeMatch instanceof PatternShapeMatch.Normal normMatch){ return HexAPI.instance().getActionI18n(normMatch.key, false); } From 223f8467f946dc857b2db8ae5321d88966b70fd3 Mon Sep 17 00:00:00 2001 From: beholderface <13290805+beholderface@users.noreply.github.com> Date: Thu, 19 Feb 2026 10:01:19 -0500 Subject: [PATCH 07/31] address pr comments --- .../api/casting/eval/env/PlayerBasedCastEnv.java | 4 ---- .../common/casting/PatternRegistryManifest.java | 8 +++++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/PlayerBasedCastEnv.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/PlayerBasedCastEnv.java index f411cd8c2a..1f5b6703ff 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/PlayerBasedCastEnv.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/PlayerBasedCastEnv.java @@ -192,10 +192,6 @@ protected long extractMediaFromInventory(long costLeft, boolean allowOvercast, b } protected boolean canOvercast() { - //bad deployer, no more infinite bloodcasting for you - if (this.caster.getClass() != ServerPlayer.class){ - return false; - } var adv = this.world.getServer().getAdvancements().getAdvancement(modLoc("y_u_no_cast_angy")); var advs = this.caster.getAdvancements(); return advs.getOrStartProgress(adv).isDone(); diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/PatternRegistryManifest.java b/Common/src/main/java/at/petrak/hexcasting/common/casting/PatternRegistryManifest.java index 2aad053352..b297bdc5e7 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/PatternRegistryManifest.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/PatternRegistryManifest.java @@ -86,9 +86,7 @@ public static Pair>> match * Try to match this pattern to an action, whether via a normal pattern, a per-world pattern, or the machinations * of a special handler. * - * @param checkForAlternateStrokeOrders if this is true, will check if the pattern given is an erroneous stroke - * order - * for a per-world pattern. + * @deprecated Giving this method a true will cause it to throw if it cannot find a normal or per-world pattern match. Use the bool-less overload instead. */ @Deprecated public static PatternShapeMatch matchPattern(HexPattern pat, CastingEnvironment environment, @@ -120,6 +118,10 @@ public static PatternShapeMatch matchPattern(HexPattern pat, CastingEnvironment return new PatternShapeMatch.Nothing(); } + /** + * Try to match this pattern to an action, whether via a normal pattern, a per-world pattern, or the machinations + * of a special handler. Will never attempt to check alternate stroke orders, which always throws. + */ public static PatternShapeMatch matchPattern(HexPattern pattern, CastingEnvironment environment){ return matchPattern(pattern, environment, false); } From d2efc75410752183c2ea6a919632d6729a105895 Mon Sep 17 00:00:00 2001 From: beholderface <13290805+beholderface@users.noreply.github.com> Date: Fri, 20 Feb 2026 11:00:31 -0500 Subject: [PATCH 08/31] since --- .../hexcasting/common/casting/PatternRegistryManifest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/PatternRegistryManifest.java b/Common/src/main/java/at/petrak/hexcasting/common/casting/PatternRegistryManifest.java index b297bdc5e7..7897213981 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/PatternRegistryManifest.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/PatternRegistryManifest.java @@ -88,7 +88,7 @@ public static Pair>> match * * @deprecated Giving this method a true will cause it to throw if it cannot find a normal or per-world pattern match. Use the bool-less overload instead. */ - @Deprecated + @Deprecated(since = "0.11.4") public static PatternShapeMatch matchPattern(HexPattern pat, CastingEnvironment environment, boolean checkForAlternateStrokeOrders) { // I am PURPOSELY checking normal actions before special handlers From bb14c13c587b2900b5004a6454cea4b510d75f7c Mon Sep 17 00:00:00 2001 From: Teal Wolf 25 Date: Tue, 10 Mar 2026 22:11:09 -0400 Subject: [PATCH 09/31] Fix Shepherd iota not bool mishap translation key --- .../api/casting/mishaps/circle/MishapBoolDirectrixNotBool.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/circle/MishapBoolDirectrixNotBool.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/circle/MishapBoolDirectrixNotBool.kt index 7c3d7b1fb5..e4975f59a8 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/circle/MishapBoolDirectrixNotBool.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/circle/MishapBoolDirectrixNotBool.kt @@ -21,5 +21,5 @@ class MishapBoolDirectrixNotBool( } override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context): Component = - error("circle.bool_directrix_no_bool", pos.toShortString(), perpetrator.display()) + error("circle.bool_directrix.no_bool", pos.toShortString(), perpetrator.display()) } \ No newline at end of file From 557bd1030ab86b6956f71681f6b0f931e3a06e2a Mon Sep 17 00:00:00 2001 From: aloaloolaola <62200152+aloaloolaola@users.noreply.github.com> Date: Fri, 27 Mar 2026 20:07:16 +0200 Subject: [PATCH 10/31] added option to disable wobblyness of patterns on animated slates --- .../java/at/petrak/hexcasting/api/mod/HexConfig.java | 3 +++ .../client/render/WorldlyPatternRenderHelpers.java | 9 +++++++-- .../resources/assets/hexcasting/lang/en_us.flatten.json5 | 4 ++++ .../at/petrak/hexcasting/fabric/FabricHexConfig.java | 5 +++++ .../java/at/petrak/hexcasting/forge/ForgeHexConfig.java | 7 +++++++ 5 files changed, 26 insertions(+), 2 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/mod/HexConfig.java b/Common/src/main/java/at/petrak/hexcasting/api/mod/HexConfig.java index aa4859f5ed..787b25b8d1 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/mod/HexConfig.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/mod/HexConfig.java @@ -51,12 +51,15 @@ public interface ClientConfigAccess { boolean alwaysShowListCommas(); + boolean staticActiveSlates(); + boolean DEFAULT_CTRL_TOGGLES_OFF_STROKE_ORDER = false; boolean DEFAULT_INVERT_SPELLBOOK_SCROLL = false; boolean DEFAULT_INVERT_ABACUS_SCROLL = false; double DEFAULT_GRID_SNAP_THRESHOLD = 0.5; boolean DEFAULT_CLICKING_TOGGLES_DRAWING = false; boolean DEFAULT_ALWAYS_SHOW_LIST_COMMAS = false; + boolean DEFAULT_STATIC_ACTIVE_SLATES = false; } public interface ServerConfigAccess { diff --git a/Common/src/main/java/at/petrak/hexcasting/client/render/WorldlyPatternRenderHelpers.java b/Common/src/main/java/at/petrak/hexcasting/client/render/WorldlyPatternRenderHelpers.java index 68c00d5d92..69229d26ce 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/render/WorldlyPatternRenderHelpers.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/WorldlyPatternRenderHelpers.java @@ -1,6 +1,7 @@ package at.petrak.hexcasting.client.render; import at.petrak.hexcasting.api.casting.math.HexPattern; +import at.petrak.hexcasting.api.mod.HexConfig; import at.petrak.hexcasting.common.blocks.akashic.BlockAkashicBookshelf; import at.petrak.hexcasting.common.blocks.akashic.BlockEntityAkashicBookshelf; import at.petrak.hexcasting.common.blocks.circles.BlockEntitySlate; @@ -71,7 +72,11 @@ public static void renderPatternForSlate(BlockEntitySlate tile, HexPattern patte boolean isOnCeiling = bs.getValue(BlockSlate.ATTACH_FACE) == AttachFace.CEILING; int facing = bs.getValue(BlockSlate.FACING).get2DDataValue(); - boolean wombly = bs.getValue(BlockSlate.ENERGIZED); + boolean energized = bs.getValue(BlockSlate.ENERGIZED); + boolean wombly = energized; + if (HexConfig.client().staticActiveSlates()) { + wombly = false; + } ps.pushPose(); @@ -93,7 +98,7 @@ public static void renderPatternForSlate(BlockEntitySlate tile, HexPattern patte renderPattern(pattern, wombly ? WORLDLY_SETTINGS_WOBBLY : WORLDLY_SETTINGS, - wombly ? PatternColors.SLATE_WOBBLY_PURPLE_COLOR : PatternColors.DEFAULT_PATTERN_COLOR, + energized ? PatternColors.SLATE_WOBBLY_PURPLE_COLOR : PatternColors.DEFAULT_PATTERN_COLOR, tile.getBlockPos().hashCode(), ps, buffer, normal, null, light, 1); ps.popPose(); } diff --git a/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 b/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 index 5a0087c10f..8229d7bcb2 100644 --- a/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 +++ b/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 @@ -355,6 +355,10 @@ "": "Always Show List Commas", "@Tooltip": "Whether all iota types should be comma-separated when displayed in lists (by default, pattern iotas do not use commas)", }, + staticActiveSlates: { + "": "Static patterned slates", + "@Tooltip": "Whether patterns on slates should always be rendered non-moving", + } }, server: { diff --git a/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexConfig.java b/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexConfig.java index 6ab5d09ff4..22d4fd9018 100644 --- a/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexConfig.java +++ b/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexConfig.java @@ -138,6 +138,8 @@ public static final class Client implements HexConfig.ClientConfigAccess, Config private boolean clickingTogglesDrawing = DEFAULT_CLICKING_TOGGLES_DRAWING; @ConfigEntry.Gui.Tooltip private boolean alwaysShowListCommas = DEFAULT_ALWAYS_SHOW_LIST_COMMAS; + @ConfigEntry.Gui.Tooltip + private boolean staticActiveSlates = DEFAULT_STATIC_ACTIVE_SLATES; @Override public void validatePostLoad() throws ValidationException { @@ -173,6 +175,9 @@ public boolean clickingTogglesDrawing() { public boolean alwaysShowListCommas() { return alwaysShowListCommas; } + + @Override + public boolean staticActiveSlates() { return staticActiveSlates; } } @Config(name = "server") diff --git a/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java b/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java index dd3eb257e3..459629c6f2 100644 --- a/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java +++ b/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java @@ -85,6 +85,7 @@ public static class Client implements HexConfig.ClientConfigAccess { private static ForgeConfigSpec.DoubleValue gridSnapThreshold; private static ForgeConfigSpec.BooleanValue clickingTogglesDrawing; private static ForgeConfigSpec.BooleanValue alwaysShowListCommas; + private static ForgeConfigSpec.BooleanValue staticActiveSlates; public Client(ForgeConfigSpec.Builder builder) { ctrlTogglesOffStrokeOrder = builder.comment( @@ -107,6 +108,9 @@ public Client(ForgeConfigSpec.Builder builder) { alwaysShowListCommas = builder.comment( "Whether all iota types should be comma-separated in lists (by default, pattern iotas don't use commas)") .define("alwaysShowListCommas", DEFAULT_ALWAYS_SHOW_LIST_COMMAS); + staticActiveSlates = builder.comment( + "Whether patterns on slates should always be rendered non-moving") + .define("staticActiveSlates", DEFAULT_STATIC_ACTIVE_SLATES); } @Override @@ -138,6 +142,9 @@ public boolean clickingTogglesDrawing() { public boolean alwaysShowListCommas() { return alwaysShowListCommas.get(); } + + @Override + public boolean staticActiveSlates() { return staticActiveSlates.get(); } } public static class Server implements HexConfig.ServerConfigAccess { From 2fe4abfc30abeb88a231540a36987d1a26dc57d3 Mon Sep 17 00:00:00 2001 From: pipythonmc <47196755+pythonmcpi@users.noreply.github.com> Date: Sun, 12 Apr 2026 15:36:25 -0700 Subject: [PATCH 11/31] Add optional keybinds for changing spellbook pages --- .../at/petrak/hexcasting/client/Keybinds.java | 34 +++++++++++++++++++ .../hexcasting/client/gui/GuiSpellcasting.kt | 15 ++++++++ .../hexcasting/lang/en_us.flatten.json5 | 9 +++++ .../fabric/FabricHexClientInitializer.kt | 5 +++ .../forge/ForgeHexClientInitializer.java | 8 +++++ 5 files changed, 71 insertions(+) create mode 100644 Common/src/main/java/at/petrak/hexcasting/client/Keybinds.java diff --git a/Common/src/main/java/at/petrak/hexcasting/client/Keybinds.java b/Common/src/main/java/at/petrak/hexcasting/client/Keybinds.java new file mode 100644 index 0000000000..a0b85e3e1a --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/client/Keybinds.java @@ -0,0 +1,34 @@ +package at.petrak.hexcasting.client; + +import com.mojang.blaze3d.platform.InputConstants; +import net.minecraft.client.KeyMapping; + +import java.util.List; + +public class Keybinds { + public static final String CATEGORY = "category.hexcasting.binds"; + + public static KeyMapping spellbookPrev = new KeyMapping( + "key.hexcasting.spellbook_prev", + InputConstants.UNKNOWN.getValue(), + CATEGORY + ); + + public static KeyMapping spellbookNext = new KeyMapping( + "key.hexcasting.spellbook_next", + InputConstants.UNKNOWN.getValue(), + CATEGORY + ); + + public static List ALL_BINDS = List.of(spellbookPrev, spellbookNext); + + public static void clientTickEnd() { + while (spellbookPrev.consumeClick()) { + ShiftScrollListener.onScroll(-1, false); + } + + while (spellbookNext.consumeClick()) { + ShiftScrollListener.onScroll(1, false); + } + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt index df0c5acb60..09c036ab3b 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt +++ b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt @@ -12,6 +12,7 @@ import at.petrak.hexcasting.api.mod.HexConfig import at.petrak.hexcasting.api.mod.HexTags import at.petrak.hexcasting.api.utils.asTranslatedComponent import at.petrak.hexcasting.client.ClientTickCounter +import at.petrak.hexcasting.client.Keybinds import at.petrak.hexcasting.client.ShiftScrollListener import at.petrak.hexcasting.client.ktxt.accumulatedScroll import at.petrak.hexcasting.client.render.* @@ -313,6 +314,20 @@ class GuiSpellcasting constructor( return true } + override fun keyPressed(key: Int, scancode: Int, modifiers: Int): Boolean { + if (super.keyPressed(key, scancode, modifiers)) return true + + if (Keybinds.spellbookPrev.matches(key, scancode)) { + ShiftScrollListener.onScroll(-1.0, false) + return true + } else if (Keybinds.spellbookNext.matches(key, scancode)) { + ShiftScrollListener.onScroll(1.0, false) + return true + } + + return false + } + override fun onClose() { if (drawState == PatternDrawState.BetweenPatterns) closeForReal() diff --git a/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 b/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 index 5a0087c10f..a74a463978 100644 --- a/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 +++ b/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 @@ -265,6 +265,15 @@ "gui.hexcasting": { spellcasting: "Hex Grid", }, + + "category.hexcasting": { + "binds": "Hex Casting", + }, + + "key.hexcasting": { + "spellbook_prev": "Previous Spellbook Page", + "spellbook_next": "Next Spellbook Page", + }, "tag.hexcasting": { staves: "Hex Staves", diff --git a/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexClientInitializer.kt b/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexClientInitializer.kt index 2ad2ec1d4a..ff7ce329b5 100644 --- a/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexClientInitializer.kt +++ b/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexClientInitializer.kt @@ -1,6 +1,7 @@ package at.petrak.hexcasting.fabric import at.petrak.hexcasting.client.ClientTickCounter +import at.petrak.hexcasting.client.Keybinds import at.petrak.hexcasting.client.RegisterClientStuff import at.petrak.hexcasting.client.ShiftScrollListener import at.petrak.hexcasting.client.gui.PatternTooltipComponent @@ -13,6 +14,7 @@ import at.petrak.hexcasting.fabric.network.FabricPacketHandler import at.petrak.hexcasting.interop.HexInterop import net.fabricmc.api.ClientModInitializer import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper import net.fabricmc.fabric.api.client.model.ModelLoadingRegistry import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents import net.fabricmc.fabric.api.client.particle.v1.ParticleFactoryRegistry @@ -37,6 +39,7 @@ object FabricHexClientInitializer : ClientModInitializer { WorldRenderEvents.START.register { ClientTickCounter.renderTickStart(it.tickDelta()) } ClientTickEvents.END_CLIENT_TICK.register { ClientTickCounter.clientTickEnd() + Keybinds.clientTickEnd() ShiftScrollListener.clientTickEnd() } TooltipComponentCallback.EVENT.register(PatternTooltipComponent::tryConvert) @@ -46,6 +49,8 @@ object FabricHexClientInitializer : ClientModInitializer { MouseScrollCallback.EVENT.register(ShiftScrollListener::onScrollInGameplay) + Keybinds.ALL_BINDS.forEach(KeyBindingHelper::registerKeyBinding) + RegisterClientStuff.init() HexModelLayers.init { loc, defn -> EntityModelLayerRegistry.registerModelLayer(loc, defn::get) } diff --git a/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexClientInitializer.java b/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexClientInitializer.java index 30e0e2a53e..10d3e82081 100644 --- a/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexClientInitializer.java +++ b/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexClientInitializer.java @@ -1,6 +1,7 @@ package at.petrak.hexcasting.forge; import at.petrak.hexcasting.client.ClientTickCounter; +import at.petrak.hexcasting.client.Keybinds; import at.petrak.hexcasting.client.RegisterClientStuff; import at.petrak.hexcasting.client.ShiftScrollListener; import at.petrak.hexcasting.client.gui.PatternTooltipComponent; @@ -28,6 +29,7 @@ import net.minecraftforge.event.TickEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import java.io.IOException; import java.util.function.Function; @@ -73,6 +75,7 @@ public static void clientInit(FMLClientSetupEvent evt) { evBus.addListener((TickEvent.ClientTickEvent e) -> { if (e.phase == TickEvent.Phase.END) { ClientTickCounter.clientTickEnd(); + Keybinds.clientTickEnd(); ShiftScrollListener.clientTickEnd(); } }); @@ -136,4 +139,9 @@ public static void addPlayerLayers(EntityRenderersEvent.AddLayers evt) { skin.addLayer(new AltioraLayer<>(skin, evt.getEntityModels())); }); } + + @SubscribeEvent + public static void registerBinds(RegisterKeyMappingsEvent event) { + Keybinds.ALL_BINDS.forEach(event::register); + } } From dbadca5d0296857f76849afbd41e4da28253bfb6 Mon Sep 17 00:00:00 2001 From: pipythonmc <47196755+pythonmcpi@users.noreply.github.com> Date: Sun, 12 Apr 2026 15:57:34 -0700 Subject: [PATCH 12/31] Add client config setting to disable scrolling spellbooks and abaci in-world --- .../java/at/petrak/hexcasting/api/mod/HexConfig.java | 3 +++ .../petrak/hexcasting/client/ShiftScrollListener.java | 2 ++ .../assets/hexcasting/lang/en_us.flatten.json5 | 6 +++++- .../at/petrak/hexcasting/fabric/FabricHexConfig.java | 10 ++++++++-- .../at/petrak/hexcasting/forge/ForgeHexConfig.java | 10 +++++++++- 5 files changed, 27 insertions(+), 4 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/mod/HexConfig.java b/Common/src/main/java/at/petrak/hexcasting/api/mod/HexConfig.java index aa4859f5ed..c1b1a91579 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/mod/HexConfig.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/mod/HexConfig.java @@ -41,6 +41,8 @@ public interface CommonConfigAccess { public interface ClientConfigAccess { boolean ctrlTogglesOffStrokeOrder(); + boolean disableInworldScrolling(); + boolean invertSpellbookScrollDirection(); boolean invertAbacusScrollDirection(); @@ -52,6 +54,7 @@ public interface ClientConfigAccess { boolean alwaysShowListCommas(); boolean DEFAULT_CTRL_TOGGLES_OFF_STROKE_ORDER = false; + boolean DEFAULT_DISABLE_INWORLD_SCROLLING = false; boolean DEFAULT_INVERT_SPELLBOOK_SCROLL = false; boolean DEFAULT_INVERT_ABACUS_SCROLL = false; double DEFAULT_GRID_SNAP_THRESHOLD = 0.5; diff --git a/Common/src/main/java/at/petrak/hexcasting/client/ShiftScrollListener.java b/Common/src/main/java/at/petrak/hexcasting/client/ShiftScrollListener.java index c6073517cc..45fc1d7d78 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/ShiftScrollListener.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/ShiftScrollListener.java @@ -17,6 +17,8 @@ public static boolean onScrollInGameplay(double delta) { return false; } + if (HexConfig.client().disableInworldScrolling()) return false; + return onScroll(delta, true); } diff --git a/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 b/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 index a74a463978..b729f60d26 100644 --- a/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 +++ b/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 @@ -344,13 +344,17 @@ "": "Ctrl Toggles Off Stroke Order", "@Tooltip": "Whether the ctrl key will instead turn *off* the color gradient on patterns", }, + disableInworldScrolling: { + "": "Disable In-World Scrolling", + "@Tooltip": "Disable scrolling input for spellbooks and abaci in the normal world, keeping keybinds and staff screen scrolling normal" + }, invertSpellbookScrollDirection: { "": "Invert Spellbook Scroll Direction", "@Tooltip": "Whether scrolling up (as opposed to down) will increase the page index of the spellbook, and vice versa", }, invertAbacusScrollDirection: { "": "Invert Abacus Scroll Direction", - "@Tooltip": "Whether scrolling up (as opposed to down) will increase the page index of the abacus, and vice versa", + "@Tooltip": "Whether scrolling up (as opposed to down) will increase the value of the abacus, and vice versa", }, gridSnapThreshold: { "": "Grid Snap Threshold", diff --git a/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexConfig.java b/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexConfig.java index 6ab5d09ff4..3503f5dc12 100644 --- a/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexConfig.java +++ b/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexConfig.java @@ -15,7 +15,6 @@ import me.shedaniel.autoconfig.serializer.PartitioningSerializer; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.RandomSource; import net.minecraft.util.Mth; import net.minecraft.world.level.Level; @@ -129,9 +128,11 @@ public static final class Client implements HexConfig.ClientConfigAccess, Config @ConfigEntry.Gui.Tooltip private boolean ctrlTogglesOffStrokeOrder = DEFAULT_CTRL_TOGGLES_OFF_STROKE_ORDER; @ConfigEntry.Gui.Tooltip + private boolean disableInworldScrolling = DEFAULT_DISABLE_INWORLD_SCROLLING; + @ConfigEntry.Gui.Tooltip private boolean invertSpellbookScrollDirection = DEFAULT_INVERT_SPELLBOOK_SCROLL; @ConfigEntry.Gui.Tooltip - private boolean invertAbacusScrollDirection = DEFAULT_INVERT_SPELLBOOK_SCROLL; + private boolean invertAbacusScrollDirection = DEFAULT_INVERT_ABACUS_SCROLL; @ConfigEntry.Gui.Tooltip private double gridSnapThreshold = DEFAULT_GRID_SNAP_THRESHOLD; @ConfigEntry.Gui.Tooltip @@ -149,6 +150,11 @@ public boolean ctrlTogglesOffStrokeOrder() { return ctrlTogglesOffStrokeOrder; } + @Override + public boolean disableInworldScrolling() { + return disableInworldScrolling; + } + @Override public boolean invertSpellbookScrollDirection() { return invertSpellbookScrollDirection; diff --git a/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java b/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java index dd3eb257e3..99cb73243a 100644 --- a/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java +++ b/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java @@ -5,7 +5,6 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.Level; import net.minecraftforge.common.ForgeConfigSpec; -import net.minecraft.util.RandomSource; import java.util.List; @@ -80,6 +79,7 @@ public int artifactCooldown() { public static class Client implements HexConfig.ClientConfigAccess { private static ForgeConfigSpec.BooleanValue ctrlTogglesOffStrokeOrder; + private static ForgeConfigSpec.BooleanValue disableInworldScrolling; private static ForgeConfigSpec.BooleanValue invertSpellbookScrollDirection; private static ForgeConfigSpec.BooleanValue invertAbacusScrollDirection; private static ForgeConfigSpec.DoubleValue gridSnapThreshold; @@ -90,6 +90,9 @@ public Client(ForgeConfigSpec.Builder builder) { ctrlTogglesOffStrokeOrder = builder.comment( "Whether the ctrl key will instead turn *off* the color gradient on patterns") .define("ctrlTogglesOffStrokeOrder", DEFAULT_CTRL_TOGGLES_OFF_STROKE_ORDER); + disableInworldScrolling = builder.comment( + "Disable scrolling input for spellbooks and abaci in the normal world, keeping keybinds and staff screen scrolling normal") + .define("disableInworldScrolling", DEFAULT_DISABLE_INWORLD_SCROLLING); invertSpellbookScrollDirection = builder.comment( "Whether scrolling up (as opposed to down) will increase the page index of the spellbook, and " + "vice versa") @@ -109,6 +112,11 @@ public Client(ForgeConfigSpec.Builder builder) { .define("alwaysShowListCommas", DEFAULT_ALWAYS_SHOW_LIST_COMMAS); } + @Override + public boolean disableInworldScrolling() { + return disableInworldScrolling.get(); + } + @Override public boolean invertSpellbookScrollDirection() { return invertSpellbookScrollDirection.get(); From ffb8de1f52604b4726e8c382105cf69eca5a45c2 Mon Sep 17 00:00:00 2001 From: pipythonmc <47196755+pythonmcpi@users.noreply.github.com> Date: Sun, 12 Apr 2026 20:40:20 -0700 Subject: [PATCH 13/31] Fix OpCreateFluid's check to return the correct message when position is out of ambit --- .../casting/actions/spells/OpCreateFluid.kt | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/OpCreateFluid.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/OpCreateFluid.kt index 46c0662619..6278ab1d76 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/OpCreateFluid.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/spells/OpCreateFluid.kt @@ -4,9 +4,8 @@ import at.petrak.hexcasting.api.casting.ParticleSpray import at.petrak.hexcasting.api.casting.RenderedSpell import at.petrak.hexcasting.api.casting.castables.SpellAction import at.petrak.hexcasting.api.casting.eval.CastingEnvironment -import at.petrak.hexcasting.api.casting.getVec3 +import at.petrak.hexcasting.api.casting.getBlockPos import at.petrak.hexcasting.api.casting.iota.Iota -import at.petrak.hexcasting.api.casting.mishaps.MishapBadLocation import at.petrak.hexcasting.xplat.IXplatAbstractions import net.minecraft.core.BlockPos import net.minecraft.server.level.ServerPlayer @@ -24,17 +23,8 @@ class OpCreateFluid(val cost: Long, val bucket: Item, val cauldron: BlockState, args: List, env: CastingEnvironment ): SpellAction.Result { - val vecPos = args.getVec3(0, argc) - val pos = BlockPos.containing(vecPos) - - if (!env.canEditBlockAt(pos) || !IXplatAbstractions.INSTANCE.isPlacingAllowed( - env.world, - pos, - ItemStack(bucket), - env.castingEntity as? ServerPlayer - ) - ) - throw MishapBadLocation(vecPos, "forbidden") + val pos = args.getBlockPos(0, argc) + env.assertPosInRangeForEditing(pos) return SpellAction.Result( Spell(pos, bucket, cauldron, fluid), @@ -45,6 +35,8 @@ class OpCreateFluid(val cost: Long, val bucket: Item, val cauldron: BlockState, private data class Spell(val pos: BlockPos, val bucket: Item, val cauldron: BlockState, val fluid: Fluid) : RenderedSpell { override fun cast(env: CastingEnvironment) { + if (!IXplatAbstractions.INSTANCE.isPlacingAllowed(env.world, pos, ItemStack(bucket), env.castingEntity as? ServerPlayer)) + return val state = env.world.getBlockState(pos) From 21c94a3a8a61b134e57a60733395ca38ce678989 Mon Sep 17 00:00:00 2001 From: Aly Date: Mon, 27 Apr 2026 19:44:28 -0600 Subject: [PATCH 14/31] add TreeList class TreeList is an immutable collection ported from Scala's Vector class --- .../petrak/hexcasting/api/utils/TreeList.java | 3386 +++++++++++++++++ 1 file changed, 3386 insertions(+) create mode 100644 Common/src/main/java/at/petrak/hexcasting/api/utils/TreeList.java diff --git a/Common/src/main/java/at/petrak/hexcasting/api/utils/TreeList.java b/Common/src/main/java/at/petrak/hexcasting/api/utils/TreeList.java new file mode 100644 index 0000000000..cbc99d2ad6 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/api/utils/TreeList.java @@ -0,0 +1,3386 @@ +package at.petrak.hexcasting.api.utils; + +import java.lang.reflect.Array; +import java.util.*; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; + +import static java.util.Arrays.copyOf; +import static java.util.Arrays.copyOfRange; + +// TODO mark everything with use-site variance + +/** + * Ported from Scala 2.13.16's scala.collection.immutable.Vector + */ +@SuppressWarnings("unchecked") +public sealed abstract class TreeList extends AbstractList implements RandomAccess { + + private static int copyToArray(Iterable it, Object[] dst, int start) { + if(it instanceof Collection cc) { + final int ccSize = cc.size(); + final int dstRoom = dst.length - start; + final int toCopy = Math.min(ccSize, dstRoom); + System.arraycopy(cc.toArray(), 0, dst, start, toCopy); + return toCopy; + } else { + int i = 0; + Iterator iter = it.iterator(); + while (i + start < dst.length && iter.hasNext()) { + dst[i + start] = iter.next(); + i++; + } + return i; + } + } + + private static int computeSize(Iterable it) { + if(it instanceof Collection cc) return cc.size(); + else { + int i = 0; + for (final Object o : it) { + i++; + } + return i; + } + } + + private static int knownSize(Iterable it) { + if(it instanceof Collection cc) { + return cc.size(); + } else return Integer.MAX_VALUE; + } + + private static int sizeCompare(Iterable left, int right) { + if(right == Integer.MAX_VALUE) return 1; + else { + final int known = knownSize(left); + if(known != Integer.MAX_VALUE) return Integer.compare(known, right); + else { + int i; + Iterator it; + for(i = 0, it = left.iterator(); it.hasNext(); i++, it.next()) { + if(i == right) return 1; + } + return i - right; + } + } + } + + private static int sizeCompare(Iterable left, Iterable right) { + final int rightKnownSize = knownSize(right); + + if(rightKnownSize != Integer.MAX_VALUE) return sizeCompare(left, rightKnownSize); + else { + final int leftKnownSize = knownSize(left); + + if(leftKnownSize != Integer.MAX_VALUE) { + final int res = sizeCompare(right, leftKnownSize); + if(res == Integer.MIN_VALUE) return 1; else return -res; + } else { + final Iterator leftIt = left.iterator(); + final Iterator rightIt = right.iterator(); + + while(leftIt.hasNext() && rightIt.hasNext()) { + leftIt.next(); + rightIt.next(); + } + return Boolean.compare(leftIt.hasNext(), rightIt.hasNext()); + } + } + } + + private static final int BITS = 5; + private static final int WIDTH = 1 << BITS; + private static final int MASK = WIDTH - 1; + private static final int BITS2 = BITS * 2; + private static final int WIDTH2 = 1 << BITS2; + private static final int BITS3 = BITS * 3; + private static final int WIDTH3 = 1 << BITS3; + private static final int BITS4 = BITS * 4; + private static final int WIDTH4 = 1 << BITS4; + private static final int BITS5 = BITS * 5; + private static final int WIDTH5 = 1 << BITS5; + private static final int LASTWIDTH = WIDTH << 1; // 1 extra bit in the last level to go up to Int.MaxValue (2^31-1) instead of 2^30: + private static final int LOG2_CONCAT_FASTER = 5; + private static final int ALIGN_TO_FASTER = 64; + + private static int vectorSliceDim(int count, int idx) { + final int c = count / 2; + return c + 1 - Math.abs(idx - c); + } + + private static T[] copyOrUse(T[] a, int start, int end) { + if(start == 0 && end == a.length) return a; else return copyOfRange(a, start, end); + } + + private static T[] copyTail(T[] a) { + return copyOfRange(a, 1, a.length); + } + + private static T[] copyInit(T[] a) { + return copyOfRange(a, 0, a.length - 1); + } + + private static T[] copyIfDifferentSize(T[] a, int len) { + if(a.length == len) return a; else return copyOf(a, len); + } + + private static Object[] wrap1(Object x) { + Object[] arr = new Object[1]; + arr[0] = x; + return arr; + } + + private static Object[][] wrap2(Object[] x) { + Object[][] arr = new Object[1][]; + arr[0] = x; + return arr; + } + + private static Object[][][] wrap3(Object[][] x) { + Object[][][] arr = new Object[1][][]; + arr[0] = x; + return arr; + } + + private static Object[][][][] wrap4(Object[][][] x) { + Object[][][][] arr = new Object[1][][][]; + arr[0] = x; + return arr; + } + + private static Object[][][][][] wrap5(Object[][][][] x) { + Object[][][][][] arr = new Object[1][][][][]; + arr[0] = x; + return arr; + } + + private static Object[] copyUpdate(Object[] a1, int idx1, Object elem) { + final Object[] a1c = a1.clone(); + a1c[idx1] = elem; + return a1c; + } + + private static Object[][] copyUpdate(Object[][] a2, int idx2, int idx1, Object elem) { + final Object[][] a2c = a2.clone(); + a2c[idx2] = copyUpdate(a2c[idx2], idx1, elem); + return a2c; + } + + private static Object[][][] copyUpdate(Object[][][] a3, int idx3, int idx2, int idx1, Object elem) { + final Object[][][] a3c = a3.clone(); + a3c[idx3] = copyUpdate(a3c[idx3], idx2, idx1, elem); + return a3c; + } + + private static Object[][][][] copyUpdate(Object[][][][] a4, int idx4, int idx3, int idx2, int idx1, Object elem) { + final Object[][][][] a4c = a4.clone(); + a4c[idx4] = copyUpdate(a4c[idx4], idx3, idx2, idx1, elem); + return a4c; + } + + private static Object[][][][][] copyUpdate(Object[][][][][] a5, int idx5, int idx4, int idx3, int idx2, int idx1, Object elem) { + final Object[][][][][] a5c = a5.clone(); + a5c[idx5] = copyUpdate(a5c[idx5], idx4, idx3, idx2, idx1, elem); + return a5c; + } + + private static Object[][][][][][] copyUpdate(Object[][][][][][] a6, int idx6, int idx5, int idx4, int idx3, int idx2, int idx1, Object elem) { + final Object[][][][][][] a6c = a6.clone(); + a6c[idx6] = copyUpdate(a6c[idx6], idx5, idx4, idx3, idx2, idx1, elem); + return a6c; + } + + private static T[] concatArrays(T[] a, T[] b) { + T[] dest = copyOf(a, a.length + b.length); + System.arraycopy(b, 0, dest, a.length, b.length); + return dest; + } + + private static Object[] copyAppend1(Object[] a, Object elem) { + final int alen = a.length; + final Object[] ac = new Object[alen + 1]; + System.arraycopy(a, 0, ac, 0, alen); + ac[alen] = elem; + return ac; + } + + private static T[] copyAppend(T[] a, T elem) { + T[] ac = copyOf(a, a.length + 1); + ac[ac.length - 1] = elem; + return ac; + } + + private static Object[] copyPrepend1(Object elem, Object[] a) { + final Object[] ac = new Object[a.length + 1]; + System.arraycopy(a, 0, ac, 1, a.length); + ac[0] = elem; + return ac; + } + + private static T[] copyPrepend(T elem, T[] a) { + T[] ac = (T[]) Array.newInstance(a.getClass().getComponentType(), a.length + 1); + System.arraycopy(a, 0, ac, 1, a.length); + ac[0] = elem; + return ac; + } + + private static final Object[] empty1 = new Object[0]; + private static final Object[][] empty2 = new Object[0][]; + private static final Object[][][] empty3 = new Object[0][][]; + private static final Object[][][][] empty4 = new Object[0][][][]; + private static final Object[][][][][] empty5 = new Object[0][][][][]; + private static final Object[][][][][][] empty6 = new Object[0][][][][][]; + + private static void foreachRec(int level, T[] a, Consumer f) { + int i = 0; + final int len = a.length; + if(level == 0) { + while(i < len) { + f.accept((A) a[i]); + i++; + } + } else { + final int l = level - 1; + while(i < len) { + foreachRec(l, (Object[]) a[i], f); + i++; + } + } + } + + private static Object[] mapElems1(Object[] a, Function f) { + for(int i = 0; i < a.length; i++) { + final Object v1 = a[i]; + final Object v2 = f.apply((A) v1); + if(v1 != v2) return mapElems1Rest(a, f, i, v2); + } + return a; + } + + private static Object[] mapElems1Rest(Object[] a, Function f, int at, Object v2) { + Object[] ac = new Object[a.length]; + if(at > 0) System.arraycopy(a, 0, ac, 0, at); + ac[at] = v2; + for(int i = at + 1; i < a.length; i++) { + ac[i] = f.apply((A) a[i]); + } + return ac; + } + + private static T[] mapElems(int n, T[] a, Function f) { + if(n == 1) return (T[]) mapElems1(a, f); + else { + for(int i = 0; i < a.length; i++) { + final T v1 = a[i]; + final Object[] v2 = mapElems(n - 1, (Object[]) v1, f); + if(v1 != v2) return mapElemsRest(n, a, f, i, v2); + } + return a; + } + } + + private static T[] mapElemsRest(int n, T[] a, Function f, int at, Object v2) { + final Object[] ac = (Object[]) Array.newInstance(a.getClass().getComponentType(), a.length); + if(at > 0) System.arraycopy(a, 0, ac, 0, at); + ac[at] = v2; + for(int i = at + 1; i < a.length; i++) { + ac[i] = mapElems(n - 1, (Object[]) a[i], f); + } + return (T[]) ac; + } + + private static Object[] prepend1IfSpace(Object[] prefix1, Iterable it) { + if(sizeCompare(it, WIDTH - prefix1.length) <= 0) { + final int s = computeSize(it); + if(s == 0) return null; + else if(s == 1) return copyPrepend(it.iterator().next(), prefix1); + else { + final Object[] prefix1b = new Object[prefix1.length + s]; + System.arraycopy(prefix1, 0, prefix1b, s, prefix1.length); + copyToArray(it, prefix1b, 0); + return prefix1b; + } + } else return null; + } + + private static Object[] append1IfSpace(Object[] suffix1, Iterable it) { + if(sizeCompare(it, WIDTH - suffix1.length) <= 0) { + final int s = computeSize(it); + if(s == 0) return null; + else if(s == 1) return copyAppend(suffix1, it.iterator().next()); + else { + final Object[] suffix1b = copyOf(suffix1, suffix1.length + s); + copyToArray(it, suffix1b, suffix1.length); + return suffix1b; + } + } else return null; + } + + final Object[] prefix1; + + TreeList(Object[] prefix1) { + this.prefix1 = prefix1; + } + + protected final IndexOutOfBoundsException ioob(int index) { + return new IndexOutOfBoundsException("%d is out of bounds (min 0, max %d)".formatted(index, this.length() - 1)); + } + + public int length() { + return this.prefix1.length; + } + + public ListIterator iterator() { + return new NewVectorIterator<>(this, this.length(), this.vectorSliceCount()); + } + + protected TreeList filterImpl(Predicate predicate, boolean isFlipped) { + int i = 0; + final int len = this.prefix1.length; + while (i != len) { + if(predicate.test((A) this.prefix1[i]) == isFlipped) { + // each 1 bit indicates that index passes the filter. + // all indices < i are also assumed to pass the filter + int bitmap = 0; + int j = i + 1; + while (j < len) { + if(predicate.test((A) this.prefix1[j]) != isFlipped) { + bitmap |= (1 << j); + } + j += 1; + } + final int newLen = Integer.bitCount(bitmap); + + if(newLen == 0) return TreeList0.getInstance(); + else { + final Object[] newData = new Object[newLen]; + System.arraycopy(prefix1, 0, newData, 0, i); + int k = i + 1; + while (i != newLen) { + if (((1 << k) & bitmap) != 0) { + newData[i] = this.prefix1[k]; + i += 1; + } + k += 1; + } + return new TreeList1(newData); + } + } + + i++; + } + + return this; + } + + public abstract TreeList updated(int index, A elem); + + public abstract TreeList appended(A elem); + + public abstract TreeList prepended(A elem); + + public TreeList prependedAll(Iterable prefix) { + int k = knownSize(prefix); + if(k == 0) return this; + else if (k >= Integer.MAX_VALUE) { + VectorBuilder builder = new VectorBuilder(); + builder.addAll(prefix); + builder.addAll(this); + return builder.result(); + } else return this.prependedAll0(prefix, k); + } + + public TreeList appendedAll(Iterable suffix) { + int k = knownSize(suffix); + if(k == 0) return this; + else if (k >= Integer.MAX_VALUE) { + VectorBuilder builder = new VectorBuilder(); + builder.addAll(this); + builder.addAll(suffix); + return builder.result(); + } else return this.appendedAll0(suffix, k); + } + + protected TreeList prependedAll0(Iterable prefix, int k) { + // k >= 0, k = prefix.knownSize + final int tinyAppendLimit = 4 + this.vectorSliceCount(); + if (k < tinyAppendLimit /*|| k < (this.size >>> Log2ConcatFaster)*/) { + TreeList v = this; + Object[] elements = new Object[k]; + Iterator it = prefix.iterator(); + for(int i = elements.length - 1; i >= 0; i--) { + elements[i] = it.next(); + } + for(Object elem : elements) { + v = v.prepended((A) elem); + } + return v; + } else if (this.size() < (k >>> LOG2_CONCAT_FASTER) && prefix instanceof TreeList) { + TreeList v = (TreeList) prefix; + for (A a : this) v = v.appended(a); + return v; + } else if (k < this.size() - ALIGN_TO_FASTER) { + return new VectorBuilder().alignTo(k, this).addAll(prefix).addAll(this).result(); + } else { + VectorBuilder builder = new VectorBuilder(); + builder.addAll(prefix); + builder.addAll(this); + return builder.result(); + } + } + + protected TreeList appendedAll0(Iterable suffix, int k) { + // k >= 0, k = suffix.knownSize + final int tinyAppendLimit = 4 + this.vectorSliceCount(); + if (k < tinyAppendLimit) { + TreeList v = this; + for(A a : suffix) v = v.appended(a); + return v; + } else if (this.size() < (k >>> LOG2_CONCAT_FASTER) && suffix instanceof TreeList) { + TreeList v = (TreeList) suffix; + for (A a : this.reversed()) v = v.prepended(a); + return v; + } else if (this.size() < k - ALIGN_TO_FASTER && suffix instanceof TreeList) { + TreeList v = (TreeList) suffix; + return new VectorBuilder().alignTo(this.size(), v).addAll(this).addAll(v).result(); + } else return new VectorBuilder().initFrom(this).addAll(suffix).result(); + } + + public final TreeList take(int n) { + return this.slice(0, n); + } + + public final TreeList drop(int n) { + return this.slice(n, this.length()); + } + + public final TreeList takeRight(int n) { + return this.slice(this.length() - Math.max(n, 0), this.length()); + } + + public final TreeList dropRight(int n) { + return this.slice(0, this.length() - Math.max(n, 0)); + } + + public TreeList tail() { + return this.slice(1, this.length()); + } + + public TreeList init() { + return this.slice(0, this.length() - 1); + } + + /** Like slice but parameters must be 0 <= lo < hi < length */ + protected abstract TreeList slice0(int lo, int hi); + + /** Number of slices */ + abstract int vectorSliceCount(); + /** Slice at index */ + abstract Object[] vectorSlice(int idx); + /** Length of all slices up to and including index */ + abstract int vectorSlicePrefixLength(int idx); + + public final A head() { + if(this.prefix1.length == 0) throw new NoSuchElementException("empty.head"); + else return (A) this.prefix1[0]; + } + + public A last() { + return (A) this.prefix1[this.prefix1.length - 1]; + } + + public void foreach(Consumer f) { + final int c = this.vectorSliceCount(); + for(int i = 0; i < c; i++) { + foreachRec(vectorSliceDim(c, i) - 1, this.vectorSlice(i), f); + } + } + + public final TreeList slice(int from, int until) { + final int lo = Math.max(from, 0); + final int hi = Math.min(until, this.length()); + if(hi <= lo) return TreeList0.getInstance(); + else if(hi - lo == this.length()) return this; + else return this.slice0(lo, hi); + } + + public boolean contains(Object elem) { + return this.exists(elem::equals); + } + + public int copyToArray(A[] arr, int start, int len) { + int i; + ListIterator li; + for(i = 0, li = this.iterator(); i < len && (i + start) < arr.length && li.hasNext(); i++) { + arr[start + i] = li.next(); + } + return i; + } + + public final int copyToArray(A[] arr, int start) { + return this.copyToArray(arr, start, Integer.MAX_VALUE); + } + + public final int copyToArray(A[] arr) { + return this.copyToArray(arr, 0, Integer.MAX_VALUE); + } + + public static TreeList empty() { + return TreeList0.getInstance(); + } + + public static TreeList from(Iterable it) { + if(it instanceof TreeList v) return v; + + final int knownSize = knownSize(it); + if(knownSize == 0) return empty(); + else if(knownSize > 0 && knownSize <= WIDTH) { + Object[] a1; + if(it instanceof Collection c) a1 = c.toArray(); + else { + a1 = new Object[knownSize]; + copyToArray(it, a1, 0); + } + return new TreeList1<>(a1); + } else { + VectorBuilder builder = new VectorBuilder<>(); + builder.addAll(it); + return builder.result(); + } + } + + @Override + public boolean equals(Object obj) { + if(this == obj) return true; + else if(obj instanceof List la) { + return this.sameElements((List) la); + } else return false; + } + + public boolean sameElements(Iterable iterable) { + final int thisKnownSize = this.knownSize(); + final boolean knownSizeDifference; + if(thisKnownSize != Integer.MAX_VALUE) { + final int thatKnownSize = knownSize(iterable); + knownSizeDifference = thatKnownSize != Integer.MAX_VALUE && thisKnownSize != thatKnownSize; + } else knownSizeDifference = false; + if(knownSizeDifference) return false; + Iterator left = this.iterator(); + Iterator right = iterable.iterator(); + while (left.hasNext() && right.hasNext()) { + if(!left.next().equals(right.next())) return false; + } + return left.hasNext() == right.hasNext(); + } + + public boolean exists(Predicate p) { + boolean res = false; + final Iterator it = this.iterator(); + while (!res && it.hasNext()) { res = p.test(it.next()); } + return res; + } + + public TreeList filter(Predicate pred) { + return this.filterImpl(pred, false); + } + + public TreeList flatMap(Function> func) { + VectorBuilder builder = new VectorBuilder(); + for(A a : this) { + builder.addAll(func.apply(a)); + } + return builder.result(); + } + + @Override + public int hashCode() { + int hashCode = 1; + for(A a : this) { + hashCode *= 31; + if(a != null) hashCode += a.hashCode(); + } + return hashCode; + } + + public boolean isEmpty() { + return this.length() == 0; + } + + public int knownSize() { + return this.length(); + } + + public abstract TreeList map(Function f); + + public TreeList reversed() { + // FIXME: Understand NewVectorIterator + VectorBuilder builder = new VectorBuilder<>(); + for(int i = this.size() - 1; i >= 0; i--) { + builder.addOne(this.get(i)); + } + return builder.result(); + } + + // FIXME: Get rid of this + public TreeList reversedVec() { + return this.reversed(); + } + + public final int size() { + return this.length(); + } + + public A[] toArray(Class clazz) { + final A[] destination = (A[]) Array.newInstance(clazz, this.knownSize()); + copyToArray(destination, 0); + return destination; + } + + public abstract A get(int i); + + private static sealed abstract class BigTreeList extends TreeList { + final Object[] suffix1; + final int length0; + + protected BigTreeList(Object[] prefix1, Object[] suffix1, int length0) { + super(prefix1); + this.suffix1 = suffix1; + this.length0 = length0; + } + + @Override + public int length() { + return this.length0; + } + + @Override + protected TreeList filterImpl(Predicate predicate, boolean isFlipped) { + int i = 0; + final int len = this.prefix1.length; + while (i != len) { + if(predicate.test((A) this.prefix1[i]) == isFlipped) { + int bitmap = 0; + int j = i + 1; + while(j < len) { + if(predicate.test((A) this.prefix1[j]) != isFlipped) { + bitmap |= (1 << j); + } + j += 1; + } + final int newLen = i + Integer.bitCount(bitmap); + + VectorBuilder b = new VectorBuilder<>(); + int k = 0; + while (k < i) { + b.addOne((A) this.prefix1[k]); + k += 1; + } + k = i + 1; + while(i != newLen) { + if(((1 << k) & bitmap) != 0) { + b.addOne((A) this.prefix1[k]); + i += 1; + } + k += 1; + } + this.foreachRest(v -> { + if(predicate.test(v) != isFlipped) b.addOne(v); + }); + return b.result(); + } + i += 1; + } + VectorBuilder b = new VectorBuilder<>(); + b.initFrom(this.prefix1); + this.foreachRest(v -> { + if(predicate.test(v) != isFlipped) b.addOne(v); + }); + return b.result(); + } + + protected final void foreachRest(Consumer f) { + final int c = this.vectorSliceCount(); + for(int i = 1; i < c; i++) { + foreachRec(vectorSliceDim(c, i)-1, vectorSlice(i), f); + } + } + + @Override + public A last() { + final Object[] suffix = this.suffix1; + if(suffix.length == 0) throw new NoSuchElementException("empty.tail"); + else return (A) suffix[suffix.length - 1]; + } + } + + private static final class TreeList0 extends BigTreeList { + private static final TreeList0 INSTANCE = new TreeList0(); + + public static TreeList getInstance() { return (TreeList) INSTANCE; } + + private TreeList0() { + super(empty1, empty1, 0); + } + + public ListIterator iterator() { + return Collections.emptyListIterator(); + } + + @Override + public Void get(int i) { + throw ioob(i); + } + + @Override + public TreeList updated(int index, Object elem) { + throw ioob(index); + } + + @Override + public TreeList appended(Object elem) { + return new TreeList1<>(wrap1(elem)); + } + + @Override + public TreeList prepended(Object elem) { + return new TreeList1<>(wrap1(elem)); + } + + @Override + public TreeList map(Function f) { + return (TreeList) this; + } + + @Override + public TreeList tail() { + throw new UnsupportedOperationException("empty.tail"); + } + + @Override + public TreeList init() { + throw new UnsupportedOperationException("empty.init"); + } + + @Override + protected TreeList slice0(int lo, int hi) { + return this; + } + + @Override + int vectorSliceCount() { + return 0; + } + + @Override + Object[] vectorSlice(int idx) { + return null; + } + + @Override + int vectorSlicePrefixLength(int idx) { + return 0; + } + + @Override + public boolean equals(Object obj) { + if(this == obj) return true; + else if(obj instanceof TreeList) return false; + else return super.equals(obj); + } + + @Override + protected TreeList prependedAll0(Iterable prefix, int k) { + return TreeList.from(prefix); + } + + @Override + protected TreeList appendedAll0(Iterable suffix, int k) { + return TreeList.from(suffix); + } + } + + private static final class TreeList1 extends TreeList { + TreeList1(Object[] data1) { + super(data1); + } + + @Override + public A get(int index) { + if(index >= 0 && index < this.prefix1.length) + return (A) this.prefix1[index]; + else throw ioob(index); + } + + @Override + public TreeList updated(int index, A elem) { + if(index >= 0 && index < this.prefix1.length) + return new TreeList1<>(copyUpdate(this.prefix1, index, elem)); + else throw ioob(index); + } + + @Override + public TreeList appended(A elem) { + final int len1 = this.prefix1.length; + if(len1 < WIDTH) return new TreeList1<>(copyAppend1(this.prefix1, elem)); + else return new TreeList2<>(this.prefix1, WIDTH, empty2, wrap1(elem), WIDTH + 1); + } + + @Override + public TreeList prepended(A elem) { + final int len1 = this.prefix1.length; + if(len1 < WIDTH) return new TreeList1<>(copyPrepend1(elem, this.prefix1)); + else return new TreeList2<>(wrap1(elem), 1, empty2, this.prefix1, len1 + 1); + } + + @Override + public TreeList map(Function f) { + return new TreeList1<>(mapElems1(this.prefix1, f)); + } + + @Override + protected TreeList slice0(int lo, int hi) { + return new TreeList1<>(copyOfRange(this.prefix1, lo, hi)); + } + + @Override + public TreeList tail() { + if(this.prefix1.length == 1) return TreeList0.getInstance(); + else return new TreeList1<>(copyTail(this.prefix1)); + } + + @Override + public TreeList init() { + if(this.prefix1.length == 1) return TreeList0.getInstance(); + else return new TreeList1<>(copyInit(this.prefix1)); + } + + @Override + int vectorSliceCount() { + return 1; + } + + @Override + Object[] vectorSlice(int idx) { + return this.prefix1; + } + + @Override + int vectorSlicePrefixLength(int idx) { + return this.prefix1.length; + } + + @Override + protected TreeList prependedAll0(Iterable prefix, int k) { + final Object[] data1b = prepend1IfSpace(this.prefix1, prefix); + if(data1b == null) return super.prependedAll0(prefix, k); + else return new TreeList1<>(data1b); + } + + @Override + protected TreeList appendedAll0(Iterable suffix, int k) { + final Object[] data1b = append1IfSpace(this.prefix1, suffix); + if(data1b != null) return new TreeList1<>(data1b); + else return super.appendedAll0(suffix, k); + } + } + + private static final class TreeList2 extends BigTreeList { + final int len1; + final Object[][] data2; + + TreeList2(Object[] prefix1, int len1, Object[][] data2, Object[] suffix1, int length0) { + super(prefix1, suffix1, length0); + this.len1 = len1; + this.data2 = data2; + } + + @Override + public A get(int i) { + if(i >= 0 && i < this.length0) { + final int io = i - this.len1; + if(io >= 0) { + final int i2 = io >>> BITS; + final int i1 = io & MASK; + if(i2 < data2.length) return (A) this.data2[i2][i1]; + else return (A) suffix1[io & MASK]; + } else return (A) this.prefix1[i]; + } else throw ioob(i); + } + + @Override + public TreeList updated(int index, A elem) { + if(index >= 0 && index < this.length0) { + if(index >= this.len1) { + final int io = index - this.len1; + final int i2 = io >>> BITS; + final int i1 = io & MASK; + if(i2 < data2.length) return new TreeList2<>( + this.prefix1, this.len1, + copyUpdate(this.data2, i2, i1, elem), + this.suffix1, this.length0 + ); else return new TreeList2<>( + this.prefix1, this.len1, this.data2, + copyUpdate(this.suffix1, i1, elem), + this.length0 + ); + } else { + return new TreeList2<>( + copyUpdate(this.prefix1, index, elem), + this.len1, this.data2, this.suffix1, this.length0 + ); + } + } else throw ioob(index); + } + + @Override + public TreeList appended(A elem) { + if (suffix1.length < WIDTH) return new TreeList2<>( + this.prefix1, this.len1, this.data2, + copyAppend1(this.suffix1, elem), this.length0 + 1 + ); else if(data2.length < WIDTH - 2) return new TreeList2<>( + this.prefix1, this.len1, + copyAppend(this.data2, this.suffix1), wrap1(elem), this.length0 + 1 + ); else return new TreeList3<>( + this.prefix1, this.len1, this.data2, WIDTH * (WIDTH - 2) + this.len1, + empty3, wrap2(this.suffix1), wrap1(elem), this.length0 + 1 + ); + } + + @Override + public TreeList prepended(A elem) { + if (this.len1 < WIDTH) return new TreeList2<>( + copyPrepend1(elem, this.prefix1), this.len1 + 1, + this.data2, this.suffix1, + this.length0 + 1 + ); else if(data2.length < WIDTH - 2) return new TreeList2<>( + wrap1(elem), 1, copyPrepend(this.prefix1, this.data2), + this.suffix1, + this.length0 + 1 + ); else return new TreeList3<>( + wrap1(elem), 1, wrap2(this.prefix1), this.len1 + 1, + empty3, this.data2, this.suffix1, this.length0 + 1 + ); + } + + @Override + public TreeList map(Function f) { + return new TreeList2<>( + mapElems1(this.prefix1, f), this.len1, + mapElems(2, this.data2, f), + mapElems1(this.suffix1, f), this.length0 + ); + } + + @Override + protected TreeList slice0(int lo, int hi) { + final VectorSliceBuilder b = new VectorSliceBuilder(lo, hi); + b.consider(1, this.prefix1); + b.consider(2, this.data2); + b.consider(1, this.suffix1); + return b.result(); + } + + @Override + public TreeList tail() { + if(this.len1 > 1) return new TreeList2<>( + copyTail(this.prefix1), this.len1 - 1, + this.data2, this.suffix1, + this.length0 - 1 + ); else return this.slice0(1, this.length0); + } + + @Override + public TreeList init() { + if(this.suffix1.length > 1) return new TreeList2<>( + this.prefix1, this.len1, this.data2, + copyInit(this.suffix1), this.length0 - 1 + ); else return this.slice0(0, this.length0 - 1); + } + + @Override + int vectorSliceCount() { + return 3; + } + + @Override + Object[] vectorSlice(int idx) { + return switch(idx) { + case 0 -> this.prefix1; + case 1 -> this.data2; + case 2 -> this.suffix1; + default -> throw new IllegalArgumentException("slice idx " + idx); + }; + } + + @Override + int vectorSlicePrefixLength(int idx) { + return switch(idx) { + case 0 -> this.len1; + case 1 -> this.length0 - this.suffix1.length; + case 2 -> this.length0; + default -> throw new IllegalArgumentException("slice idx " + idx); + }; + } + + @Override + protected TreeList prependedAll0(Iterable prefix, int k) { + final Object[] prefix1b = prepend1IfSpace(this.prefix1, prefix); + if(prefix1b == null) return super.prependedAll0(prefix, k); + else { + final int diff = prefix1b.length - prefix1.length; + return new TreeList2<>( + prefix1b, this.len1 + diff, + this.data2, this.suffix1, + this.length0 + diff + ); + } + } + + @Override + protected TreeList appendedAll0(Iterable suffix, int k) { + final Object[] suffix1b = append1IfSpace(this.suffix1, suffix); + if(suffix1b != null) return new TreeList2<>( + this.prefix1, this.len1, this.data2, + suffix1b, this.length0 - suffix1.length + suffix1b.length + ); else return super.appendedAll0(suffix, k); + } + } + + private static final class TreeList3 extends BigTreeList { + + final int len1; + final Object[][] prefix2; + final int len12; + final Object[][][] data3; + final Object[][] suffix2; + + TreeList3( + Object[] prefix1, int len1, + Object[][] prefix2, int len12, + Object[][][] data3, Object[][] suffix2, Object[] suffix1, + int length0 + ) { + super(prefix1, suffix1, length0); + this.len1 = len1; + this.prefix2 = prefix2; + this.len12 = len12; + this.data3 = data3; + this.suffix2 = suffix2; + } + + @Override + public A get(int i) { + if(i >= 0 && i < this.length0) { + final int io = i - this.len12; + if(io >= 0) { + final int i3 = io >>> BITS2; + final int i2 = (io >>> BITS) & MASK; + final int i1 = io & MASK; + if(i3 < this.data3.length) return (A) this.data3[i3][i2][i1]; + else if(i2 < this.suffix2.length) return (A) this.suffix2[i2][i1]; + else return (A) this.suffix1[i1]; + } else if(i >= this.len1) { + final int io_ = i - this.len1; + return (A) prefix2[io_ >>> BITS][io_ & MASK]; + } else return (A) this.prefix1[i]; + } else throw ioob(i); + } + + @Override + public TreeList updated(int index, A elem) { + if(index >= 0 && index < this.length0) { + if(index >= this.len12) { + final int io = index - this.len12; + final int i3 = io >>> BITS2; + final int i2 = (io >>> BITS) & MASK; + final int i1 = io & MASK; + if(i3 < this.data3.length) return new TreeList3<>( + this.prefix1, this.len1, this.prefix2, this.len12, + copyUpdate(this.data3, i3, i2, i1, elem), + this.suffix2, this.suffix1, this.length0 + ); else if(i2 < this.suffix2.length) return new TreeList3<>( + this.prefix1, this.len1, this.prefix2, this.len12, this.data3, + copyUpdate(this.suffix2, i2, i1, elem), + this.suffix1, this.length0 + ); else return new TreeList3<>( + this.prefix1, this.len1, this.prefix2, this.len12, this.data3, this.suffix2, + copyUpdate(this.suffix1, i1, elem), + this.length0 + ); + } else if(index >= this.len1) { + final int io = index - this.len1; + return new TreeList3<>( + this.prefix1, this.len1, + copyUpdate(prefix2, io >>> BITS, io & MASK, elem), + this.len12, this.data3, this.suffix2, this.suffix1, this.length0 + ); + } else { + return new TreeList3<>( + copyUpdate(this.prefix1, index, elem), + this.len1, this.prefix2, this.len12, this.data3, this.suffix2, this.suffix1, this.length0 + ); + } + } else throw ioob(index); + } + + @Override + public TreeList appended(A elem) { + if(this.suffix1.length < WIDTH) return new TreeList3<>( + this.prefix1, this.len1, this.prefix2, this.len12, this.data3, this.suffix2, + copyAppend1(this.suffix1, elem), this.length0 + 1 + ); else if(this.suffix2.length < WIDTH - 1) return new TreeList3<>( + this.prefix1, this.len1, this.prefix2, this.len12, this.data3, + copyAppend(this.suffix2, this.suffix1), wrap1(elem), this.length0 + 1 + ); else if(this.data3.length < WIDTH - 2) return new TreeList3<>( + this.prefix1, this.len1, this.prefix2, this.len12, + copyAppend(this.data3, copyAppend(this.suffix2, this.suffix1)), empty2, wrap1(elem), this.length0 + 1 + ); else return new TreeList4<>( + this.prefix1, this.len1, this.prefix2, this.len12, this.data3, (WIDTH - 2) * WIDTH2 + this.len12, + empty4, wrap3(copyAppend(this.suffix2, this.suffix1)), empty2, wrap1(elem), this.length0 + 1 + ); + } + + @Override + public TreeList prepended(A elem) { + if(this.len1 < WIDTH) return new TreeList3<>( + copyPrepend1(elem, this.prefix1), this.len1 + 1, + this.prefix2, + this.len12 + 1, + this.data3, this.suffix2, this.suffix1, + this.length0 + 1 + ); else if(this.len12 < WIDTH2) return new TreeList3<>( + wrap1(elem), 1, copyPrepend(this.prefix1, this.prefix2), this.len12 + 1, + this.data3, this.suffix2, this.suffix1, + this.length0 + 1 + ); else if(this.data3.length < WIDTH - 2) return new TreeList3<>( + wrap1(elem), 1, empty2, 1, copyPrepend(copyPrepend(this.prefix1, this.prefix2), this.data3), + this.suffix2, this.suffix1, + this.length0 + 1 + ); else return new TreeList4<>( + wrap1(elem), 1, empty2, 1, wrap3(copyPrepend(this.prefix1, this.prefix2)), this.len12 + 1, + empty4, this.data3, this.suffix2, this.suffix1, this.length0 + 1 + ); + } + + @Override + public TreeList map(Function f) { + return new TreeList3<>( + mapElems1(this.prefix1, f), this.len1, + mapElems(2, this.prefix2, f), this.len12, + mapElems(3, this.data3, f), + mapElems(2, this.suffix2, f), + mapElems1(this.suffix1, f), + this.length0 + ); + } + + @Override + protected TreeList slice0(int lo, int hi) { + final VectorSliceBuilder b = new VectorSliceBuilder(lo, hi); + b.consider(1, this.prefix1); + b.consider(2, this.prefix2); + b.consider(3, this.data3); + b.consider(2, this.suffix2); + b.consider(1, this.suffix1); + return b.result(); + } + + @Override + public TreeList tail() { + if(this.len1 > 1) return new TreeList3<>( + copyTail(this.prefix1), this.len1 - 1, + this.prefix2, this.len12 - 1, + this.data3, this.suffix2, this.suffix1, + this.length0 - 1 + ); else return this.slice0(1, this.length0); + } + + @Override + public TreeList init() { + if(this.suffix1.length > 1) return new TreeList3<>( + this.prefix1, this.len1, this.prefix2, this.len12, this.data3, this.suffix2, + copyInit(this.suffix1), this.length0 - 1 + ); else return this.slice0(0, this.length0 - 1); + } + + @Override + int vectorSliceCount() { + return 5; + } + + @Override + Object[] vectorSlice(int idx) { + return switch(idx) { + case 0 -> this.prefix1; + case 1 -> this.prefix2; + case 2 -> this.data3; + case 3 -> this.suffix2; + case 4 -> this.suffix1; + default -> throw new IllegalArgumentException("slice idx " + idx); + }; + } + + @Override + int vectorSlicePrefixLength(int idx) { + return switch (idx) { + case 0 -> this.len1; + case 1 -> this.len12; + case 2 -> this.len12 + this.data3.length * WIDTH2; + case 3 -> this.length0 - this.suffix1.length; + case 4 -> this.length0; + default -> throw new IllegalArgumentException("slice idx " + idx); + }; + } + + @Override + protected TreeList prependedAll0(Iterable prefix, int k) { + final Object[] prefix1b = prepend1IfSpace(this.prefix1, prefix); + if(prefix1b == null) return super.prependedAll0(prefix, k); + else { + final int diff = prefix1b.length - this.prefix1.length; + return new TreeList3<>( + prefix1b, this.len1 + diff, + this.prefix2, this.len12 + diff, + this.data3, this.suffix2, this.suffix1, + this.length0 + diff + ); + } + } + + @Override + protected TreeList appendedAll0(Iterable suffix, int k) { + final Object[] suffix1b = prepend1IfSpace(this.suffix1, suffix); + if(suffix1b != null) return new TreeList3<>( + this.prefix1, this.len1, this.prefix2, this.len12, this.data3, this.suffix2, + suffix1b, this.length0 - this.suffix1.length + suffix1b.length + ); else return super.appendedAll0(suffix, k); + } + } + + private static final class TreeList4 extends BigTreeList { + + final int len1; + final Object[][] prefix2; + final int len12; + final Object[][][] prefix3; + final int len123; + final Object[][][][] data4; + final Object[][][] suffix3; + final Object[][] suffix2; + + TreeList4( + Object[] prefix1, int len1, + Object[][] prefix2, int len12, + Object[][][] prefix3, int len123, + Object[][][][] data4, + Object[][][] suffix3, Object[][] suffix2, Object[] suffix1, + int length0 + ) { + super(prefix1, suffix1, length0); + this.len1 = len1; + this.prefix2 = prefix2; + this.len12 = len12; + this.prefix3 = prefix3; + this.len123 = len123; + this.data4 = data4; + this.suffix3 = suffix3; + this.suffix2 = suffix2; + } + + @Override + public A get(int i) { + if(i >= 0 && i < this.length0) { + final int io_ = i - this.len123; + if(io_ >= 0) { + final int i4 = io_ >>> BITS3; + final int i3 = (io_ >>> BITS2) & MASK; + final int i2 = (io_ >>> BITS) & MASK; + final int i1 = io_ & MASK; + if(i4 < this.data4.length) return (A) this.data4[i4][i3][i2][i1]; + else if(i3 < this.suffix3.length) return (A) this.suffix3[i3][i2][i1]; + else if(i2 < this.suffix2.length) return (A) this.suffix2[i2][i1]; + else return (A) this.suffix1[i1]; + } else if(i >= this.len12) { + final int io = i - this.len12; + return (A) this.prefix3[io >>> BITS2][(io >>> BITS) & MASK][io & MASK]; + } else if(i >= this.len1) { + final int io = i - this.len1; + return (A) this.prefix2[io >>> BITS][io & MASK]; + } else return (A) this.prefix1[i]; + } else throw ioob(i); + } + + @Override + public TreeList updated(int index, A elem) { + if(index >= 0 && index < this.length0) { + if(index >= this.len123) { + final int io = index - this.len123; + final int i4 = io >>> BITS3; + final int i3 = (io >>> BITS2) & MASK; + final int i2 = (io >>> BITS) & MASK; + final int i1 = io & MASK; + if(i4 < this.data4.length) return new TreeList4<>( + this.prefix1, this.len1, + this.prefix2, this.len12, + this.prefix3, this.len123, + copyUpdate(this.data4, i4, i3, i2, i1, elem), + this.suffix3, this.suffix2, this.suffix1, + this.length0 + ); else if(i3 < suffix3.length) return new TreeList4<>( + this.prefix1, this.len1, + this.prefix2, this.len12, + this.prefix3, this.len123, + this.data4, + copyUpdate(this.suffix3, i3, i2, i1, elem), this.suffix2, this.suffix1, + this.length0 + ); else if(i2 < suffix2.length) return new TreeList4<>( + this.prefix1, this.len1, + this.prefix2, this.len12, + this.prefix3, this.len123, + this.data4, + this.suffix3, copyUpdate(this.suffix2, i2, i1, elem), this.suffix1, + this.length0 + ); else return new TreeList4<>( + this.prefix1, this.len1, + this.prefix2, this.len12, + this.prefix3, this.len123, + this.data4, + this.suffix3, this.suffix2, copyUpdate(this.suffix1, i1, elem), + this.length0 + ); + } else if(index >= this.len12) { + final int io = index - this.len12; + return new TreeList4<>( + this.prefix1, this.len1, + this.prefix2, this.len12, + copyUpdate(this.prefix3,io >>> BITS2, (io >>> BITS) & MASK, io & MASK, elem), this.len123, + this.data4, + this.suffix3, this.suffix2, this.suffix1, + this.length0 + ); + } else if(index >= this.len1) { + final int io = index - this.len1; + return new TreeList4<>( + this.prefix1, this.len1, + copyUpdate(this.prefix2,io >>> BITS, io & MASK, elem), this.len12, + this.prefix3, this.len123, + this.data4, + this.suffix3, this.suffix2, this.suffix1, + this.length0 + ); + } else return new TreeList4<>( + copyUpdate(this.prefix1, index, elem), this.len1, + this.prefix2, this.len12, + this.prefix3, this.len123, + this.data4, + this.suffix3, this.suffix2, this.suffix1, + this.length0 + ); + } else throw ioob(index); + } + + @Override + public TreeList appended(A elem) { + if(suffix1.length < WIDTH) return new TreeList4<>( + this.prefix1, this.len1, this.prefix2, this.len12, this.prefix3, this.len123, this.data4, + this.suffix3, this.suffix2, copyAppend1(this.suffix1, elem), this.length0 + 1 + ); else if(suffix2.length < WIDTH - 1) return new TreeList4<>( + this.prefix1, this.len1, this.prefix2, this.len12, this.prefix3, this.len123, this.data4, + this.suffix3, copyAppend(this.suffix2, this.suffix1), wrap1(elem), this.length0 + 1 + ); else if(suffix3.length < WIDTH - 1) return new TreeList4<>( + this.prefix1, this.len1, this.prefix2, this.len12, this.prefix3, this.len123, this.data4, + copyAppend(this.suffix3, copyAppend(this.suffix2, this.suffix1)), empty2, wrap1(elem), this.length0 + 1 + ); else if(data4.length < WIDTH - 2) return new TreeList4<>( + this.prefix1, this.len1, this.prefix2, this.len12, this.prefix3, this.len123, + copyAppend(this.data4, copyAppend(this.suffix3, copyAppend(this.suffix2, this.suffix1))), + empty3, empty2, wrap1(elem), this.length0 + 1 + ); else return new TreeList5<>( + this.prefix1, this.len1, this.prefix2, this.len12, this.prefix3, this.len123, + this.data4, (WIDTH - 2) * WIDTH3 + this.len123, empty5, + wrap4(copyAppend(this.suffix3, copyAppend(this.suffix2, this.suffix1))), empty3, empty2, wrap1(elem), + this.length0 + 1 + ); + } + + @Override + public TreeList prepended(A elem) { + if(this.len1 < WIDTH) return new TreeList4<>( + copyPrepend1(elem, this.prefix1), this.len1 + 1, this.prefix2, this.len12 + 1, + this.prefix3, this.len123 + 1, this.data4, this.suffix3, this.suffix2, this.suffix1, + this.length0 + 1 + ); else if(this.len12 < WIDTH2) return new TreeList4<>( + wrap1(elem), 1, copyPrepend(this.prefix1, this.prefix2), this.len12 + 1, + this.prefix3, this.len123 + 1, this.data4, this.suffix3, this.suffix2, this.suffix1, + this.length0 + 1 + ); else if(this.len123 < WIDTH3) return new TreeList4<>( + wrap1(elem), 1, empty2, 1, + copyPrepend(copyPrepend(this.prefix1, this.prefix2), this.prefix3), this.len123 + 1, + this.data4, this.suffix3, this.suffix2, this.suffix1, + this.length0 + 1 + ); else if(this.data4.length < WIDTH - 2) return new TreeList4<>( + wrap1(elem), 1, empty2, 1, empty3, 1, + copyPrepend(copyPrepend(copyPrepend(this.prefix1, this.prefix2), this.prefix3), this.data4), + this.suffix3, this.suffix2, this.suffix1, + this.length0 + 1 + ); else return new TreeList5<>( + wrap1(elem), 1, empty2, 1, empty3, 1, + wrap4(copyPrepend(copyPrepend(this.prefix1, this.prefix2), this.prefix3)), this.len123 + 1, + empty5, this.data4, this.suffix3, this.suffix2, this.suffix1, this.length0 + 1 + ); + } + + @Override + public TreeList map(Function f) { + return new TreeList4<>( + mapElems1(this.prefix1, f), this.len1, + mapElems(2, this.prefix2, f), this.len12, + mapElems(3, this.prefix3, f), this.len123, + mapElems(4, this.data4, f), + mapElems(3, this.suffix3, f), mapElems(2, this.suffix2, f), mapElems1(this.suffix1, f), + this.length0 + ); + } + + @Override + protected TreeList slice0(int lo, int hi) { + final VectorSliceBuilder b = new VectorSliceBuilder(lo, hi); + b.consider(1, this.prefix1); + b.consider(2, this.prefix2); + b.consider(3, this.prefix3); + b.consider(4, this.data4); + b.consider(3, this.suffix3); + b.consider(2, this.suffix2); + b.consider(1, this.suffix1); + return b.result(); + } + + @Override + public TreeList tail() { + if(this.len1 > 1) return new TreeList4<>( + copyTail(this.prefix1), this.len1 - 1, + this.prefix2, this.len12 - 1, + this.prefix3, this.len123 - 1, + this.data4, this.suffix3, this.suffix2, this.suffix1, + this.length0 - 1 + ); else return this.slice0(1, this.length0); + } + + @Override + public TreeList init() { + if(this.suffix1.length > 1) return new TreeList4<>( + this.prefix1, this.len1, this.prefix2, this.len12, + this.prefix3, this.len123, this.data4, + this.suffix3, this.suffix2, copyInit(this.suffix1), + this.length0 - 1 + ); else return this.slice(0, this.length0 - 1); + } + + @Override + int vectorSliceCount() { + return 7; + } + + @Override + Object[] vectorSlice(int idx) { + return switch(idx) { + case 0 -> this.prefix1; + case 1 -> this.prefix2; + case 2 -> this.prefix3; + case 3 -> this.data4; + case 4 -> this.suffix3; + case 5 -> this.suffix2; + case 6 -> this.suffix1; + default -> throw new IllegalArgumentException("slice idx " + idx); + }; + } + + @Override + int vectorSlicePrefixLength(int idx) { + return switch (idx) { + case 0 -> this.len1; + case 1 -> this.len12; + case 2 -> this.len123; + case 3 -> this.len123 + this.data4.length * WIDTH3; + case 4 -> this.len123 + this.data4.length * WIDTH3 + this.suffix3.length * WIDTH2; + case 5 -> this.length0 - this.suffix1.length; + case 6 -> this.length0; + default -> throw new IllegalArgumentException("slice idx " + idx); + }; + } + + @Override + protected TreeList prependedAll0(Iterable prefix, int k) { + Object[] prefix1b = prepend1IfSpace(this.prefix1, prefix); + if(prefix1b == null) return super.prependedAll0(prefix, k); + else { + final int diff = prefix1b.length - this.prefix1.length; + return new TreeList4<>( + prefix1b, this.len1 + diff, + this.prefix2, this.len12 + diff, + this.prefix3, this.len123 + diff, + this.data4, this.suffix3, this.suffix2, this.suffix1, + this.length0 + diff + ); + } + } + + @Override + protected TreeList appendedAll0(Iterable suffix, int k) { + Object[] suffix1b = append1IfSpace(this.suffix1, suffix); + if(suffix1b != null) return new TreeList4<>( + this.prefix1, this.len1, this.prefix2, this.len12, + this.prefix3, this.len123, this.data4, this.suffix3, this.suffix2, + suffix1b, this.length0 - this.suffix1.length + suffix1b.length + ); else return super.appendedAll0(suffix, k); + } + } + + private static final class TreeList5 extends BigTreeList { + + final int len1; + final Object[][] prefix2; + final int len12; + final Object[][][] prefix3; + final int len123; + final Object[][][][] prefix4; + final int len1234; + final Object[][][][][] data5; + final Object[][][][] suffix4; + final Object[][][] suffix3; + final Object[][] suffix2; + + TreeList5( + Object[] prefix1, int len1, + Object[][] prefix2, int len12, + Object[][][] prefix3, int len123, + Object[][][][] prefix4, int len1234, + Object[][][][][] data5, + Object[][][][] suffix4, Object[][][] suffix3, Object[][] suffix2, Object[] suffix1, + int length0 + ) { + super(prefix1, suffix1, length0); + this.len1 = len1; + this.prefix2 = prefix2; + this.len12 = len12; + this.prefix3 = prefix3; + this.len123 = len123; + this.prefix4 = prefix4; + this.len1234 = len1234; + this.data5 = data5; + this.suffix4 = suffix4; + this.suffix3 = suffix3; + this.suffix2 = suffix2; + } + + @Override + public A get(int i) { + if(i >= 0 && i < this.length0) { + final int io_ = i - this.len1234; + if(io_ >= 0) { + final int i5 = io_ >>> BITS4; + final int i4 = (io_ >>> BITS3) & MASK; + final int i3 = (io_ >>> BITS2) & MASK; + final int i2 = (io_ >>> BITS) & MASK; + final int i1 = io_ & MASK; + if(i5 < this.data5.length) return (A) this.data5[i5][i4][i3][i2][i1]; + else if(i4 < this.suffix4.length) return (A) this.suffix4[i4][i3][i2][i1]; + else if(i3 < this.suffix3.length) return (A) this.suffix3[i3][i2][i1]; + else if(i2 < this.suffix2.length) return (A) this.suffix2[i2][i1]; + else return (A) this.suffix1[i1]; + } else if(i >= this.len123) { + final int io = i - this.len123; + return (A) this.prefix4[io >>> BITS3][(io >>> BITS2) & MASK][(io >>> BITS) & MASK][io & MASK]; + } else if(i >= this.len12) { + final int io = i - this.len12; + return (A) this.prefix3[io >>> BITS2][(io >>> BITS) & MASK][io & MASK]; + } else if(i >= this.len1) { + final int io = i - this.len1; + return (A) this.prefix2[io >>> BITS][io & MASK]; + } else return (A) this.prefix1[i]; + } else throw ioob(i); + } + + @Override + public TreeList updated(int index, A elem) { + if(index >= 0 && index < this.length0) { + if(index >= this.len1234) { + final int io = index - this.len1234; + final int i5 = io >>> BITS4; + final int i4 = (io >>> BITS3) & MASK; + final int i3 = (io >>> BITS2) & MASK; + final int i2 = (io >>> BITS) & MASK; + final int i1 = io & MASK; + if(i5 < this.data5.length) return new TreeList5<>( + this.prefix1, this.len1, + this.prefix2, this.len12, + this.prefix3, this.len123, + this.prefix4, this.len1234, + copyUpdate(this.data5, i5, i4, i3, i2, i1, elem), + this.suffix4, this.suffix3, this.suffix2, this.suffix1, + this.length0 + ); else if(i4 < suffix4.length) return new TreeList5<>( + this.prefix1, this.len1, + this.prefix2, this.len12, + this.prefix3, this.len123, + this.prefix4, this.len1234, + this.data5, + copyUpdate(this.suffix4, i4, i3, i2, i1, elem), this.suffix3, this.suffix2, this.suffix1, + this.length0 + ); else if(i3 < suffix3.length) return new TreeList5<>( + this.prefix1, this.len1, + this.prefix2, this.len12, + this.prefix3, this.len123, + this.prefix4, this.len1234, + this.data5, + this.suffix4, copyUpdate(this.suffix3, i3, i2, i1, elem), this.suffix2, this.suffix1, + this.length0 + ); else if(i2 < suffix2.length) return new TreeList5<>( + this.prefix1, this.len1, + this.prefix2, this.len12, + this.prefix3, this.len123, + this.prefix4, this.len1234, + this.data5, + this.suffix4, this.suffix3, copyUpdate(this.suffix2, i2, i1, elem), this.suffix1, + this.length0 + ); else return new TreeList5<>( + this.prefix1, this.len1, + this.prefix2, this.len12, + this.prefix3, this.len123, + this.prefix4, this.len1234, + this.data5, + this.suffix4, this.suffix3, this.suffix2, copyUpdate(this.suffix1, i1, elem), + this.length0 + ); + } else if(index >= this.len123) { + final int io = index - this.len123; + return new TreeList5<>( + this.prefix1, this.len1, + this.prefix2, this.len12, + this.prefix3, this.len123, + copyUpdate(this.prefix4,io >>> BITS3, (io >>> BITS2) & MASK, (io >>> BITS) & MASK, io & MASK, elem), this.len1234, + this.data5, + this.suffix4, this.suffix3, this.suffix2, this.suffix1, + this.length0 + ); + } else if(index >= this.len12) { + final int io = index - this.len12; + return new TreeList5<>( + this.prefix1, this.len1, + this.prefix2, this.len12, + copyUpdate(this.prefix3,io >>> BITS2, (io >>> BITS) & MASK, io & MASK, elem), this.len123, + this.prefix4, this.len1234, + this.data5, + this.suffix4, this.suffix3, this.suffix2, this.suffix1, + this.length0 + ); + } else if(index >= this.len1) { + final int io = index - this.len1; + return new TreeList5<>( + this.prefix1, this.len1, + copyUpdate(this.prefix2,io >>> BITS, io & MASK, elem), this.len12, + this.prefix3, this.len123, + this.prefix4, this.len1234, + this.data5, + this.suffix4, this.suffix3, this.suffix2, this.suffix1, + this.length0 + ); + } else return new TreeList5<>( + copyUpdate(this.prefix1, index, elem), this.len1, + this.prefix2, this.len12, + this.prefix3, this.len123, + this.prefix4, this.len1234, + this.data5, + this.suffix4, this.suffix3, this.suffix2, this.suffix1, + this.length0 + ); + } else throw ioob(index); + } + + @Override + public TreeList appended(A elem) { + if(suffix1.length < WIDTH) return new TreeList5<>( + this.prefix1, this.len1, this.prefix2, this.len12, this.prefix3, this.len123, this.prefix4, this.len1234, + this.data5, this.suffix4, this.suffix3, this.suffix2, copyAppend1(this.suffix1, elem), this.length0 + 1 + ); else if(suffix2.length < WIDTH - 1) return new TreeList5<>( + this.prefix1, this.len1, this.prefix2, this.len12, this.prefix3, this.len123, this.prefix4, this.len1234, + this.data5, this.suffix4, this.suffix3, copyAppend(this.suffix2, this.suffix1), wrap1(elem), this.length0 + 1 + ); else if(suffix3.length < WIDTH - 1) return new TreeList5<>( + this.prefix1, this.len1, this.prefix2, this.len12, this.prefix3, this.len123, this.prefix4, this.len1234, + this.data5, this.suffix4, + copyAppend(this.suffix3, copyAppend(this.suffix2, this.suffix1)), empty2, wrap1(elem), this.length0 + 1 + ); else if(suffix4.length < WIDTH - 1) return new TreeList5<>( + this.prefix1, this.len1, this.prefix2, this.len12, this.prefix3, this.len123, this.prefix4, this.len1234, this.data5, + copyAppend(this.suffix4, copyAppend(this.suffix3, copyAppend(this.suffix2, this.suffix1))), empty3, empty2, wrap1(elem), + this.length0 + 1 + ); else if(data5.length < WIDTH - 2) return new TreeList5<>( + this.prefix1, this.len1, this.prefix2, this.len12, this.prefix3, this.len123, this.prefix4, this.len1234, + copyAppend(this.data5, copyAppend(this.suffix4, copyAppend(this.suffix3, copyAppend(this.suffix2, this.suffix1)))), + empty4, empty3, empty2, wrap1(elem), this.length0 + 1 + ); else return new TreeList6<>( + this.prefix1, this.len1, this.prefix2, this.len12, this.prefix3, this.len123, this.prefix4, this.len1234, + this.data5, (WIDTH - 2) * WIDTH4 + this.len1234, empty6, + wrap5(copyAppend(this.suffix4, copyAppend(this.suffix3, copyAppend(this.suffix2, this.suffix1)))), + empty4, empty3, empty2, wrap1(elem), + this.length0 + 1 + ); + } + + @Override + public TreeList prepended(A elem) { + if(this.len1 < WIDTH) return new TreeList5<>( + copyPrepend1(elem, this.prefix1), this.len1 + 1, this.prefix2, this.len12 + 1, + this.prefix3, this.len123 + 1, this.prefix4, this.len1234 + 1, + this.data5, this.suffix4, this.suffix3, this.suffix2, this.suffix1, + this.length0 + 1 + ); else if(this.len12 < WIDTH2) return new TreeList5<>( + wrap1(elem), 1, copyPrepend(this.prefix1, this.prefix2), this.len12 + 1, + this.prefix3, this.len123 + 1, this.prefix4, this.len1234 + 1, + this.data5, this.suffix4, this.suffix3, this.suffix2, this.suffix1, + this.length0 + 1 + ); else if(this.len123 < WIDTH3) return new TreeList5<>( + wrap1(elem), 1, empty2, 1, + copyPrepend(copyPrepend(this.prefix1, this.prefix2), this.prefix3), this.len123 + 1, + this.prefix4, this.len1234 + 1, this.data5, + this.suffix4, this.suffix3, this.suffix2, this.suffix1, + this.length0 + 1 + ); else if(this.len1234 < WIDTH4) return new TreeList5<>( + wrap1(elem), 1, empty2, 1, empty3, 1, + copyPrepend(copyPrepend(copyPrepend(this.prefix1, this.prefix2), this.prefix3), this.prefix4), this.len1234 + 1, + this.data5, this.suffix4, this.suffix3, this.suffix2, this.suffix1, + this.length0 + 1 + ); else if(this.data5.length < WIDTH - 2) return new TreeList5<>( + wrap1(elem), 1, empty2, 1, empty3, 1, empty4, 1, + copyPrepend(copyPrepend(copyPrepend(copyPrepend(this.prefix1, this.prefix2), this.prefix3), this.prefix4), this.data5), + this.suffix4, this.suffix3, this.suffix2, this.suffix1, + this.length0 + 1 + ); else return new TreeList6<>( + wrap1(elem), 1, empty2, 1, empty3, 1, empty4, 1, + wrap5(copyPrepend(copyPrepend(copyPrepend(this.prefix1, this.prefix2), this.prefix3), this.prefix4)), this.len1234 + 1, + empty6, this.data5, this.suffix4, this.suffix3, this.suffix2, this.suffix1, this.length0 + 1 + ); + } + + @Override + public TreeList map(Function f) { + return new TreeList5<>( + mapElems1(this.prefix1, f), this.len1, + mapElems(2, this.prefix2, f), this.len12, + mapElems(3, this.prefix3, f), this.len123, + mapElems(4, this.prefix4, f), this.len1234, + mapElems(5, this.data5, f), + mapElems(4, this.suffix4, f), mapElems(3, this.suffix3, f), mapElems(2, this.suffix2, f), mapElems1(this.suffix1, f), + this.length0 + ); + } + + @Override + protected TreeList slice0(int lo, int hi) { + final VectorSliceBuilder b = new VectorSliceBuilder(lo, hi); + b.consider(1, prefix1); + b.consider(2, prefix2); + b.consider(3, prefix3); + b.consider(4, prefix4); + b.consider(5, data5); + b.consider(4, suffix4); + b.consider(3, suffix3); + b.consider(2, suffix2); + b.consider(1, suffix1); + return b.result(); + } + + @Override + public TreeList tail() { + if(this.len1 > 1) return new TreeList5<>( + copyTail(this.prefix1), this.len1 - 1, + this.prefix2, this.len12 - 1, + this.prefix3, this.len123 - 1, + this.prefix4, this.len1234 - 1, + this.data5, this.suffix4, this.suffix3, this.suffix2, this.suffix1, + this.length0 - 1 + ); else return this.slice0(1, this.length0); + } + + @Override + public TreeList init() { + if(this.suffix1.length > 1) return new TreeList5<>( + this.prefix1, this.len1, this.prefix2, this.len12, + this.prefix3, this.len123, this.prefix4, this.len1234, + this.data5, this.suffix4, this.suffix3, this.suffix2, + copyInit(this.suffix1), this.length0 - 1 + ); else return this.slice(0, this.length0 - 1); + } + + @Override + int vectorSliceCount() { + return 9; + } + + @Override + Object[] vectorSlice(int idx) { + return switch(idx) { + case 0 -> this.prefix1; + case 1 -> this.prefix2; + case 2 -> this.prefix3; + case 3 -> this.prefix4; + case 4 -> this.data5; + case 5 -> this.suffix4; + case 6 -> this.suffix3; + case 7 -> this.suffix2; + case 8 -> this.suffix1; + default -> throw new IllegalArgumentException("slice idx " + idx); + }; + } + + @Override + int vectorSlicePrefixLength(int idx) { + return switch(idx) { + case 0 -> this.len1; + case 1 -> this.len12; + case 2 -> this.len123; + case 3 -> this.len1234; + case 4 -> this.len1234 + this.data5.length * WIDTH4; + case 5 -> this.len1234 + this.data5.length * WIDTH4 + this.suffix4.length * WIDTH3; + case 6 -> this.len1234 + this.data5.length * WIDTH4 + this.suffix4.length * WIDTH3 + this.suffix3.length * WIDTH2; + case 7 -> this.length0 - this.suffix1.length; + case 8 -> this.length0; + default -> throw new IllegalArgumentException("slice idx " + idx); + }; + } + + @Override + protected TreeList prependedAll0(Iterable prefix, int k) { + Object[] prefix1b = prepend1IfSpace(this.prefix1, prefix); + if(prefix1b == null) return super.prependedAll0(prefix, k); + else { + final int diff = prefix1b.length - this.prefix1.length; + return new TreeList5<>( + prefix1b, this.len1 + diff, + this.prefix2, this.len12 + diff, + this.prefix3, this.len123 + diff, + this.prefix4, this.len1234 + diff, + this.data5, this.suffix4, this.suffix3, this.suffix2, this.suffix1, + this.length0 + diff + ); + } + } + + @Override + protected TreeList appendedAll0(Iterable suffix, int k) { + Object[] suffix1b = append1IfSpace(this.suffix1, suffix); + if(suffix1b != null) return new TreeList5<>( + this.prefix1, this.len1, this.prefix2, this.len12, + this.prefix3, this.len123, this.prefix4, this.len1234, + this.data5, this.suffix4, this.suffix3, this.suffix2, + suffix1b, this.length0 - this.suffix1.length + suffix1b.length + ); else return super.appendedAll0(suffix, k); + } + } + + private static final class TreeList6 extends BigTreeList { + + final int len1; + final Object[][] prefix2; + final int len12; + final Object[][][] prefix3; + final int len123; + final Object[][][][] prefix4; + final int len1234; + final Object[][][][][] prefix5; + final int len12345; + final Object[][][][][][] data6; + final Object[][][][][] suffix5; + final Object[][][][] suffix4; + final Object[][][] suffix3; + final Object[][] suffix2; + + TreeList6( + Object[] prefix1, int len1, + Object[][] prefix2, int len12, + Object[][][] prefix3, int len123, + Object[][][][] prefix4, int len1234, + Object[][][][][] prefix5, int len12345, + Object[][][][][][] data6, + Object[][][][][] suffix5, Object[][][][] suffix4, Object[][][] suffix3, Object[][] suffix2, Object[] suffix1, + int length0 + ) { + super(prefix1, suffix1, length0); + this.len1 = len1; + this.prefix2 = prefix2; + this.len12 = len12; + this.prefix3 = prefix3; + this.len123 = len123; + this.prefix4 = prefix4; + this.len1234 = len1234; + this.prefix5 = prefix5; + this.len12345 = len12345; + this.data6 = data6; + this.suffix5 = suffix5; + this.suffix4 = suffix4; + this.suffix3 = suffix3; + this.suffix2 = suffix2; + } + + @Override + public A get(int i) { + if(i >= 0 && i < this.length0) { + final int io_ = i - this.len12345; + if(io_ >= 0) { + final int i6 = io_ >>> BITS5; + final int i5 = (io_ >>> BITS4) & MASK; + final int i4 = (io_ >>> BITS3) & MASK; + final int i3 = (io_ >>> BITS2) & MASK; + final int i2 = (io_ >>> BITS) & MASK; + final int i1 = io_ & MASK; + if(i6 < this.data6.length) return (A) this.data6[i6][i5][i4][i3][i2][i1]; + else if(i5 < this.suffix5.length) return (A) this.suffix5[i5][i4][i3][i2][i1]; + else if(i4 < this.suffix4.length) return (A) this.suffix4[i4][i3][i2][i1]; + else if(i3 < this.suffix3.length) return (A) this.suffix3[i3][i2][i1]; + else if(i2 < this.suffix2.length) return (A) this.suffix2[i2][i1]; + else return (A) this.suffix1[i1]; + } else if(i >= this.len1234) { + final int io = i - this.len1234; + return (A) this.prefix5[io >>> BITS4][(io >>> BITS3) & MASK][(io >>> BITS2) & MASK][(io >>> BITS) & MASK][io & MASK]; + } else if(i >= this.len123) { + final int io = i - this.len123; + return (A) this.prefix4[io >>> BITS3][(io >>> BITS2) & MASK][(io >>> BITS) & MASK][io & MASK]; + } else if(i >= this.len12) { + final int io = i - this.len12; + return (A) this.prefix3[io >>> BITS2][(io >>> BITS) & MASK][io & MASK]; + } else if(i >= this.len1) { + final int io = i - this.len1; + return (A) this.prefix2[io >>> BITS][io & MASK]; + } else return (A) this.prefix1[i]; + } else throw ioob(i); + } + + @Override + public TreeList updated(int index, A elem) { + if(index >= 0 && index < this.length0) { + if(index >= this.len12345) { + final int io = index - this.len12345; + final int i6 = io >>> BITS5; + final int i5 = (io >>> BITS4) & MASK; + final int i4 = (io >>> BITS3) & MASK; + final int i3 = (io >>> BITS2) & MASK; + final int i2 = (io >>> BITS) & MASK; + final int i1 = io & MASK; + if(i6 < this.data6.length) return new TreeList6<>( + this.prefix1, this.len1, + this.prefix2, this.len12, + this.prefix3, this.len123, + this.prefix4, this.len1234, + this.prefix5, this.len12345, + copyUpdate(this.data6, i6, i5, i4, i3, i2, i1, elem), + this.suffix5, this.suffix4, this.suffix3, this.suffix2, this.suffix1, + this.length0 + ); else if(i5 < suffix5.length) return new TreeList6<>( + this.prefix1, this.len1, + this.prefix2, this.len12, + this.prefix3, this.len123, + this.prefix4, this.len1234, + this.prefix5, this.len12345, + this.data6, + copyUpdate(this.suffix5, i5, i4, i3, i2, i1, elem), this.suffix4, this.suffix3, this.suffix2, this.suffix1, + this.length0 + ); else if(i4 < suffix4.length) return new TreeList6<>( + this.prefix1, this.len1, + this.prefix2, this.len12, + this.prefix3, this.len123, + this.prefix4, this.len1234, + this.prefix5, this.len12345, + this.data6, + this.suffix5, copyUpdate(this.suffix4, i4, i3, i2, i1, elem), this.suffix3, this.suffix2, this.suffix1, + this.length0 + ); else if(i3 < suffix3.length) return new TreeList6<>( + this.prefix1, this.len1, + this.prefix2, this.len12, + this.prefix3, this.len123, + this.prefix4, this.len1234, + this.prefix5, this.len12345, + this.data6, + this.suffix5, this.suffix4, copyUpdate(this.suffix3, i3, i2, i1, elem), this.suffix2, this.suffix1, + this.length0 + ); else if(i2 < suffix2.length) return new TreeList6<>( + this.prefix1, this.len1, + this.prefix2, this.len12, + this.prefix3, this.len123, + this.prefix4, this.len1234, + this.prefix5, this.len12345, + this.data6, + this.suffix5, this.suffix4, this.suffix3, copyUpdate(this.suffix2, i2, i1, elem), this.suffix1, + this.length0 + ); else return new TreeList6<>( + this.prefix1, this.len1, + this.prefix2, this.len12, + this.prefix3, this.len123, + this.prefix4, this.len1234, + this.prefix5, this.len12345, + this.data6, + this.suffix5, this.suffix4, this.suffix3, this.suffix2, copyUpdate(this.suffix1, i1, elem), + this.length0 + ); + } else if(index >= this.len1234) { + final int io = index - this.len1234; + return new TreeList6<>( + this.prefix1, this.len1, + this.prefix2, this.len12, + this.prefix3, this.len123, + this.prefix4, this.len1234, + copyUpdate(this.prefix5, io >>> BITS4, (io >>> BITS3) & MASK, (io >>> BITS2) & MASK, (io >>> BITS) & MASK, io & MASK, elem), this.len12345, + this.data6, + this.suffix5, this.suffix4, this.suffix3, this.suffix2, this.suffix1, + this.length0 + ); + } else if(index >= this.len123) { + final int io = index - this.len123; + return new TreeList6<>( + this.prefix1, this.len1, + this.prefix2, this.len12, + this.prefix3, this.len123, + copyUpdate(this.prefix4,io >>> BITS3, (io >>> BITS2) & MASK, (io >>> BITS) & MASK, io & MASK, elem), this.len1234, + this.prefix5, this.len12345, + this.data6, + this.suffix5, this.suffix4, this.suffix3, this.suffix2, this.suffix1, + this.length0 + ); + } else if(index >= this.len12) { + final int io = index - this.len12; + return new TreeList6<>( + this.prefix1, this.len1, + this.prefix2, this.len12, + copyUpdate(this.prefix3,io >>> BITS2, (io >>> BITS) & MASK, io & MASK, elem), this.len123, + this.prefix4, this.len1234, + this.prefix5, this.len12345, + this.data6, + this.suffix5, this.suffix4, this.suffix3, this.suffix2, this.suffix1, + this.length0 + ); + } else if(index >= this.len1) { + final int io = index - this.len1; + return new TreeList6<>( + this.prefix1, this.len1, + copyUpdate(this.prefix2,io >>> BITS, io & MASK, elem), this.len12, + this.prefix3, this.len123, + this.prefix4, this.len1234, + this.prefix5, this.len12345, + this.data6, + this.suffix5, this.suffix4, this.suffix3, this.suffix2, this.suffix1, + this.length0 + ); + } else return new TreeList6<>( + copyUpdate(this.prefix1, index, elem), this.len1, + this.prefix2, this.len12, + this.prefix3, this.len123, + this.prefix4, this.len1234, + this.prefix5, this.len12345, + this.data6, + this.suffix5, this.suffix4, this.suffix3, this.suffix2, this.suffix1, + this.length0 + ); + } else throw ioob(index); + } + + @Override + public TreeList appended(A elem) { + if(suffix1.length < WIDTH) return new TreeList6<>( + this.prefix1, this.len1, this.prefix2, this.len12, + this.prefix3, this.len123, this.prefix4, this.len1234, + this.prefix5, this.len12345, this.data6, + this.suffix5, this.suffix4, this.suffix3, this.suffix2, + copyAppend1(this.suffix1, elem), this.length0 + 1 + ); else if(suffix2.length < WIDTH - 1) return new TreeList6<>( + this.prefix1, this.len1, this.prefix2, this.len12, + this.prefix3, this.len123, this.prefix4, this.len1234, + this.prefix5, this.len12345, this.data6, + this.suffix5, this.suffix4, this.suffix3, + copyAppend(this.suffix2, this.suffix1), wrap1(elem), this.length0 + 1 + ); else if(suffix3.length < WIDTH - 1) return new TreeList6<>( + this.prefix1, this.len1, this.prefix2, this.len12, + this.prefix3, this.len123, this.prefix4, this.len1234, + this.prefix5, this.len12345, this.data6, + this.suffix5, this.suffix4, + copyAppend(this.suffix3, copyAppend(this.suffix2, this.suffix1)), empty2, wrap1(elem), this.length0 + 1 + ); else if(suffix4.length < WIDTH - 1) return new TreeList6<>( + this.prefix1, this.len1, this.prefix2, this.len12, + this.prefix3, this.len123, this.prefix4, this.len1234, + this.prefix5, this.len12345, this.data6, + this.suffix5, + copyAppend(this.suffix4, copyAppend(this.suffix3, copyAppend(this.suffix2, this.suffix1))), + empty3, empty2, wrap1(elem), this.length0 + 1 + ); else if(suffix5.length < WIDTH - 1) return new TreeList6<>( + this.prefix1, this.len1, this.prefix2, this.len12, + this.prefix3, this.len123, this.prefix4, this.len1234, + this.prefix5, this.len12345, this.data6, + copyAppend(this.suffix5, copyAppend(this.suffix4, copyAppend(this.suffix3, copyAppend(this.suffix2, this.suffix1)))), + empty4, empty3, empty2, wrap1(elem), this.length0 + 1 + ); else if(data6.length < LASTWIDTH - 2) return new TreeList6<>( + this.prefix1, this.len1, this.prefix2, this.len12, + this.prefix3, this.len123, this.prefix4, this.len1234, + this.prefix5, this.len12345, + copyAppend(this.data6, copyAppend(this.suffix5, copyAppend(this.suffix4, copyAppend(this.suffix3, copyAppend(this.suffix2, this.suffix1))))), + empty5, empty4, empty3, empty2, wrap1(elem), this.length0 + 1 + ); else throw new IllegalArgumentException(); + } + + @Override + public TreeList prepended(A elem) { + if(this.len1 < WIDTH) return new TreeList6<>( + copyPrepend1(elem, this.prefix1), this.len1 + 1, + this.prefix2, this.len12 + 1, + this.prefix3, this.len123 + 1, + this.prefix4, this.len1234 + 1, + this.prefix5, this.len12345 + 1, + this.data6, this.suffix5, this.suffix4, this.suffix3, this.suffix2, this.suffix1, + this.length0 + 1 + ); else if(this.len12 < WIDTH2) return new TreeList6<>( + wrap1(elem), 1, + copyPrepend(this.prefix1, this.prefix2), this.len12 + 1, + this.prefix3, this.len123 + 1, + this.prefix4, this.len1234 + 1, + this.prefix5, this.len12345 + 1, + this.data6, this.suffix5, this.suffix4, this.suffix3, this.suffix2, this.suffix1, + this.length0 + 1 + ); else if(this.len123 < WIDTH3) return new TreeList6<>( + wrap1(elem), 1, + empty2, 1, + copyPrepend(copyPrepend(this.prefix1, this.prefix2), this.prefix3), this.len123 + 1, + this.prefix4, this.len1234 + 1, + this.prefix5, this.len12345 + 1, + this.data6, this.suffix5, this.suffix4, this.suffix3, this.suffix2, this.suffix1, + this.length0 + 1 + ); else if(this.len1234 < WIDTH4) return new TreeList6<>( + wrap1(elem), 1, + empty2, 1, + empty3, 1, + copyPrepend(copyPrepend(copyPrepend(this.prefix1, this.prefix2), this.prefix3), this.prefix4), this.len1234 + 1, + this.prefix5, this.len12345 + 1, + this.data6, this.suffix5, this.suffix4, this.suffix3, this.suffix2, this.suffix1, + this.length0 + 1 + ); else if(this.len12345 < WIDTH5) return new TreeList6<>( + wrap1(elem), 1, + empty2, 1, + empty3, 1, + empty4, 1, + copyPrepend(copyPrepend(copyPrepend(copyPrepend(this.prefix1, this.prefix2), this.prefix3), this.prefix4), this.prefix5), this.len12345 + 1, + this.data6, this.suffix5, this.suffix4, this.suffix3, this.suffix2, this.suffix1, + this.length0 + 1 + ); else if(data6.length < LASTWIDTH - 2) return new TreeList6<>( + wrap1(elem), 1, + empty2, 1, + empty3, 1, + empty4, 1, + empty5, 1, + copyPrepend(copyPrepend(copyPrepend(copyPrepend(copyPrepend(this.prefix1, this.prefix2), this.prefix3), this.prefix4), this.prefix5), this.data6), + this.suffix5, this.suffix4, this.suffix3, this.suffix2, this.suffix1, + this.length0 + 1 + ); else throw new IllegalArgumentException(); + } + + @Override + public TreeList map(Function f) { + return new TreeList6<>( + mapElems1(this.prefix1, f), this.len1, + mapElems(2, this.prefix2, f), this.len12, + mapElems(3, this.prefix3, f), this.len123, + mapElems(4, this.prefix4, f), this.len1234, + mapElems(5, this.prefix5, f), this.len12345, + mapElems(6, this.data6, f), + mapElems(5, this.suffix5, f), mapElems(4, this.suffix4, f), mapElems(3, this.suffix3, f), mapElems(2, this.suffix2, f), mapElems1(this.suffix1, f), + this.length0 + ); + } + + @Override + protected TreeList slice0(int lo, int hi) { + final VectorSliceBuilder b = new VectorSliceBuilder(lo, hi); + b.consider(1, this.prefix1); + b.consider(2, this.prefix2); + b.consider(3, this.prefix3); + b.consider(4, this.prefix4); + b.consider(5, this.prefix5); + b.consider(6, this.data6); + b.consider(5, this.suffix5); + b.consider(4, this.suffix4); + b.consider(3, this.suffix3); + b.consider(2, this.suffix2); + b.consider(1, this.suffix1); + return b.result(); + } + + @Override + public TreeList tail() { + if(this.len1 > 1) return new TreeList6<>( + copyTail(this.prefix1), this.len1 - 1, + this.prefix2, this.len12 - 1, + this.prefix3, this.len123 - 1, + this.prefix4, this.len1234 - 1, + this.prefix5, this.len12345 - 1, + this.data6, this.suffix5, this.suffix4, this.suffix3, this.suffix2, this.suffix1, + this.length0 - 1 + ); else return this.slice0(1, this.length0); + } + + @Override + public TreeList init() { + if(this.suffix1.length > 1) return new TreeList6<>( + this.prefix1, this.len1, this.prefix2, this.len12, + this.prefix3, this.len123, this.prefix4, this.len1234, + this.prefix5, this.len12345, this.data6, + this.suffix5, this.suffix4, this.suffix3, this.suffix2, + copyInit(this.suffix1), this.length0 - 1 + ); else return this.slice0(0, this.length0 - 1); + } + + @Override + int vectorSliceCount() { + return 11; + } + + @Override + Object[] vectorSlice(int idx) { + return switch(idx) { + case 0 -> this.prefix1; + case 1 -> this.prefix2; + case 2 -> this.prefix3; + case 3 -> this.prefix4; + case 4 -> this.prefix5; + case 5 -> this.data6; + case 6 -> this.suffix5; + case 7 -> this.suffix4; + case 8 -> this.suffix3; + case 9 -> this.suffix2; + case 10 -> this.suffix1; + default -> throw new IllegalArgumentException("slice idx " + idx); + }; + } + + @Override + int vectorSlicePrefixLength(int idx) { + return switch(idx) { + case 0 -> this.len1; + case 1 -> this.len12; + case 2 -> this.len123; + case 3 -> this.len1234; + case 4 -> this.len12345; + case 5 -> this.len12345 + this.data6.length * WIDTH5; + case 6 -> this.len12345 + this.data6.length * WIDTH5 + this.suffix5.length * WIDTH4; + case 7 -> this.len12345 + this.data6.length * WIDTH5 + this.suffix5.length * WIDTH4 + this.suffix4.length * WIDTH3; + case 8 -> this.len12345 + this.data6.length * WIDTH5 + this.suffix5.length * WIDTH4 + this.suffix4.length * WIDTH3 + this.suffix3.length * WIDTH2; + case 9 -> this.length0 - this.suffix1.length; + case 10 -> this.length0; + default -> throw new IllegalArgumentException("slice idx " + idx); + }; + } + + @Override + protected TreeList prependedAll0(Iterable prefix, int k) { + final Object[] prefix1b = prepend1IfSpace(this.prefix1, prefix); + if(prefix1b == null) return super.prependedAll0(prefix, k); + else { + final int diff = prefix1b.length - this.prefix1.length; + return new TreeList6<>( + prefix1b, this.len1 + diff, + this.prefix2, this.len12 + diff, + this.prefix3, this.len123 + diff, + this.prefix4, this.len1234 + diff, + this.prefix5, this.len12345 + diff, + this.data6, this.suffix5, this.suffix4, this.suffix3, this.suffix2, this.suffix1, + this.length0 + diff + ); + } + } + + @Override + protected TreeList appendedAll0(Iterable suffix, int k) { + final Object[] suffix1b = append1IfSpace(this.suffix1, suffix); + if(suffix1b != null) return new TreeList6<>( + this.prefix1, this.len1, this.prefix2, this.len12, + this.prefix3, this.len123, this.prefix4, this.len1234, + this.prefix5, this.len12345, this.data6, + this.suffix5, this.suffix4, this.suffix3, this.suffix2, + suffix1b, this.length0 - this.suffix1.length + suffix1b.length + ); else return super.appendedAll0(suffix, k); + } + } + + private static final class VectorSliceBuilder { + private static int prefixIdx(int n) { + return n - 1; + } + + private static int suffixIdx(int n) { + return 11 - n; + } + + private final int lo; + private final int hi; + + private final Object[][] slices; + private int len; + private int pos; + private int maxDim; + + public VectorSliceBuilder(int lo, int hi) { + this.lo = lo; + this.hi = hi; + this.slices = new Object[11][]; + this.len = 0; + this.pos = 0; + this.maxDim = 0; + } + + public void consider(int n, T[] a) { + final int count = a.length * (1 << (BITS * (n - 1))); + final int lo0 = Math.max(this.lo - this.pos, 0); + final int hi0 = Math.min(this.hi - this.pos, count); + if(hi0 > lo0) { + this.addSlice(n, a, lo0, hi0); + this.len += (hi0 - lo0); + } + this.pos += count; + } + + private void addSlice(int n, T[] a, int lo, int hi) { + if(n == 1) { + this.add(1, copyOrUse(a, lo, hi)); + } else { + final int bitsN = BITS * (n - 1); + final int widthN = 1 << bitsN; + final int loN = lo >>> bitsN; + final int hiN = hi >>> bitsN; + final int loRest = lo & (widthN - 1); + final int hiRest = hi & (widthN - 1); + + if(loRest == 0) { + if(hiRest == 0) { + this.add(n, copyOrUse(a, loN, hiN)); + } else { + if(hiN > loN) this.add(n, copyOrUse(a, loN, hiN)); + this.addSlice(n - 1, (Object[]) a[hiN], 0, hiRest); + } + } else { + if(hiN == loN) { + this.addSlice(n - 1, (Object[]) a[loN], loRest, hiRest); + } else { + this.addSlice(n - 1, (Object[]) a[loN], loRest, widthN); + if(hiRest == 0) { + if(hiN > loN + 1) this.add(n, copyOrUse(a, loN + 1, hiN)); + } else { + if(hiN > loN + 1) this.add(n, copyOrUse(a, loN + 1, hiN)); + this.addSlice(n - 1, (Object[]) a[hiN], 0, hiRest); + } + } + } + } + } + + private void add(int n, T[] a) { + final int idx; + if(n <= maxDim) idx = suffixIdx(n); + else { + maxDim = n; + idx = prefixIdx(n); + } + this.slices[idx] = a; + } + + public TreeList result() { + if(this.len <= 32) { + if(this.len == 0) return TreeList0.getInstance(); + else { + final Object[] prefix1 = slices[prefixIdx(1)]; + final Object[] suffix1 = slices[suffixIdx(1)]; + final Object[] a; + if(prefix1 != null) { + if(suffix1 != null) a = concatArrays(prefix1, suffix1); + else a = prefix1; + } else if(suffix1 != null) a = suffix1; + else { + final Object[][] prefix2 = (Object[][]) slices[prefixIdx(2)]; + if(prefix2 != null) a = prefix2[0]; + else { + final Object[][] suffix2 = (Object[][]) slices[suffixIdx(2)]; + a = suffix2[0]; + } + } + return new TreeList1<>(a); + } + } else { + this.balancePrefix(1); + this.balanceSuffix(1); + int resultDim = this.maxDim; + if(resultDim < 6) { + final Object[] pre = this.slices[prefixIdx(this.maxDim)]; + final Object[] suf = this.slices[suffixIdx(this.maxDim)]; + if(pre != null && suf != null) { + if(pre.length + suf.length <= WIDTH - 2) { + this.slices[prefixIdx(this.maxDim)] = concatArrays(pre, suf); + this.slices[suffixIdx(this.maxDim)] = null; + } else resultDim += 1; + } else { + final Object[] one = pre != null ? pre : suf; + if(one.length > WIDTH - 2) resultDim += 1; + } + } + final Object[] prefix1 = this.slices[prefixIdx(1)]; + final Object[] suffix1 = this.slices[suffixIdx(1)]; + final int len1 = prefix1.length; + return switch(resultDim) { + case 2 -> { + final Object[][] data2 = dataOr(2, empty2); + yield new TreeList2<>(prefix1, len1, data2, suffix1, this.len); + } + case 3 -> { + final Object[][] prefix2 = prefixOr(2, empty2); + final Object[][][] data3 = dataOr(3, empty3); + final Object[][] suffix2 = suffixOr(2, empty2); + final int len12 = len1 + (prefix2.length * WIDTH); + yield new TreeList3<>( + prefix1, len1, + prefix2, len12, + data3, + suffix2, suffix1, + this.len + ); + } + case 4 -> { + final Object[][] prefix2 = prefixOr(2, empty2); + final Object[][][] prefix3 = prefixOr(3, empty3); + final Object[][][][] data4 = dataOr(4, empty4); + final Object[][][] suffix3 = suffixOr(3, empty3); + final Object[][] suffix2 = suffixOr(2, empty2); + final int len12 = len1 + (prefix2.length * WIDTH); + final int len123 = len12 + (prefix3.length * WIDTH2); + yield new TreeList4<>( + prefix1, len1, + prefix2, len12, + prefix3, len123, + data4, + suffix3, suffix2, suffix1, + this.len + ); + } + case 5 -> { + final Object[][] prefix2 = prefixOr(2, empty2); + final Object[][][] prefix3 = prefixOr(3, empty3); + final Object[][][][] prefix4 = prefixOr(4, empty4); + final Object[][][][][] data5 = dataOr(5, empty5); + final Object[][][][] suffix4 = suffixOr(4, empty4); + final Object[][][] suffix3 = suffixOr(3, empty3); + final Object[][] suffix2 = suffixOr(2, empty2); + final int len12 = len1 + (prefix2.length * WIDTH); + final int len123 = len12 + (prefix3.length * WIDTH2); + final int len1234 = len123 + (prefix4.length * WIDTH3); + yield new TreeList5<>( + prefix1, len1, + prefix2, len12, + prefix3, len123, + prefix4, len1234, + data5, + suffix4, suffix3, suffix2, suffix1, + this.len + ); + } + case 6 -> { + final Object[][] prefix2 = prefixOr(2, empty2); + final Object[][][] prefix3 = prefixOr(3, empty3); + final Object[][][][] prefix4 = prefixOr(4, empty4); + final Object[][][][][] prefix5 = prefixOr(5, empty5); + final Object[][][][][][] data6 = dataOr(6, empty6); + final Object[][][][][] suffix5 = suffixOr(5, empty5); + final Object[][][][] suffix4 = suffixOr(4, empty4); + final Object[][][] suffix3 = suffixOr(3, empty3); + final Object[][] suffix2 = suffixOr(2, empty2); + final int len12 = len1 + (prefix2.length * WIDTH); + final int len123 = len12 + (prefix3.length * WIDTH2); + final int len1234 = len123 + (prefix4.length * WIDTH3); + final int len12345 = len1234 + (prefix5.length * WIDTH4); + yield new TreeList6<>( + prefix1, len1, prefix2, len12, + prefix3, len123, prefix4, len1234, + prefix5, len12345, + data6, + suffix5, suffix4, suffix3, suffix2, suffix1, + this.len + ); + } + default -> throw new IllegalStateException("bad resultDim " + resultDim); + }; + } + } + + private T[] prefixOr(int n, T[] a) { + final Object[] p = this.slices[prefixIdx(n)]; + return p != null ? (T[]) p : a; + } + + private T[] suffixOr(int n, T[] a) { + final Object[] s = this.slices[suffixIdx(n)]; + return s != null ? (T[]) s : a; + } + + private T[] dataOr(int n, T[] a) { + final Object[] p = this.slices[prefixIdx(n)]; + if(p != null) return (T[]) p; + else { + final Object[] s = this.slices[suffixIdx(n)]; + return s != null ? (T[]) s : a; + } + } + + private void balancePrefix(int n) { + if(this.slices[prefixIdx(n)] == null) { + if(n == this.maxDim) { + this.slices[prefixIdx(n)] = this.slices[suffixIdx(n)]; + this.slices[suffixIdx(n)] = null; + } else { + this.balancePrefix(n + 1); + final Object[][] preN1 = (Object[][]) this.slices[prefixIdx(n + 1)]; + this.slices[prefixIdx(n)] = preN1[0]; + if(preN1.length == 1) { + this.slices[prefixIdx(n + 1)] = null; + if((this.maxDim == n + 1) && (this.slices[suffixIdx(n + 1)] == null)) this.maxDim = n; + } else { + this.slices[prefixIdx(n + 1)] = copyOfRange(preN1, 1, preN1.length); + } + } + } + } + + private void balanceSuffix(int n) { + if(this.slices[suffixIdx(n)] == null) { + if(n == this.maxDim) { + slices[suffixIdx(n)] = this.slices[prefixIdx(n)]; + slices[prefixIdx(n)] = null; + } else { + this.balanceSuffix(n + 1); + final Object[][] sufN1 = (Object[][]) this.slices[suffixIdx(n + 1)]; + this.slices[suffixIdx(n)] = sufN1[sufN1.length - 1]; + if(sufN1.length == 1) { + this.slices[suffixIdx(n + 1)] = null; + if((this.maxDim == n + 1) && (this.slices[prefixIdx(n + 1)] == null)) this.maxDim = n; + } else { + this.slices[suffixIdx(n + 1)] = copyOfRange(sufN1, 0, sufN1.length - 1); + } + } + } + } + } + + public static final class VectorBuilder { + private Object[][][][][][] a6; + private Object[][][][][] a5; + private Object[][][][] a4; + private Object[][][] a3; + private Object[][] a2; + private Object[] a1; + private int len1; + private int lenRest; + private int offset; + private boolean prefixIsRightAligned; + private int depth; + + public VectorBuilder() { + this.clear(); + } + + private void setLen(int i) { + this.len1 = i & MASK; + this.lenRest = i - this.len1; + } + + public int size() { + return this.len1 + this.lenRest - this.offset; + } + + public void clear() { + this.a6 = null; + this.a5 = null; + this.a4 = null; + this.a3 = null; + this.a2 = null; + this.a1 = new Object[WIDTH]; + this.len1 = 0; + this.lenRest = 0; + this.offset = 0; + this.prefixIsRightAligned = false; + this.depth = 1; + } + + void initSparse(int size, A elem) { + this.setLen(size); + Arrays.fill(this.a1, elem); + if(size > WIDTH) { + this.a2 = new Object[WIDTH][]; + Arrays.fill(this.a2, this.a1); + if(size > WIDTH2) { + this.a3 = new Object[WIDTH][][]; + Arrays.fill(this.a3, this.a2); + if(size > WIDTH3) { + this.a4 = new Object[WIDTH][][][]; + Arrays.fill(this.a4, this.a3); + if(size > WIDTH4) { + this.a5 = new Object[WIDTH][][][][]; + Arrays.fill(this.a5, this.a4); + if(size > WIDTH5) { + this.a6 = new Object[WIDTH][][][][][]; + Arrays.fill(this.a6, this.a5); + this.depth = 6; + } else this.depth = 5; + } else this.depth = 4; + } else this.depth = 3; + } else this.depth = 2; + } else this.depth = 1; + } + + void initFrom(Object[] prefix1) { + this.depth = 1; + this.setLen(prefix1.length); + this.a1 = copyOrUse(prefix1, 0, WIDTH); + if(this.len1 == 0 && this.lenRest > 0) { + this.len1 = WIDTH; + this.lenRest -= WIDTH; + } + } + + VectorBuilder initFrom(TreeList v) { + switch(v.vectorSliceCount()) { + case 0 -> {} + case 1 -> { + final TreeList1 v1 = (TreeList1) v; + this.depth = 1; + this.setLen(v1.prefix1.length); + this.a1 = copyOrUse(v1.prefix1, 0, WIDTH); + } + case 3 -> { + final TreeList2 v2 = (TreeList2) v; + final Object[][] d2 = v2.data2; + this.a1 = copyOrUse(v2.suffix1, 0, WIDTH); + this.depth = 2; + this.offset = WIDTH - v2.len1; + this.setLen(v2.length0 + this.offset); + this.a2 = new Object[WIDTH][]; + this.a2[0] = v2.prefix1; + System.arraycopy(d2, 0, this.a2, 1, d2.length); + this.a2[d2.length + 1] = this.a1; + } + case 5 -> { + final TreeList3 v3 = (TreeList3) v; + final Object[][][] d3 = v3.data3; + final Object[][] s2 = v3.suffix2; + this.a1 = copyOrUse(v3.suffix1, 0, WIDTH); + this.depth = 3; + this.offset = WIDTH2 - v3.len12; + this.setLen(v3.length0 + this.offset); + this.a3 = new Object[WIDTH][][]; + this.a3[0] = copyPrepend(v3.prefix1, v3.prefix2); + System.arraycopy(d3, 0, this.a3, 1, d3.length); + this.a2 = copyOf(s2, WIDTH); + this.a3[d3.length + 1] = this.a2; + this.a2[s2.length] = this.a1; + } + case 7 -> { + final TreeList4 v4 = (TreeList4) v; + final Object[][][][] d4 = v4.data4; + final Object[][][] s3 = v4.suffix3; + final Object[][] s2 = v4.suffix2; + this.a1 = copyOrUse(v4.suffix1, 0, WIDTH); + this.depth = 4; + this.offset = WIDTH3 - v4.len123; + this.setLen(v4.length0 + this.offset); + this.a4 = new Object[WIDTH][][][]; + this.a4[0] = copyPrepend(copyPrepend(v4.prefix1, v4.prefix2), v4.prefix3); + System.arraycopy(d4, 0, this.a4, 1, d4.length); + this.a3 = copyOf(s3, WIDTH); + this.a2 = copyOf(s2, WIDTH); + this.a4[d4.length + 1] = this.a3; + this.a3[s3.length] = this.a2; + this.a2[s2.length] = this.a1; + } + case 9 -> { + final TreeList5 v5 = (TreeList5) v; + final Object[][][][][] d5 = v5.data5; + final Object[][][][] s4 = v5.suffix4; + final Object[][][] s3 = v5.suffix3; + final Object[][] s2 = v5.suffix2; + this.a1 = copyOrUse(v5.suffix1, 0, WIDTH); + this.depth = 5; + this.offset = WIDTH4 - v5.len1234; + this.setLen(v5.length0 + this.offset); + this.a5 = new Object[WIDTH][][][][]; + this.a5[0] = copyPrepend(copyPrepend(copyPrepend(v5.prefix1, v5.prefix2), v5.prefix3), v5.prefix4); + System.arraycopy(d5, 0, this.a5, 1, d5.length); + this.a4 = copyOf(s4, WIDTH); + this.a3 = copyOf(s3, WIDTH); + this.a2 = copyOf(s2, WIDTH); + this.a5[d5.length + 1] = this.a4; + this.a4[s4.length] = this.a3; + this.a3[s3.length] = this.a2; + this.a2[s2.length] = this.a1; + } + case 11 -> { + final TreeList6 v6 = (TreeList6) v; + final Object[][][][][][] d6 = v6.data6; + final Object[][][][][] s5 = v6.suffix5; + final Object[][][][] s4 = v6.suffix4; + final Object[][][] s3 = v6.suffix3; + final Object[][] s2 = v6.suffix2; + this.a1 = copyOrUse(v6.suffix1, 0, WIDTH); + this.depth = 6; + this.offset = WIDTH5 - v6.len12345; + this.setLen(v6.length0 + this.offset); + this.a6 = new Object[WIDTH][][][][][]; + this.a6[0] = copyPrepend(copyPrepend(copyPrepend(copyPrepend(v6.prefix1, v6.prefix2), v6.prefix3), v6.prefix4), v6.prefix5); + System.arraycopy(d6, 0, this.a6, 1, d6.length); + this.a5 = copyOf(s5, WIDTH); + this.a4 = copyOf(s4, WIDTH); + this.a3 = copyOf(s3, WIDTH); + this.a2 = copyOf(s2, WIDTH); + this.a6[d6.length + 1] = this.a5; + this.a5[s5.length] = this.a4; + this.a4[s4.length] = this.a3; + this.a3[s3.length] = this.a2; + this.a2[s2.length] = this.a1; + } + } + if(this.len1 == 0 && this.lenRest > 0) { + this.len1 = WIDTH; + this.lenRest -= WIDTH; + } + return this; + } + + public VectorBuilder alignTo(int before, TreeList bigTreeList) { + if(this.len1 != 0 || this.lenRest != 0) + throw new UnsupportedOperationException("A non-empty VectorBuilder cannot be aligned retrospectively. Please call .reset() or use a new VectorBuilder."); + + final int prefixLength; + final int maxPrefixLength; + if(bigTreeList instanceof TreeList.TreeList0) { + prefixLength = 0; maxPrefixLength = 1; + } else if(bigTreeList instanceof TreeList.TreeList1) { + prefixLength = 0; maxPrefixLength = 1; + } else if(bigTreeList instanceof TreeList.TreeList2 v2) { + prefixLength = v2.len1; maxPrefixLength = WIDTH; + } else if(bigTreeList instanceof TreeList.TreeList3 v3) { + prefixLength = v3.len12; maxPrefixLength = WIDTH2; + } else if(bigTreeList instanceof TreeList.TreeList4 v4) { + prefixLength = v4.len123; maxPrefixLength = WIDTH3; + } else if(bigTreeList instanceof TreeList.TreeList5 v5) { + prefixLength = v5.len1234; maxPrefixLength = WIDTH4; + } else if(bigTreeList instanceof TreeList.TreeList6 v6) { + prefixLength = v6.len12345; maxPrefixLength = WIDTH5; + } else { + throw new IllegalStateException(); + } + + if(maxPrefixLength == 1) return this; + + final int overallPrefixLength = (before + prefixLength) % maxPrefixLength; + this.offset = (maxPrefixLength - overallPrefixLength) % maxPrefixLength; + + this.advanceN(this.offset & ~MASK); + this.len1 = this.offset & MASK; + this.prefixIsRightAligned = true; + return this; + } + + private void shrinkOffsetIfTooLarge(int width) { + final int newOffset = this.offset % width; + this.lenRest -= (offset - newOffset); + this.offset = newOffset; + } + + private void leftAlignPrefix() { + Object[] a = null; + Object[] aParent = null; + if(this.depth >= 6) { + a = this.a6; + final int i = this.offset >>> BITS5; + if(i > 0) System.arraycopy(a, i, a, 0, LASTWIDTH - i); + this.shrinkOffsetIfTooLarge(WIDTH5); + if((this.lenRest >>> BITS5) == 0) this.depth = 5; + aParent = a; + a = (Object[]) a[0]; + } + if(this.depth >= 5) { + if(a == null) a = this.a5; + final int i = (this.offset >>> BITS4) & MASK; + if(this.depth == 5) { + if(i > 0) System.arraycopy(a, i, a, 0, WIDTH - i); + this.a5 = (Object[][][][][]) a; + this.shrinkOffsetIfTooLarge(WIDTH4); + if((this.lenRest >>> BITS4) == 0) this.depth = 4; + } else { + if(i > 0) a = copyOfRange(a, i, WIDTH); + aParent[0] = a; + } + aParent = a; + a = (Object[]) a[0]; + } + if(this.depth >= 4) { + if(a == null) a = this.a4; + final int i = (this.offset >>> BITS3) & MASK; + if(this.depth == 4) { + if(i > 0) System.arraycopy(a, i, a, 0, WIDTH - i); + this.a4 = (Object[][][][]) a; + this.shrinkOffsetIfTooLarge(WIDTH3); + if((this.lenRest >>> BITS3) == 0) this.depth = 3; + } else { + if(i > 0) a = copyOfRange(a, i, WIDTH); + aParent[0] = a; + } + aParent = a; + a = (Object[]) a[0]; + } + if(this.depth >= 3) { + if(a == null) a = this.a3; + final int i = (this.offset >>> BITS2) & MASK; + if(this.depth == 3) { + if(i > 0) System.arraycopy(a, i, a, 0, WIDTH - i); + this.a3 = (Object[][][]) a; + this.shrinkOffsetIfTooLarge(WIDTH2); + if((this.lenRest >>> BITS2) == 0) this.depth = 2; + } else { + if(i > 0) a = copyOfRange(a, i, WIDTH); + aParent[0] = a; + } + aParent = a; + a = (Object[]) a[0]; + } + if(this.depth >= 2) { + if(a == null) a = this.a2; + final int i = (this.offset >>> BITS) & MASK; + if(this.depth == 2) { + if(i > 0) System.arraycopy(a, i, a, 0, WIDTH - i); + this.a2 = (Object[][]) a; + this.shrinkOffsetIfTooLarge(WIDTH); + if((this.lenRest >>> BITS) == 0) this.depth = 1; + } else { + if(i > 0) a = copyOfRange(a, i, WIDTH); + aParent[0] = a; + } + aParent = a; + a = (Object[]) a[0]; + } + if(this.depth >= 1) { + if(a == null) a = this.a1; + final int i = this.offset & MASK; + if(this.depth == 1) { + if(i > 0) System.arraycopy(a, i, a, 0, WIDTH - i); + this.a1 = (Object[]) a; + this.len1 -= this.offset; + this.offset = 0; + } else { + if(i > 0) a = copyOfRange(a, i, WIDTH); + aParent[0] = a; + } + } + this.prefixIsRightAligned = false; + } + + public VectorBuilder addOne(A elem) { + if(this.len1 == WIDTH) this.advance(); + this.a1[len1] = elem; + this.len1++; + return this; + } + + private void addArr1(Object[] data) { + final int dl = data.length; + if(dl > 0) { + if(this.len1 == WIDTH) this.advance(); + final int copy1 = Math.min(WIDTH - this.len1, dl); + final int copy2 = dl - copy1; + System.arraycopy(data, 0, this.a1, this.len1, copy1); + this.len1 += copy1; + if(copy2 > 0) { + this.advance(); + System.arraycopy(data, copy1, this.a1, 0, copy2); + this.len1 += copy2; + } + } + } + + private void addArrN(Object[] slice, int dim) { + if(slice.length == 0) return; + if(this.len1 == WIDTH) this.advance(); + + final int sl = slice.length; + switch(dim) { + case 2 -> { + final int copy1 = Math.min(((WIDTH2 - this.lenRest) >>> BITS) & MASK, sl); + final int copy2 = sl - copy1; + final int destPos = (this.lenRest >>> BITS) & MASK; + System.arraycopy((Object[][]) slice, 0, this.a2, destPos, copy1); + this.advanceN(WIDTH * copy1); + if(copy2 > 0) { + System.arraycopy((Object[][]) slice, copy1, this.a2, 0, copy2); + this.advanceN(WIDTH * copy2); + } + } + case 3 -> { + if(this.lenRest % WIDTH2 != 0) { + for(Object[][] e : (Object[][][]) slice) { + this.addArrN(e, 2); + } + } else { + final int copy1 = Math.min(((WIDTH3 - this.lenRest) >>> BITS2) & MASK, sl); + final int copy2 = sl - copy1; + final int destPos = (this.lenRest >>> BITS2) & MASK; + System.arraycopy((Object[][][]) slice, 0, this.a3, destPos, copy1); + this.advanceN(WIDTH2 * copy1); + if(copy2 > 0) { + System.arraycopy((Object[][][]) slice, copy1, this.a3, 0, copy2); + this.advanceN(WIDTH2 * copy2); + } + } + } + case 4 -> { + if(this.lenRest % WIDTH3 != 0) { + for(Object[][][] e : (Object[][][][]) slice) { + this.addArrN(e, 3); + } + } else { + final int copy1 = Math.min(((WIDTH4 - this.lenRest) >>> BITS3) & MASK, sl); + final int copy2 = sl - copy1; + final int destPos = (this.lenRest >>> BITS3) & MASK; + System.arraycopy((Object[][][][]) slice, 0, this.a4, destPos, copy1); + this.advanceN(WIDTH3 * copy1); + if(copy2 > 0) { + System.arraycopy((Object[][][][]) slice, copy1, this.a4, 0, copy2); + this.advanceN(WIDTH3 * copy2); + } + } + } + case 5 -> { + if(this.lenRest % WIDTH4 != 0) { + for(Object[][][][] e : (Object[][][][][]) slice) { + this.addArrN(e, 4); + } + } else { + final int copy1 = Math.min(((WIDTH5 - this.lenRest) >>> BITS4) & MASK, sl); + final int copy2 = sl - copy1; + final int destPos = (this.lenRest >>> BITS4) & MASK; + System.arraycopy((Object[][][][][]) slice, 0, this.a5, destPos, copy1); + this.advanceN(WIDTH4 * copy1); + if(copy2 > 0) { + System.arraycopy((Object[][][][][]) slice, copy1, this.a5, 0, copy2); + this.advanceN(WIDTH4 * copy2); + } + } + } + case 6 -> { + if(this.lenRest % WIDTH5 != 0) { + for(Object[][][][][] e : (Object[][][][][][]) slice) { + this.addArrN(e, 5); + } + } else { + final int copy1 = sl; + final int destPos = this.lenRest >>> BITS5; + if(destPos + copy1 > LASTWIDTH) + throw new IllegalArgumentException("exceeding 2^31 elements"); + System.arraycopy((Object[][][][][][]) slice, 0, this.a6, destPos, copy1); + this.advanceN(WIDTH5 * copy1); + } + } + default -> throw new IllegalArgumentException(); + } + } + + private VectorBuilder addVector(TreeList xs) { + final int sliceCount = xs.vectorSliceCount(); + for(int sliceIdx = 0; sliceIdx < sliceCount; sliceIdx++) { + final Object[] slice = xs.vectorSlice(sliceIdx); + final int n = vectorSliceDim(sliceCount, sliceIdx); + if(n == 1) this.addArr1(slice); + else if(this.len1 == WIDTH || this.len1 == 0) this.addArrN(slice, n); + else foreachRec(n - 2, slice, this::addArr1); + } + return this; + } + + public VectorBuilder addAll(Iterable xs) { + if(xs instanceof TreeList v) { + if(this.len1 == 0 && this.lenRest == 0 && !prefixIsRightAligned) this.initFrom(v); + else this.addVector((TreeList) v); + } else { + for(A a : xs) { + this.addOne(a); + } + } + return this; + } + + private void advance() { + final int idx = this.lenRest + WIDTH; + final int xor = idx ^ this.lenRest; + this.lenRest = idx; + this.len1 = 0; + this.advance1(idx, xor); + } + + private void advanceN(int n) { + if(n > 0) { + final int idx = this.lenRest + n; + final int xor = idx ^ this.lenRest; + this.lenRest = idx; + this.len1 = 0; + this.advance1(idx, xor); + } + } + + private void advance1(int idx, int xor) { + if(xor <= 0) { + // TODO actually print stuff like Scala stdlib does + throw new IllegalStateException(); + } else if(xor < WIDTH2) { + if(this.depth <= 1) { + this.a2 = new Object[WIDTH][]; + this.a2[0] = this.a1; + this.depth = 2; + } + this.a1 = new Object[WIDTH]; + this.a2[(idx >>> BITS) & MASK] = this.a1; + } else if(xor < WIDTH3) { + if(this.depth <= 2) { + this.a3 = new Object[WIDTH][][]; + this.a3[0] = this.a2; + this.depth = 3; + } + this.a1 = new Object[WIDTH]; + this.a2 = new Object[WIDTH][]; + this.a2[(idx >>> BITS) & MASK] = this.a1; + this.a3[(idx >>> BITS2) & MASK] = this.a2; + } else if(xor < WIDTH4) { + if(this.depth <= 3) { + this.a4 = new Object[WIDTH][][][]; + this.a4[0] = this.a3; + this.depth = 4; + } + this.a1 = new Object[WIDTH]; + this.a2 = new Object[WIDTH][]; + this.a3 = new Object[WIDTH][][]; + this.a2[(idx >>> BITS) & MASK] = this.a1; + this.a3[(idx >>> BITS2) & MASK] = this.a2; + this.a4[(idx >>> BITS3) & MASK] = this.a3; + } else if(xor < WIDTH5) { + if(this.depth <= 4) { + this.a5 = new Object[WIDTH][][][][]; + this.a5[0] = this.a4; + this.depth = 5; + } + this.a1 = new Object[WIDTH]; + this.a2 = new Object[WIDTH][]; + this.a3 = new Object[WIDTH][][]; + this.a4 = new Object[WIDTH][][][]; + this.a2[(idx >>> BITS) & MASK] = this.a1; + this.a3[(idx >>> BITS2) & MASK] = this.a2; + this.a4[(idx >>> BITS3) & MASK] = this.a3; + this.a5[(idx >>> BITS4) & MASK] = this.a4; + } else { + if(this.depth <= 5) { + this.a6 = new Object[LASTWIDTH][][][][][]; + this.a6[0] = this.a5; + this.depth = 6; + } + this.a1 = new Object[WIDTH]; + this.a2 = new Object[WIDTH][]; + this.a3 = new Object[WIDTH][][]; + this.a4 = new Object[WIDTH][][][]; + this.a5 = new Object[WIDTH][][][][]; + this.a2[(idx >>> BITS) & MASK] = this.a1; + this.a3[(idx >>> BITS2) & MASK] = this.a2; + this.a4[(idx >>> BITS3) & MASK] = this.a3; + this.a5[(idx >>> BITS4) & MASK] = this.a4; + this.a6[idx >>> BITS5] = this.a5; + } + } + + public TreeList result() { + if(this.prefixIsRightAligned) this.leftAlignPrefix(); + + final int len = this.len1 + this.lenRest; + final int realLen = len - this.offset; + if(realLen == 0) return TreeList.empty(); + else if(len < 0) throw new IndexOutOfBoundsException("Vector cannot have negative size " + len); + else if(len <= WIDTH) { + return new TreeList1<>(copyIfDifferentSize(this.a1, realLen)); + } else if(len <= WIDTH2) { + final int i1 = (len - 1) & MASK; + final int i2 = (len - 1) >>> BITS; + final Object[][] data = copyOfRange(this.a2, 1, i2); + final Object[] prefix1 = this.a2[0]; + final Object[] suffix1 = copyIfDifferentSize(this.a2[i2], i1 + 1); + return new TreeList2<>(prefix1, WIDTH - this.offset, data, suffix1, realLen); + } else if(len <= WIDTH3) { + final int i1 = (len - 1) & MASK; + final int i2 = ((len - 1) >>> BITS) & MASK; + final int i3 = (len - 1) >>> BITS2; + final Object[][][] data = copyOfRange(this.a3, 1, i3); + final Object[][] prefix2 = copyTail(this.a3[0]); + final Object[] prefix1 = this.a3[0][0]; + final Object[][] suffix2 = copyOf(this.a3[i3], i2); + final Object[] suffix1 = copyIfDifferentSize(this.a3[i3][i2], i1 + 1); + final int len1 = prefix1.length; + final int len12 = len1 + prefix2.length * WIDTH; + return new TreeList3<>(prefix1, len1, prefix2, len12, data, suffix2, suffix1, realLen); + } else if(len <= WIDTH4) { + final int i1 = (len - 1) & MASK; + final int i2 = ((len - 1) >>> BITS) & MASK; + final int i3 = ((len - 1) >>> BITS2) & MASK; + final int i4 = (len - 1) >>> BITS3; + final Object[][][][] data = copyOfRange(this.a4, 1, i4); + final Object[][][] prefix3 = copyTail(this.a4[0]); + final Object[][] prefix2 = copyTail(this.a4[0][0]); + final Object[] prefix1 = this.a4[0][0][0]; + final Object[][][] suffix3 = copyOf(this.a4[i4], i3); + final Object[][] suffix2 = copyOf(this.a4[i4][i3], i2); + final Object[] suffix1 = copyIfDifferentSize(this.a4[i4][i3][i2], i1 + 1); + final int len1 = prefix1.length; + final int len12 = len1 + prefix2.length * WIDTH; + final int len123 = len12 + prefix3.length * WIDTH2; + return new TreeList4<>( + prefix1, len1, prefix2, len12, prefix3, len123, data, suffix3, suffix2, suffix1, realLen + ); + } else if(len <= WIDTH5) { + final int i1 = (len - 1) & MASK; + final int i2 = ((len - 1) >>> BITS) & MASK; + final int i3 = ((len - 1) >>> BITS2) & MASK; + final int i4 = ((len - 1) >>> BITS3) & MASK; + final int i5 = (len - 1) >>> BITS4; + final Object[][][][][] data = copyOfRange(this.a5, 1, i5); + final Object[][][][] prefix4 = copyTail(this.a5[0]); + final Object[][][] prefix3 = copyTail(this.a5[0][0]); + final Object[][] prefix2 = copyTail(this.a5[0][0][0]); + final Object[] prefix1 = this.a5[0][0][0][0]; + final Object[][][][] suffix4 = copyOf(this.a5[i5], i4); + final Object[][][] suffix3 = copyOf(this.a5[i5][i4], i3); + final Object[][] suffix2 = copyOf(this.a5[i5][i4][i3], i2); + final Object[] suffix1 = copyIfDifferentSize(a5[i5][i4][i3][i2], i1 + 1); + final int len1 = prefix1.length; + final int len12 = len1 + prefix2.length * WIDTH; + final int len123 = len12 + prefix3.length * WIDTH2; + final int len1234 = len123 + prefix4.length * WIDTH3; + return new TreeList5<>( + prefix1, len1, prefix2, len12, prefix3, len123, prefix4, len1234, + data, suffix4, suffix3, suffix2, suffix1, realLen + ); + } else { + final int i1 = (len - 1) & MASK; + final int i2 = ((len - 1) >>> BITS) & MASK; + final int i3 = ((len - 1) >>> BITS2) & MASK; + final int i4 = ((len - 1) >>> BITS3) & MASK; + final int i5 = ((len - 1) >>> BITS4) & MASK; + final int i6 = (len - 1) >>> BITS5; + final Object[][][][][][] data = copyOfRange(this.a6, 1, i6); + final Object[][][][][] prefix5 = copyTail(this.a6[0]); + final Object[][][][] prefix4 = copyTail(this.a6[0][0]); + final Object[][][] prefix3 = copyTail(this.a6[0][0][0]); + final Object[][] prefix2 = copyTail(this.a6[0][0][0][0]); + final Object[] prefix1 = this.a6[0][0][0][0][0]; + final Object[][][][][] suffix5 = copyOf(this.a6[i6], i5); + final Object[][][][] suffix4 = copyOf(this.a6[i6][i5], i4); + final Object[][][] suffix3 = copyOf(this.a6[i6][i5][i4], i3); + final Object[][] suffix2 = copyOf(this.a6[i6][i5][i4][i3], i2); + final Object[] suffix1 = copyIfDifferentSize(this.a6[i6][i5][i4][i3][i2], i1 + 1); + final int len1 = prefix1.length; + final int len12 = len1 + prefix2.length * WIDTH; + final int len123 = len12 + prefix3.length * WIDTH2; + final int len1234 = len123 + prefix4.length * WIDTH3; + final int len12345 = len1234 + prefix5.length * WIDTH4; + return new TreeList6<>( + prefix1, len1, prefix2, len12, prefix3, len123, prefix4, len1234, + prefix5, len12345, data, suffix5, suffix4, suffix3, suffix2, suffix1, + realLen + ); + } + } + + } + + private static final class NewVectorIterator implements ListIterator { + + private final TreeList v; + private int totalLength; + private final int sliceCount; + + private Object[] a1; + private Object[][] a2; + private Object[][][] a3; + private Object[][][][] a4; + private Object[][][][][] a5; + private Object[][][][][][] a6; + private int a1len; + private int i1; + private int oldPos; + private int len1; + + private int sliceIdx; + private int sliceDim; + private int sliceStart; + private int sliceEnd; + + public NewVectorIterator(TreeList v, int totalLength, int sliceCount) { + this.v = v; + this.totalLength = totalLength; + this.sliceCount = sliceCount; + + this.a1 = v.prefix1; + this.a2 = null; + this.a3 = null; + this.a4 = null; + this.a5 = null; + this.a6 = null; + this.a1len = this.a1.length; + this.i1 = 0; + this.oldPos = 0; + this.len1 = this.totalLength; + + this.sliceIdx = 0; + this.sliceDim = 1; + this.sliceStart = 0; + this.sliceEnd = this.a1len; + } + + @Override + public boolean hasNext() { + return this.len1 > this.i1; + } + + @Override + public A next() { + if(this.i1 == this.a1len) this.advance(); + + final Object r = this.a1[this.i1]; + this.i1++; + return (A) r; + } + + private void advanceSlice() { + if(!this.hasNext()) Collections.emptyIterator().next(); + + this.sliceIdx++; + Object[] slice = v.vectorSlice(this.sliceIdx); + while(slice.length == 0) { + this.sliceIdx++; + slice = v.vectorSlice(this.sliceIdx); + } + + this.sliceStart = this.sliceEnd; + this.sliceDim = vectorSliceDim(this.sliceCount, this.sliceIdx); + switch(this.sliceDim) { + case 1 -> this.a1 = (Object[]) slice; + case 2 -> this.a2 = (Object[][]) slice; + case 3 -> this.a3 = (Object[][][]) slice; + case 4 -> this.a4 = (Object[][][][]) slice; + case 5 -> this.a5 = (Object[][][][][]) slice; + case 6 -> this.a6 = (Object[][][][][][]) slice; + } + this.sliceEnd = this.sliceStart + slice.length * (1 << (BITS * (this.sliceDim - 1))); + if(this.sliceEnd > this.totalLength) this.sliceEnd = this.totalLength; + if(this.sliceDim > 1) this.oldPos = (1 << (BITS * this.sliceDim)) - 1; + } + + private void advance() { + final int pos = this.i1 - this.len1 + this.totalLength; + if(pos == sliceEnd) this.advanceSlice(); + if(this.sliceDim > 1) { + final int io = pos - this.sliceStart; + final int xor = this.oldPos ^ io; + this.advanceA(io, xor); + this.oldPos = io; + } + this.len1 -= this.i1; + this.a1len = Math.min(this.a1.length, this.len1); + this.i1 = 0; + } + + private void advanceA(int io, int xor) { + if(xor < WIDTH2) { + this.a1 = this.a2[(io >>> BITS) & MASK]; + } else if(xor < WIDTH3) { + this.a2 = this.a3[(io >>> BITS2) & MASK]; + this.a1 = this.a2[0]; + } else if(xor < WIDTH4) { + this.a3 = this.a4[(io >>> BITS3) & MASK]; + this.a2 = this.a3[0]; + this.a1 = this.a2[0]; + } else if(xor < WIDTH5) { + this.a4 = this.a5[(io >>> BITS4) & MASK]; + this.a3 = this.a4[0]; + this.a2 = this.a3[0]; + this.a1 = this.a2[0]; + } else { + this.a5 = this.a6[io >>> BITS5]; + this.a4 = this.a5[0]; + this.a3 = this.a4[0]; + this.a2 = this.a3[0]; + this.a1 = this.a2[0]; + } + } + + private void setA(int io, int xor) { + if(xor < WIDTH2) { + this.a1 = this.a2[(io >>> BITS) & MASK]; + } else if(xor < WIDTH3) { + this.a2 = this.a3[(io >>> BITS2) & MASK]; + this.a1 = this.a2[(io >>> BITS) & MASK]; + } else if(xor < WIDTH4) { + this.a3 = this.a4[(io >>> BITS3) & MASK]; + this.a2 = this.a3[(io >>> BITS2) & MASK]; + this.a1 = this.a2[(io >>> BITS) & MASK]; + } else if(xor < WIDTH5) { + this.a4 = this.a5[(io >>> BITS4) & MASK]; + this.a3 = this.a4[(io >>> BITS3) & MASK]; + this.a2 = this.a3[(io >>> BITS2) & MASK]; + this.a1 = this.a2[(io >>> BITS) & MASK]; + } else { + this.a5 = this.a6[io >>> BITS5]; + this.a4 = this.a5[(io >>> BITS4) & MASK]; + this.a3 = this.a4[(io >>> BITS3) & MASK]; + this.a2 = this.a3[(io >>> BITS2) & MASK]; + this.a1 = this.a2[(io >>> BITS) & MASK]; + } + } + + @Override + public int nextIndex() { + throw new UnsupportedOperationException("Not yet implemented"); + } + + @Override + public boolean hasPrevious() { + throw new UnsupportedOperationException("Not yet implemented"); + } + + @Override + public A previous() { + throw new UnsupportedOperationException("Not yet implemented"); + } + + @Override + public int previousIndex() { + throw new UnsupportedOperationException("Not yet implemented"); + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Immutable collection"); + } + + @Override + public void set(A a) { + throw new UnsupportedOperationException("Immutable collection"); + } + + @Override + public void add(A a) { + throw new UnsupportedOperationException("Immutable collection"); + } + } + +} From 936f50fa3e9a739c4c63248d3150a88d1fce0c90 Mon Sep 17 00:00:00 2001 From: Aly Date: Mon, 27 Apr 2026 20:00:08 -0600 Subject: [PATCH 15/31] Fix FrameForEach to use TreeList Co-authored-by: Alwinfy <20421383+Alwinfy@users.noreply.github.com> --- .../api/casting/eval/vm/FrameForEach.kt | 25 +++++++++---------- .../common/casting/actions/eval/OpForEach.kt | 3 ++- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt index 02d9612767..dcfa0b1355 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt @@ -6,6 +6,7 @@ import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType import at.petrak.hexcasting.api.casting.iota.Iota import at.petrak.hexcasting.api.casting.iota.ListIota import at.petrak.hexcasting.api.utils.NBTBuilder +import at.petrak.hexcasting.api.utils.TreeList import at.petrak.hexcasting.api.utils.getList import at.petrak.hexcasting.api.utils.hasList import at.petrak.hexcasting.api.utils.serializeToNBT @@ -28,14 +29,13 @@ data class FrameForEach( val data: SpellList, val code: SpellList, val baseStack: List?, - val acc: MutableList + val acc: TreeList ) : ContinuationFrame { /** When halting, we add the stack state at halt to the stack accumulator, then return the original pre-Thoth stack, plus the accumulator. */ override fun breakDownwards(stack: List): Pair> { val newStack = baseStack?.toMutableList() ?: mutableListOf() - acc.addAll(stack) - newStack.add(ListIota(acc)) + newStack.add(ListIota(acc.appendedAll(stack).toList())) return true to newStack } @@ -46,13 +46,12 @@ data class FrameForEach( harness: CastingVM ): CastResult { // If this isn't the very first Thoth step (i.e. no Thoth computations run yet)... - val stack = if (baseStack == null) { - // init stack to the VM stack... - harness.image.stack.toList() + val (stack, nextAcc) = if (baseStack == null) { + // init stack to the harness stack... + harness.image.stack.toList() to acc } else { // else save the stack to the accumulator and reuse the saved base stack. - acc.addAll(harness.image.stack) - baseStack + baseStack to acc.appendedAll(harness.image.stack) } // If we still have data to process... @@ -60,13 +59,13 @@ data class FrameForEach( // push the next datum to the top of the stack, val cont2 = continuation // put the next Thoth object back on the stack for the next Thoth cycle, - .pushFrame(FrameForEach(data.cdr, code, stack, acc)) + .pushFrame(FrameForEach(data.cdr, code, stack, nextAcc)) // and prep the Thoth'd code block for evaluation. .pushFrame(FrameEvaluate(code, true)) Triple(data.car, harness.image.withUsedOp(), cont2) } else { // Else, dump our final list onto the stack. - Triple(ListIota(acc), harness.image, continuation) + Triple(ListIota(acc.toList()), harness.image, continuation) } val tStack = stack.toMutableList() tStack.add(stackTop) @@ -86,7 +85,7 @@ data class FrameForEach( "code" %= code.serializeToNBT() if (baseStack != null) "base" %= baseStack.serializeToNBT() - "accumulator" %= acc.serializeToNBT() + "accumulator" %= acc.toList().serializeToNBT() } override fun size() = data.size() + code.size() + acc.size + (baseStack?.size ?: 0) @@ -104,10 +103,10 @@ data class FrameForEach( HexIotaTypes.LIST.deserialize(tag.getList("base", Tag.TAG_COMPOUND), world)!!.list.toList() else null, - HexIotaTypes.LIST.deserialize( + TreeList.from(HexIotaTypes.LIST.deserialize( tag.getList("accumulator", Tag.TAG_COMPOUND), world - )!!.list.toMutableList() + )!!.list) ) } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/eval/OpForEach.kt b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/eval/OpForEach.kt index bdd4f76f7a..926bce1d82 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/eval/OpForEach.kt +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/actions/eval/OpForEach.kt @@ -8,6 +8,7 @@ import at.petrak.hexcasting.api.casting.eval.vm.FrameForEach import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation import at.petrak.hexcasting.api.casting.getList import at.petrak.hexcasting.api.casting.mishaps.MishapNotEnoughArgs +import at.petrak.hexcasting.api.utils.TreeList import at.petrak.hexcasting.common.lib.hex.HexEvalSounds object OpForEach : Action { @@ -22,7 +23,7 @@ object OpForEach : Action { stack.removeLastOrNull() stack.removeLastOrNull() - val frame = FrameForEach(datums, instrs, null, mutableListOf()) + val frame = FrameForEach(datums, instrs, null, TreeList.empty()) val image2 = image.withUsedOp().copy(stack = stack) return OperationResult(image2, listOf(), continuation.pushFrame(frame), HexEvalSounds.THOTH) From 97186dced70aedd836e92a54798f0f13e605660c Mon Sep 17 00:00:00 2001 From: Aly Date: Mon, 27 Apr 2026 22:24:56 -0600 Subject: [PATCH 16/31] add deprecated constructor for FrameForEach compat --- .../api/casting/eval/vm/FrameForEach.kt | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt index dcfa0b1355..7f860595a1 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt @@ -23,19 +23,27 @@ import net.minecraft.server.level.ServerLevel * @property data list of *remaining* datums to ForEach over * @property code code to run per datum * @property baseStack the stack state at Thoth entry - * @property acc concatenated list of final stack states after Thoth exit + * @property immutableAcc concatenated list of final stack states after Thoth exit */ data class FrameForEach( val data: SpellList, val code: SpellList, val baseStack: List?, - val acc: TreeList + val immutableAcc: TreeList ) : ContinuationFrame { + @Deprecated("use the primary constructor that accepts a TreeList instead") + constructor( + data: SpellList, + code: SpellList, + baseStack: List?, + acc: MutableList + ) : this(data, code, baseStack, TreeList.from(acc)) + /** When halting, we add the stack state at halt to the stack accumulator, then return the original pre-Thoth stack, plus the accumulator. */ override fun breakDownwards(stack: List): Pair> { val newStack = baseStack?.toMutableList() ?: mutableListOf() - newStack.add(ListIota(acc.appendedAll(stack).toList())) + newStack.add(ListIota(immutableAcc.appendedAll(stack).toList())) return true to newStack } @@ -48,10 +56,10 @@ data class FrameForEach( // If this isn't the very first Thoth step (i.e. no Thoth computations run yet)... val (stack, nextAcc) = if (baseStack == null) { // init stack to the harness stack... - harness.image.stack.toList() to acc + harness.image.stack.toList() to immutableAcc } else { // else save the stack to the accumulator and reuse the saved base stack. - baseStack to acc.appendedAll(harness.image.stack) + baseStack to immutableAcc.appendedAll(harness.image.stack) } // If we still have data to process... @@ -65,7 +73,7 @@ data class FrameForEach( Triple(data.car, harness.image.withUsedOp(), cont2) } else { // Else, dump our final list onto the stack. - Triple(ListIota(acc.toList()), harness.image, continuation) + Triple(ListIota(immutableAcc.toList()), harness.image, continuation) } val tStack = stack.toMutableList() tStack.add(stackTop) @@ -85,10 +93,10 @@ data class FrameForEach( "code" %= code.serializeToNBT() if (baseStack != null) "base" %= baseStack.serializeToNBT() - "accumulator" %= acc.toList().serializeToNBT() + "accumulator" %= immutableAcc.toList().serializeToNBT() } - override fun size() = data.size() + code.size() + acc.size + (baseStack?.size ?: 0) + override fun size() = data.size() + code.size() + immutableAcc.size + (baseStack?.size ?: 0) override val type: ContinuationFrame.Type<*> = TYPE From 7e4beb9c50fd9ef4fb61d1107668118754e7f277 Mon Sep 17 00:00:00 2001 From: Aly Date: Mon, 27 Apr 2026 22:26:34 -0600 Subject: [PATCH 17/31] add auxilliary constructor for ListIota to accept TreeList --- .../java/at/petrak/hexcasting/api/casting/iota/ListIota.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/ListIota.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/ListIota.java index 0eddd14808..5a0ea6ddeb 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/ListIota.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/ListIota.java @@ -2,6 +2,7 @@ import at.petrak.hexcasting.api.casting.SpellList; import at.petrak.hexcasting.api.utils.HexUtils; +import at.petrak.hexcasting.api.utils.TreeList; import at.petrak.hexcasting.common.lib.hex.HexIotaTypes; import at.petrak.hexcasting.api.mod.HexConfig; import net.minecraft.ChatFormatting; @@ -37,6 +38,10 @@ public ListIota(@NotNull SpellList list) { size = totalSize; } + public ListIota(@NotNull TreeList list) { + this(new SpellList.LList(list)); + } + public ListIota(@NotNull List list) { this(new SpellList.LList(list)); } From 39bf888b86be3e4bdae19453cc6ec38a85eec6fc Mon Sep 17 00:00:00 2001 From: Aly Date: Mon, 27 Apr 2026 22:27:01 -0600 Subject: [PATCH 18/31] nit: rename nextAcc to newAcc --- .../at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt index 7f860595a1..7bb95c7c0c 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt @@ -54,7 +54,7 @@ data class FrameForEach( harness: CastingVM ): CastResult { // If this isn't the very first Thoth step (i.e. no Thoth computations run yet)... - val (stack, nextAcc) = if (baseStack == null) { + val (stack, newAcc) = if (baseStack == null) { // init stack to the harness stack... harness.image.stack.toList() to immutableAcc } else { @@ -67,7 +67,7 @@ data class FrameForEach( // push the next datum to the top of the stack, val cont2 = continuation // put the next Thoth object back on the stack for the next Thoth cycle, - .pushFrame(FrameForEach(data.cdr, code, stack, nextAcc)) + .pushFrame(FrameForEach(data.cdr, code, stack, newAcc)) // and prep the Thoth'd code block for evaluation. .pushFrame(FrameEvaluate(code, true)) Triple(data.car, harness.image.withUsedOp(), cont2) From 78edb30b665f93563c08fc2027ebb26bdfff78b4 Mon Sep 17 00:00:00 2001 From: Aly Date: Mon, 27 Apr 2026 22:27:55 -0600 Subject: [PATCH 19/31] remove useless .toList calls --- .../petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt index 7bb95c7c0c..3ddbc29a87 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt @@ -43,7 +43,7 @@ data class FrameForEach( /** When halting, we add the stack state at halt to the stack accumulator, then return the original pre-Thoth stack, plus the accumulator. */ override fun breakDownwards(stack: List): Pair> { val newStack = baseStack?.toMutableList() ?: mutableListOf() - newStack.add(ListIota(immutableAcc.appendedAll(stack).toList())) + newStack.add(ListIota(immutableAcc.appendedAll(stack))) return true to newStack } @@ -73,7 +73,7 @@ data class FrameForEach( Triple(data.car, harness.image.withUsedOp(), cont2) } else { // Else, dump our final list onto the stack. - Triple(ListIota(immutableAcc.toList()), harness.image, continuation) + Triple(ListIota(immutableAcc), harness.image, continuation) } val tStack = stack.toMutableList() tStack.add(stackTop) @@ -93,7 +93,7 @@ data class FrameForEach( "code" %= code.serializeToNBT() if (baseStack != null) "base" %= baseStack.serializeToNBT() - "accumulator" %= immutableAcc.toList().serializeToNBT() + "accumulator" %= immutableAcc.serializeToNBT() } override fun size() = data.size() + code.size() + immutableAcc.size + (baseStack?.size ?: 0) From 19e353b6999f16bd26cd192b686179ab2f4c1c90 Mon Sep 17 00:00:00 2001 From: Aly Date: Mon, 27 Apr 2026 22:30:58 -0600 Subject: [PATCH 20/31] doc TreeList --- .../at/petrak/hexcasting/api/utils/TreeList.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/utils/TreeList.java b/Common/src/main/java/at/petrak/hexcasting/api/utils/TreeList.java index cbc99d2ad6..fbc479c7e9 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/utils/TreeList.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/utils/TreeList.java @@ -13,6 +13,21 @@ /** * Ported from Scala 2.13.16's scala.collection.immutable.Vector + *

+ * TreeList is an immutable collection that provides effectively-constant asymptotics for the following operations: + *

+ *
    + *
  • head (first)
  • + *
  • tail (rest)
  • + *
  • apply (index)
  • + *
  • update
  • + *
  • prepend
  • + *
  • append
  • + *
+ *

+ * It also provides O(min(m, n)) performance for concatenation. This makes it useful as a stack in a VM that requires + * serialization of the stack or continuation at any time. + *

*/ @SuppressWarnings("unchecked") public sealed abstract class TreeList
extends AbstractList implements RandomAccess { From 248b6d9065d7f02f6a088def36b94ef4dffca097 Mon Sep 17 00:00:00 2001 From: Aly Date: Mon, 27 Apr 2026 22:36:37 -0600 Subject: [PATCH 21/31] rename usages of vector to treeList --- .../petrak/hexcasting/api/utils/TreeList.java | 140 +++++++++--------- 1 file changed, 70 insertions(+), 70 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/utils/TreeList.java b/Common/src/main/java/at/petrak/hexcasting/api/utils/TreeList.java index fbc479c7e9..2537e56ae8 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/utils/TreeList.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/utils/TreeList.java @@ -121,7 +121,7 @@ private static int sizeCompare(Iterable left, Iterable right) { private static final int LOG2_CONCAT_FASTER = 5; private static final int ALIGN_TO_FASTER = 64; - private static int vectorSliceDim(int count, int idx) { + private static int treeListSliceDim(int count, int idx) { final int c = count / 2; return c + 1 - Math.abs(idx - c); } @@ -349,7 +349,7 @@ public int length() { } public ListIterator iterator() { - return new NewVectorIterator<>(this, this.length(), this.vectorSliceCount()); + return new NewTreeListIterator<>(this, this.length(), this.treeListSliceCount()); } protected TreeList filterImpl(Predicate predicate, boolean isFlipped) { @@ -401,7 +401,7 @@ public TreeList prependedAll(Iterable prefix) { int k = knownSize(prefix); if(k == 0) return this; else if (k >= Integer.MAX_VALUE) { - VectorBuilder builder = new VectorBuilder(); + TreeListBuilder builder = new TreeListBuilder(); builder.addAll(prefix); builder.addAll(this); return builder.result(); @@ -412,7 +412,7 @@ public TreeList appendedAll(Iterable suffix) { int k = knownSize(suffix); if(k == 0) return this; else if (k >= Integer.MAX_VALUE) { - VectorBuilder builder = new VectorBuilder(); + TreeListBuilder builder = new TreeListBuilder(); builder.addAll(this); builder.addAll(suffix); return builder.result(); @@ -421,7 +421,7 @@ else if (k >= Integer.MAX_VALUE) { protected TreeList prependedAll0(Iterable prefix, int k) { // k >= 0, k = prefix.knownSize - final int tinyAppendLimit = 4 + this.vectorSliceCount(); + final int tinyAppendLimit = 4 + this.treeListSliceCount(); if (k < tinyAppendLimit /*|| k < (this.size >>> Log2ConcatFaster)*/) { TreeList v = this; Object[] elements = new Object[k]; @@ -438,9 +438,9 @@ protected TreeList prependedAll0(Iterable prefix, int k) { for (A a : this) v = v.appended(a); return v; } else if (k < this.size() - ALIGN_TO_FASTER) { - return new VectorBuilder().alignTo(k, this).addAll(prefix).addAll(this).result(); + return new TreeListBuilder().alignTo(k, this).addAll(prefix).addAll(this).result(); } else { - VectorBuilder builder = new VectorBuilder(); + TreeListBuilder builder = new TreeListBuilder(); builder.addAll(prefix); builder.addAll(this); return builder.result(); @@ -449,7 +449,7 @@ protected TreeList prependedAll0(Iterable prefix, int k) { protected TreeList appendedAll0(Iterable suffix, int k) { // k >= 0, k = suffix.knownSize - final int tinyAppendLimit = 4 + this.vectorSliceCount(); + final int tinyAppendLimit = 4 + this.treeListSliceCount(); if (k < tinyAppendLimit) { TreeList v = this; for(A a : suffix) v = v.appended(a); @@ -460,8 +460,8 @@ protected TreeList appendedAll0(Iterable suffix, int k) { return v; } else if (this.size() < k - ALIGN_TO_FASTER && suffix instanceof TreeList) { TreeList v = (TreeList) suffix; - return new VectorBuilder().alignTo(this.size(), v).addAll(this).addAll(v).result(); - } else return new VectorBuilder().initFrom(this).addAll(suffix).result(); + return new TreeListBuilder().alignTo(this.size(), v).addAll(this).addAll(v).result(); + } else return new TreeListBuilder().initFrom(this).addAll(suffix).result(); } public final TreeList take(int n) { @@ -492,11 +492,11 @@ public TreeList init() { protected abstract TreeList slice0(int lo, int hi); /** Number of slices */ - abstract int vectorSliceCount(); + abstract int treeListSliceCount(); /** Slice at index */ - abstract Object[] vectorSlice(int idx); + abstract Object[] treeListSlice(int idx); /** Length of all slices up to and including index */ - abstract int vectorSlicePrefixLength(int idx); + abstract int treeListSlicePrefixLength(int idx); public final A head() { if(this.prefix1.length == 0) throw new NoSuchElementException("empty.head"); @@ -508,9 +508,9 @@ public A last() { } public void foreach(Consumer f) { - final int c = this.vectorSliceCount(); + final int c = this.treeListSliceCount(); for(int i = 0; i < c; i++) { - foreachRec(vectorSliceDim(c, i) - 1, this.vectorSlice(i), f); + foreachRec(treeListSliceDim(c, i) - 1, this.treeListSlice(i), f); } } @@ -561,7 +561,7 @@ else if(knownSize > 0 && knownSize <= WIDTH) { } return new TreeList1<>(a1); } else { - VectorBuilder builder = new VectorBuilder<>(); + TreeListBuilder builder = new TreeListBuilder<>(); builder.addAll(it); return builder.result(); } @@ -603,7 +603,7 @@ public TreeList filter(Predicate pred) { } public TreeList flatMap(Function> func) { - VectorBuilder builder = new VectorBuilder(); + TreeListBuilder builder = new TreeListBuilder(); for(A a : this) { builder.addAll(func.apply(a)); } @@ -631,8 +631,8 @@ public int knownSize() { public abstract TreeList map(Function f); public TreeList reversed() { - // FIXME: Understand NewVectorIterator - VectorBuilder builder = new VectorBuilder<>(); + // FIXME: Understand NewVectorIterator / NewTreeListIterator + TreeListBuilder builder = new TreeListBuilder<>(); for(int i = this.size() - 1; i >= 0; i--) { builder.addOne(this.get(i)); } @@ -687,7 +687,7 @@ protected TreeList filterImpl(Predicate predicate, boolean isFlipped) { } final int newLen = i + Integer.bitCount(bitmap); - VectorBuilder b = new VectorBuilder<>(); + TreeListBuilder b = new TreeListBuilder<>(); int k = 0; while (k < i) { b.addOne((A) this.prefix1[k]); @@ -708,7 +708,7 @@ protected TreeList filterImpl(Predicate predicate, boolean isFlipped) { } i += 1; } - VectorBuilder b = new VectorBuilder<>(); + TreeListBuilder b = new TreeListBuilder<>(); b.initFrom(this.prefix1); this.foreachRest(v -> { if(predicate.test(v) != isFlipped) b.addOne(v); @@ -717,9 +717,9 @@ protected TreeList filterImpl(Predicate predicate, boolean isFlipped) { } protected final void foreachRest(Consumer f) { - final int c = this.vectorSliceCount(); + final int c = this.treeListSliceCount(); for(int i = 1; i < c; i++) { - foreachRec(vectorSliceDim(c, i)-1, vectorSlice(i), f); + foreachRec(treeListSliceDim(c, i)-1, treeListSlice(i), f); } } @@ -785,17 +785,17 @@ protected TreeList slice0(int lo, int hi) { } @Override - int vectorSliceCount() { + int treeListSliceCount() { return 0; } @Override - Object[] vectorSlice(int idx) { + Object[] treeListSlice(int idx) { return null; } @Override - int vectorSlicePrefixLength(int idx) { + int treeListSlicePrefixLength(int idx) { return 0; } @@ -873,17 +873,17 @@ public TreeList init() { } @Override - int vectorSliceCount() { + int treeListSliceCount() { return 1; } @Override - Object[] vectorSlice(int idx) { + Object[] treeListSlice(int idx) { return this.prefix1; } @Override - int vectorSlicePrefixLength(int idx) { + int treeListSlicePrefixLength(int idx) { return this.prefix1.length; } @@ -991,7 +991,7 @@ public TreeList map(Function f) { @Override protected TreeList slice0(int lo, int hi) { - final VectorSliceBuilder b = new VectorSliceBuilder(lo, hi); + final TreeListSliceBuilder b = new TreeListSliceBuilder(lo, hi); b.consider(1, this.prefix1); b.consider(2, this.data2); b.consider(1, this.suffix1); @@ -1016,12 +1016,12 @@ public TreeList init() { } @Override - int vectorSliceCount() { + int treeListSliceCount() { return 3; } @Override - Object[] vectorSlice(int idx) { + Object[] treeListSlice(int idx) { return switch(idx) { case 0 -> this.prefix1; case 1 -> this.data2; @@ -1031,7 +1031,7 @@ Object[] vectorSlice(int idx) { } @Override - int vectorSlicePrefixLength(int idx) { + int treeListSlicePrefixLength(int idx) { return switch(idx) { case 0 -> this.len1; case 1 -> this.length0 - this.suffix1.length; @@ -1194,7 +1194,7 @@ public TreeList map(Function f) { @Override protected TreeList slice0(int lo, int hi) { - final VectorSliceBuilder b = new VectorSliceBuilder(lo, hi); + final TreeListSliceBuilder b = new TreeListSliceBuilder(lo, hi); b.consider(1, this.prefix1); b.consider(2, this.prefix2); b.consider(3, this.data3); @@ -1222,12 +1222,12 @@ public TreeList init() { } @Override - int vectorSliceCount() { + int treeListSliceCount() { return 5; } @Override - Object[] vectorSlice(int idx) { + Object[] treeListSlice(int idx) { return switch(idx) { case 0 -> this.prefix1; case 1 -> this.prefix2; @@ -1239,7 +1239,7 @@ Object[] vectorSlice(int idx) { } @Override - int vectorSlicePrefixLength(int idx) { + int treeListSlicePrefixLength(int idx) { return switch (idx) { case 0 -> this.len1; case 1 -> this.len12; @@ -1461,7 +1461,7 @@ public TreeList map(Function f) { @Override protected TreeList slice0(int lo, int hi) { - final VectorSliceBuilder b = new VectorSliceBuilder(lo, hi); + final TreeListSliceBuilder b = new TreeListSliceBuilder(lo, hi); b.consider(1, this.prefix1); b.consider(2, this.prefix2); b.consider(3, this.prefix3); @@ -1494,12 +1494,12 @@ this.suffix3, this.suffix2, copyInit(this.suffix1), } @Override - int vectorSliceCount() { + int treeListSliceCount() { return 7; } @Override - Object[] vectorSlice(int idx) { + Object[] treeListSlice(int idx) { return switch(idx) { case 0 -> this.prefix1; case 1 -> this.prefix2; @@ -1513,7 +1513,7 @@ Object[] vectorSlice(int idx) { } @Override - int vectorSlicePrefixLength(int idx) { + int treeListSlicePrefixLength(int idx) { return switch (idx) { case 0 -> this.len1; case 1 -> this.len12; @@ -1793,7 +1793,7 @@ public TreeList map(Function f) { @Override protected TreeList slice0(int lo, int hi) { - final VectorSliceBuilder b = new VectorSliceBuilder(lo, hi); + final TreeListSliceBuilder b = new TreeListSliceBuilder(lo, hi); b.consider(1, prefix1); b.consider(2, prefix2); b.consider(3, prefix3); @@ -1829,12 +1829,12 @@ public TreeList init() { } @Override - int vectorSliceCount() { + int treeListSliceCount() { return 9; } @Override - Object[] vectorSlice(int idx) { + Object[] treeListSlice(int idx) { return switch(idx) { case 0 -> this.prefix1; case 1 -> this.prefix2; @@ -1850,7 +1850,7 @@ Object[] vectorSlice(int idx) { } @Override - int vectorSlicePrefixLength(int idx) { + int treeListSlicePrefixLength(int idx) { return switch(idx) { case 0 -> this.len1; case 1 -> this.len12; @@ -2210,7 +2210,7 @@ public TreeList map(Function f) { @Override protected TreeList slice0(int lo, int hi) { - final VectorSliceBuilder b = new VectorSliceBuilder(lo, hi); + final TreeListSliceBuilder b = new TreeListSliceBuilder(lo, hi); b.consider(1, this.prefix1); b.consider(2, this.prefix2); b.consider(3, this.prefix3); @@ -2250,12 +2250,12 @@ public TreeList init() { } @Override - int vectorSliceCount() { + int treeListSliceCount() { return 11; } @Override - Object[] vectorSlice(int idx) { + Object[] treeListSlice(int idx) { return switch(idx) { case 0 -> this.prefix1; case 1 -> this.prefix2; @@ -2273,7 +2273,7 @@ Object[] vectorSlice(int idx) { } @Override - int vectorSlicePrefixLength(int idx) { + int treeListSlicePrefixLength(int idx) { return switch(idx) { case 0 -> this.len1; case 1 -> this.len12; @@ -2321,7 +2321,7 @@ protected TreeList appendedAll0(Iterable suffix, int k) { } } - private static final class VectorSliceBuilder { + private static final class TreeListSliceBuilder { private static int prefixIdx(int n) { return n - 1; } @@ -2338,7 +2338,7 @@ private static int suffixIdx(int n) { private int pos; private int maxDim; - public VectorSliceBuilder(int lo, int hi) { + public TreeListSliceBuilder(int lo, int hi) { this.lo = lo; this.hi = hi; this.slices = new Object[11][]; @@ -2585,7 +2585,7 @@ private void balanceSuffix(int n) { } } - public static final class VectorBuilder { + public static final class TreeListBuilder { private Object[][][][][][] a6; private Object[][][][][] a5; private Object[][][][] a4; @@ -2598,7 +2598,7 @@ public static final class VectorBuilder { private boolean prefixIsRightAligned; private int depth; - public VectorBuilder() { + public TreeListBuilder() { this.clear(); } @@ -2661,8 +2661,8 @@ void initFrom(Object[] prefix1) { } } - VectorBuilder initFrom(TreeList v) { - switch(v.vectorSliceCount()) { + TreeListBuilder initFrom(TreeList v) { + switch(v.treeListSliceCount()) { case 0 -> {} case 1 -> { final TreeList1 v1 = (TreeList1) v; @@ -2768,9 +2768,9 @@ VectorBuilder initFrom(TreeList v) { return this; } - public VectorBuilder alignTo(int before, TreeList bigTreeList) { + public TreeListBuilder alignTo(int before, TreeList bigTreeList) { if(this.len1 != 0 || this.lenRest != 0) - throw new UnsupportedOperationException("A non-empty VectorBuilder cannot be aligned retrospectively. Please call .reset() or use a new VectorBuilder."); + throw new UnsupportedOperationException("A non-empty TreeListBuilder cannot be aligned retrospectively. Please call .reset() or use a new TreeListBuilder."); final int prefixLength; final int maxPrefixLength; @@ -2897,7 +2897,7 @@ private void leftAlignPrefix() { this.prefixIsRightAligned = false; } - public VectorBuilder addOne(A elem) { + public TreeListBuilder addOne(A elem) { if(this.len1 == WIDTH) this.advance(); this.a1[len1] = elem; this.len1++; @@ -3006,11 +3006,11 @@ private void addArrN(Object[] slice, int dim) { } } - private VectorBuilder addVector(TreeList xs) { - final int sliceCount = xs.vectorSliceCount(); + private TreeListBuilder addTreeList(TreeList xs) { + final int sliceCount = xs.treeListSliceCount(); for(int sliceIdx = 0; sliceIdx < sliceCount; sliceIdx++) { - final Object[] slice = xs.vectorSlice(sliceIdx); - final int n = vectorSliceDim(sliceCount, sliceIdx); + final Object[] slice = xs.treeListSlice(sliceIdx); + final int n = treeListSliceDim(sliceCount, sliceIdx); if(n == 1) this.addArr1(slice); else if(this.len1 == WIDTH || this.len1 == 0) this.addArrN(slice, n); else foreachRec(n - 2, slice, this::addArr1); @@ -3018,10 +3018,10 @@ private VectorBuilder addVector(TreeList xs) { return this; } - public VectorBuilder addAll(Iterable xs) { + public TreeListBuilder addAll(Iterable xs) { if(xs instanceof TreeList v) { if(this.len1 == 0 && this.lenRest == 0 && !prefixIsRightAligned) this.initFrom(v); - else this.addVector((TreeList) v); + else this.addTreeList((TreeList) v); } else { for(A a : xs) { this.addOne(a); @@ -3121,7 +3121,7 @@ public TreeList result() { final int len = this.len1 + this.lenRest; final int realLen = len - this.offset; if(realLen == 0) return TreeList.empty(); - else if(len < 0) throw new IndexOutOfBoundsException("Vector cannot have negative size " + len); + else if(len < 0) throw new IndexOutOfBoundsException("TreeList cannot have negative size " + len); else if(len <= WIDTH) { return new TreeList1<>(copyIfDifferentSize(this.a1, realLen)); } else if(len <= WIDTH2) { @@ -3217,7 +3217,7 @@ else if(len <= WIDTH) { } - private static final class NewVectorIterator implements ListIterator { + private static final class NewTreeListIterator implements ListIterator { private final TreeList v; private int totalLength; @@ -3239,7 +3239,7 @@ private static final class NewVectorIterator implements ListIterator { private int sliceStart; private int sliceEnd; - public NewVectorIterator(TreeList v, int totalLength, int sliceCount) { + public NewTreeListIterator(TreeList v, int totalLength, int sliceCount) { this.v = v; this.totalLength = totalLength; this.sliceCount = sliceCount; @@ -3279,14 +3279,14 @@ private void advanceSlice() { if(!this.hasNext()) Collections.emptyIterator().next(); this.sliceIdx++; - Object[] slice = v.vectorSlice(this.sliceIdx); + Object[] slice = v.treeListSlice(this.sliceIdx); while(slice.length == 0) { this.sliceIdx++; - slice = v.vectorSlice(this.sliceIdx); + slice = v.treeListSlice(this.sliceIdx); } this.sliceStart = this.sliceEnd; - this.sliceDim = vectorSliceDim(this.sliceCount, this.sliceIdx); + this.sliceDim = treeListSliceDim(this.sliceCount, this.sliceIdx); switch(this.sliceDim) { case 1 -> this.a1 = (Object[]) slice; case 2 -> this.a2 = (Object[][]) slice; From 5322998348603ace5a98cc6b1c188ef256fba695 Mon Sep 17 00:00:00 2001 From: Aly Date: Mon, 27 Apr 2026 22:37:26 -0600 Subject: [PATCH 22/31] clarify fixmes and todos --- .../main/java/at/petrak/hexcasting/api/utils/TreeList.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/utils/TreeList.java b/Common/src/main/java/at/petrak/hexcasting/api/utils/TreeList.java index 2537e56ae8..24f0991d8e 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/utils/TreeList.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/utils/TreeList.java @@ -632,6 +632,7 @@ public int knownSize() { public TreeList reversed() { // FIXME: Understand NewVectorIterator / NewTreeListIterator + // NewVectorIterator dodges indexing costs. If we could make it run in reverse it could be a lot faster TreeListBuilder builder = new TreeListBuilder<>(); for(int i = this.size() - 1; i >= 0; i--) { builder.addOne(this.get(i)); @@ -639,7 +640,8 @@ public TreeList reversed() { return builder.result(); } - // FIXME: Get rid of this + // TODO: Get rid of this + // Kotlin tries to force a different signature for `reversed` on us. This should remain here as a public alias until that is fixed public TreeList reversedVec() { return this.reversed(); } From 25b7f007fe997eb4476194adb753ee9089c9d600 Mon Sep 17 00:00:00 2001 From: Aly Date: Mon, 27 Apr 2026 23:51:14 -0600 Subject: [PATCH 23/31] add getter for deprecated mutable accumulator --- .../at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt index 3ddbc29a87..872bbe8e14 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt @@ -40,6 +40,9 @@ data class FrameForEach( acc: MutableList ) : this(data, code, baseStack, TreeList.from(acc)) + @Deprecated("access immutableAcc instead") + val acc: MutableList get() = immutableAcc.toMutableList() + /** When halting, we add the stack state at halt to the stack accumulator, then return the original pre-Thoth stack, plus the accumulator. */ override fun breakDownwards(stack: List): Pair> { val newStack = baseStack?.toMutableList() ?: mutableListOf() From 6fc406e6df80588ddc32c007e7bf02add584ba7c Mon Sep 17 00:00:00 2001 From: pipythonmc <47196755+pythonmcpi@users.noreply.github.com> Date: Tue, 28 Apr 2026 20:51:22 -0700 Subject: [PATCH 24/31] fix: op consumption check should happen in CastingVM instead of PatternIota --- .../at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt | 7 +++++++ .../at/petrak/hexcasting/api/casting/iota/PatternIota.java | 4 ---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt index 11503b0b6f..3d03a9a843 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/CastingVM.kt @@ -59,6 +59,13 @@ class CastingVM(var image: CastingImage, val env: CastingEnvironment) { resolutionType = ResolvedPatternType.ERRORED, sound = HexEvalSounds.MISHAP, ) + } else if (result.newData != null && result.newData.opsConsumed > env.maxOpCount()) { + result.copy( + newData = null, + sideEffects = listOf(OperatorSideEffect.DoMishap(MishapEvalTooMuch(), Mishap.Context(null, null))), + resolutionType = ResolvedPatternType.ERRORED, + sound = HexEvalSounds.MISHAP, + ) } else { result } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/PatternIota.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/PatternIota.java index 74db0a95fb..536710b92b 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/PatternIota.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/PatternIota.java @@ -108,10 +108,6 @@ public boolean toleratesOther(Iota that) { continuation ); - if (result.getNewImage().getOpsConsumed() > vm.getEnv().maxOpCount()) { - throw new MishapEvalTooMuch(); - } - var cont2 = result.getNewContinuation(); // TODO parens also break prescience var sideEffects = result.getSideEffects(); From 5067fb147749b4ab4b283e82bf0dc6654573ca12 Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Fri, 8 May 2026 18:17:46 -0400 Subject: [PATCH 25/31] Properly use the global on fabric --- .../petrak/hexcasting/fabric/FabricHexClientInitializer.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexClientInitializer.kt b/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexClientInitializer.kt index 65fbd55ad2..5bfffdc01c 100644 --- a/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexClientInitializer.kt +++ b/Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexClientInitializer.kt @@ -29,7 +29,6 @@ import net.minecraft.world.level.block.entity.BlockEntityType import java.util.function.Function object FabricHexClientInitializer : ClientModInitializer { - private var patternRegistryIsProcessed: Boolean = false override fun onInitializeClient() { FabricPacketHandler.initClient() @@ -44,9 +43,9 @@ object FabricHexClientInitializer : ClientModInitializer { } TooltipComponentCallback.EVENT.register(PatternTooltipComponent::tryConvert) ClientPlayConnectionEvents.JOIN.register { _, _, _ -> - if (patternRegistryIsProcessed) { + if (!FabricHexInitializer.patternRegistryIsProcessed) { PatternRegistryManifest.processRegistry(null) - patternRegistryIsProcessed = true + FabricHexInitializer.patternRegistryIsProcessed = true } } From 9490c429597420e1fca8969707e6bcf8e4c2d714 Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Fri, 8 May 2026 19:18:00 -0400 Subject: [PATCH 26/31] More informative description --- .../main/resources/assets/hexcasting/lang/en_us.flatten.json5 | 4 ++-- .../main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 b/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 index 8229d7bcb2..82594a9f67 100644 --- a/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 +++ b/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 @@ -356,8 +356,8 @@ "@Tooltip": "Whether all iota types should be comma-separated when displayed in lists (by default, pattern iotas do not use commas)", }, staticActiveSlates: { - "": "Static patterned slates", - "@Tooltip": "Whether patterns on slates should always be rendered non-moving", + "": "Static Active Slates", + "@Tooltip": "Whether patterns on active slates should be rendered without wobble (improves performance with lots of active slates)", } }, diff --git a/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java b/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java index 459629c6f2..08fc51a03a 100644 --- a/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java +++ b/Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java @@ -109,7 +109,7 @@ public Client(ForgeConfigSpec.Builder builder) { "Whether all iota types should be comma-separated in lists (by default, pattern iotas don't use commas)") .define("alwaysShowListCommas", DEFAULT_ALWAYS_SHOW_LIST_COMMAS); staticActiveSlates = builder.comment( - "Whether patterns on slates should always be rendered non-moving") + "Whether patterns on active slates should be rendered without wobble (improves performance with lots of active slates)") .define("staticActiveSlates", DEFAULT_STATIC_ACTIVE_SLATES); } From 42a795e5d43c9618bd25f6a5c00e3043701cc74f Mon Sep 17 00:00:00 2001 From: Robotgiggle <88736742+Robotgiggle@users.noreply.github.com> Date: Fri, 8 May 2026 19:54:08 -0400 Subject: [PATCH 27/31] Invert delta for page turn keybinds --- .../main/java/at/petrak/hexcasting/client/Keybinds.java | 7 +++++-- .../at/petrak/hexcasting/client/gui/GuiSpellcasting.kt | 6 ++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/client/Keybinds.java b/Common/src/main/java/at/petrak/hexcasting/client/Keybinds.java index a0b85e3e1a..ab57aede66 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/Keybinds.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/Keybinds.java @@ -23,12 +23,15 @@ public class Keybinds { public static List ALL_BINDS = List.of(spellbookPrev, spellbookNext); public static void clientTickEnd() { + // because of how mouse scrolling works (scrolling upward moves the page down), a positive + // delta value makes the book flip backward while a negative one makes it flip forward + while (spellbookPrev.consumeClick()) { - ShiftScrollListener.onScroll(-1, false); + ShiftScrollListener.onScroll(1, false); } while (spellbookNext.consumeClick()) { - ShiftScrollListener.onScroll(1, false); + ShiftScrollListener.onScroll(-1, false); } } } diff --git a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt index 09c036ab3b..9d1e3820d6 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt +++ b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt @@ -317,11 +317,13 @@ class GuiSpellcasting constructor( override fun keyPressed(key: Int, scancode: Int, modifiers: Int): Boolean { if (super.keyPressed(key, scancode, modifiers)) return true + // because of how mouse scrolling works (scrolling upward moves the page down), a positive + // delta value makes the book flip backward while a negative one makes it flip forward if (Keybinds.spellbookPrev.matches(key, scancode)) { - ShiftScrollListener.onScroll(-1.0, false) + ShiftScrollListener.onScroll(1.0, false) return true } else if (Keybinds.spellbookNext.matches(key, scancode)) { - ShiftScrollListener.onScroll(1.0, false) + ShiftScrollListener.onScroll(-1.0, false) return true } From ead6d2591544ae7aa898b50d7fcd13865fe019c3 Mon Sep 17 00:00:00 2001 From: ZiG60020405 <47026578+zhichaoxi2006@users.noreply.github.com> Date: Wed, 25 Feb 2026 20:52:43 +0800 Subject: [PATCH 28/31] Fix NullPointerException when draw an invalid pattern --- .../hexcasting/api/casting/eval/env/PlayerBasedCastEnv.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/PlayerBasedCastEnv.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/PlayerBasedCastEnv.java index 405fe1ed20..34eaf46d8b 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/PlayerBasedCastEnv.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/env/PlayerBasedCastEnv.java @@ -73,11 +73,10 @@ public ServerPlayer getCaster() { @Override protected double getCostModifier(PatternShapeMatch match) { ResourceLocation loc = actionKey(match); - if (isOfTag(IXplatAbstractions.INSTANCE.getActionRegistry(), loc, HexTags.Actions.CANNOT_MODIFY_COST)) { + if (loc != null && isOfTag(IXplatAbstractions.INSTANCE.getActionRegistry(), loc, HexTags.Actions.CANNOT_MODIFY_COST)) { return 1.0; - } else { - return this.caster.getAttributeValue(HexAttributes.MEDIA_CONSUMPTION_MODIFIER); } + return this.caster.getAttributeValue(HexAttributes.MEDIA_CONSUMPTION_MODIFIER); } @Override From d63ada1a2a5a20fa6f7144a34b067196ed6e2d66 Mon Sep 17 00:00:00 2001 From: pipythonmc <47196755+pythonmcpi@users.noreply.github.com> Date: Fri, 8 May 2026 17:53:13 -0700 Subject: [PATCH 29/31] Fix scroll invert setting interfering with keybind direction --- .../at/petrak/hexcasting/client/Keybinds.java | 4 +-- .../client/ShiftScrollListener.java | 25 +++++++++++++++---- .../hexcasting/client/gui/GuiSpellcasting.kt | 4 +-- .../common/msgs/MsgShiftScrollC2S.java | 17 ++----------- 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/client/Keybinds.java b/Common/src/main/java/at/petrak/hexcasting/client/Keybinds.java index ab57aede66..46fe6d5d03 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/Keybinds.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/Keybinds.java @@ -27,11 +27,11 @@ public static void clientTickEnd() { // delta value makes the book flip backward while a negative one makes it flip forward while (spellbookPrev.consumeClick()) { - ShiftScrollListener.onScroll(1, false); + ShiftScrollListener.onScroll(1, false, false); } while (spellbookNext.consumeClick()) { - ShiftScrollListener.onScroll(-1, false); + ShiftScrollListener.onScroll(-1, false, false); } } } diff --git a/Common/src/main/java/at/petrak/hexcasting/client/ShiftScrollListener.java b/Common/src/main/java/at/petrak/hexcasting/client/ShiftScrollListener.java index 45fc1d7d78..ed42647c40 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/ShiftScrollListener.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/ShiftScrollListener.java @@ -23,6 +23,10 @@ public static boolean onScrollInGameplay(double delta) { } public static boolean onScroll(double delta, boolean needsSneaking) { + return onScroll(delta, needsSneaking, true); + } + + public static boolean onScroll(double delta, boolean needsSneaking, boolean allowInverting) { LocalPlayer player = Minecraft.getInstance().player; // not .isCrouching! that fails for players who are not on the ground // yes, this does work if you remap your sneak key @@ -33,10 +37,10 @@ public static boolean onScroll(double delta, boolean needsSneaking) { } if (IsScrollableItem(player.getMainHandItem().getItem())) { - mainHandDelta += delta; + mainHandDelta += delta * getScrollModifier(player.getMainHandItem().getItem(), allowInverting); return true; } else if (IsScrollableItem(player.getOffhandItem().getItem())) { - offHandDelta += delta; + offHandDelta += delta * getScrollModifier(player.getOffhandItem().getItem(), allowInverting); return true; } } @@ -47,9 +51,8 @@ public static boolean onScroll(double delta, boolean needsSneaking) { public static void clientTickEnd() { if (mainHandDelta != 0 || offHandDelta != 0) { IClientXplatAbstractions.INSTANCE.sendPacketToServer( - new MsgShiftScrollC2S(mainHandDelta, offHandDelta, Minecraft.getInstance().options.keySprint.isDown(), - HexConfig.client().invertSpellbookScrollDirection(), - HexConfig.client().invertAbacusScrollDirection())); + new MsgShiftScrollC2S(mainHandDelta, offHandDelta, Minecraft.getInstance().options.keySprint.isDown()) + ); mainHandDelta = 0; offHandDelta = 0; } @@ -58,4 +61,16 @@ public static void clientTickEnd() { private static boolean IsScrollableItem(Item item) { return item == HexItems.SPELLBOOK || item == HexItems.ABACUS; } + + private static double getScrollModifier(Item item, boolean allowInverting) { + if (!allowInverting) return 1; + + if (item == HexItems.SPELLBOOK) { + return HexConfig.client().invertSpellbookScrollDirection() ? -1 : 1; + } else if (item == HexItems.ABACUS) { + return HexConfig.client().invertAbacusScrollDirection() ? -1 : 1; + } else { + return 1; + } + } } diff --git a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt index 9d1e3820d6..208af39ea4 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt +++ b/Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt @@ -320,10 +320,10 @@ class GuiSpellcasting constructor( // because of how mouse scrolling works (scrolling upward moves the page down), a positive // delta value makes the book flip backward while a negative one makes it flip forward if (Keybinds.spellbookPrev.matches(key, scancode)) { - ShiftScrollListener.onScroll(1.0, false) + ShiftScrollListener.onScroll(1.0, false, false) return true } else if (Keybinds.spellbookNext.matches(key, scancode)) { - ShiftScrollListener.onScroll(-1.0, false) + ShiftScrollListener.onScroll(-1.0, false, false) return true } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/msgs/MsgShiftScrollC2S.java b/Common/src/main/java/at/petrak/hexcasting/common/msgs/MsgShiftScrollC2S.java index e1f4d354a6..81e5191535 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/msgs/MsgShiftScrollC2S.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/msgs/MsgShiftScrollC2S.java @@ -24,8 +24,7 @@ * Sent client->server when the client shift+scrolls with a shift-scrollable item * or scrolls in the spellcasting UI. */ -public record MsgShiftScrollC2S(double mainHandDelta, double offHandDelta, boolean isCtrl, boolean invertSpellbook, - boolean invertAbacus) implements IMessage { +public record MsgShiftScrollC2S(double mainHandDelta, double offHandDelta, boolean isCtrl) implements IMessage { public static final ResourceLocation ID = modLoc("scroll"); @Override @@ -38,17 +37,13 @@ public static MsgShiftScrollC2S deserialize(ByteBuf buffer) { var mainHandDelta = buf.readDouble(); var offHandDelta = buf.readDouble(); var isCtrl = buf.readBoolean(); - var invertSpellbook = buf.readBoolean(); - var invertAbacus = buf.readBoolean(); - return new MsgShiftScrollC2S(mainHandDelta, offHandDelta, isCtrl, invertSpellbook, invertAbacus); + return new MsgShiftScrollC2S(mainHandDelta, offHandDelta, isCtrl); } public void serialize(FriendlyByteBuf buf) { buf.writeDouble(this.mainHandDelta); buf.writeDouble(this.offHandDelta); buf.writeBoolean(this.isCtrl); - buf.writeBoolean(this.invertSpellbook); - buf.writeBoolean(this.invertAbacus); } public void handle(MinecraftServer server, ServerPlayer sender) { @@ -71,10 +66,6 @@ private void handleForHand(ServerPlayer sender, InteractionHand hand, double del } private void spellbook(ServerPlayer sender, InteractionHand hand, ItemStack stack, double delta) { - if (invertSpellbook) { - delta = -delta; - } - var newIdx = ItemSpellbook.rotatePageIdx(stack, delta < 0.0); var len = ItemSpellbook.highestPage(stack); @@ -115,10 +106,6 @@ private void spellbook(ServerPlayer sender, InteractionHand hand, ItemStack stac } private void abacus(ServerPlayer sender, InteractionHand hand, ItemStack stack, double delta) { - if (invertAbacus) { - delta = -delta; - } - var increase = delta < 0; double num = NBTHelper.getDouble(stack, ItemAbacus.TAG_VALUE); From 2b438c5cd94008a37b31d9475c890c974166a458 Mon Sep 17 00:00:00 2001 From: Aly Date: Wed, 10 Jun 2026 00:03:45 -0600 Subject: [PATCH 30/31] remove deprecated overloads from FrameForEach --- .../api/casting/eval/vm/FrameForEach.kt | 27 ++++++------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt index be73f09eaa..1bbd83251d 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/eval/vm/FrameForEach.kt @@ -24,30 +24,19 @@ import kotlin.jvm.optionals.getOrNull * @property data list of *remaining* datums to ForEach over * @property code code to run per datum * @property baseStack the stack state at Thoth entry - * @property immutableAcc concatenated list of final stack states after Thoth exit + * @property acc concatenated list of final stack states after Thoth exit */ data class FrameForEach( val data: SpellList, val code: SpellList, val baseStack: List?, - val immutableAcc: TreeList + val acc: TreeList ) : ContinuationFrame { - @Deprecated("use the primary constructor that accepts a TreeList instead") - constructor( - data: SpellList, - code: SpellList, - baseStack: List?, - acc: MutableList - ) : this(data, code, baseStack, TreeList.from(acc)) - - @Deprecated("access immutableAcc instead") - val acc: MutableList get() = immutableAcc.toMutableList() - /** When halting, we add the stack state at halt to the stack accumulator, then return the original pre-Thoth stack, plus the accumulator. */ override fun breakDownwards(stack: List): Pair> { val newStack = baseStack?.toMutableList() ?: mutableListOf() - newStack.add(ListIota(immutableAcc.appendedAll(stack))) + newStack.add(ListIota(acc.appendedAll(stack))) return true to newStack } @@ -60,10 +49,10 @@ data class FrameForEach( // If this isn't the very first Thoth step (i.e. no Thoth computations run yet)... val (stack, newAcc) = if (baseStack == null) { // init stack to the harness stack... - harness.image.stack.toList() to immutableAcc + harness.image.stack.toList() to acc } else { // else save the stack to the accumulator and reuse the saved base stack. - baseStack to immutableAcc.appendedAll(harness.image.stack) + baseStack to acc.appendedAll(harness.image.stack) } // If we still have data to process... @@ -77,7 +66,7 @@ data class FrameForEach( Triple(data.car, harness.image.withUsedOp(), cont2) } else { // Else, dump our final list onto the stack. - Triple(ListIota(immutableAcc), harness.image, continuation) + Triple(ListIota(acc), harness.image, continuation) } val tStack = stack.toMutableList() tStack.add(stackTop) @@ -92,7 +81,7 @@ data class FrameForEach( ) } - override fun size() = data.size() + code.size() + immutableAcc.size + (baseStack?.size ?: 0) + override fun size() = data.size() + code.size() + acc.size + (baseStack?.size ?: 0) override val type: ContinuationFrame.Type<*> = TYPE @@ -106,7 +95,7 @@ data class FrameForEach( IotaType.TYPED_CODEC.listOf().optionalFieldOf("base").forGetter { Optional.ofNullable(it.baseStack) }, IotaType.TYPED_CODEC.listOf().fieldOf("accumulator").forGetter { it.acc } ).apply(inst) { a, b, c, d -> - FrameForEach(a, b, c.getOrNull(), d) + FrameForEach(a, b, c.getOrNull(), TreeList.from(d)) } } val STREAM_CODEC = StreamCodec.composite( From 7a4172297a53f21f7665347cfe73b0ce6286e26d Mon Sep 17 00:00:00 2001 From: Aly Date: Wed, 10 Jun 2026 00:05:21 -0600 Subject: [PATCH 31/31] remove deprecated matchPattern overload from PatternRegistryManifest --- .../casting/PatternRegistryManifest.java | 22 +++---------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/common/casting/PatternRegistryManifest.java b/Common/src/main/java/at/petrak/hexcasting/common/casting/PatternRegistryManifest.java index 4561af15af..e8ca9d1f37 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/casting/PatternRegistryManifest.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/casting/PatternRegistryManifest.java @@ -84,13 +84,9 @@ public static Pair>> match /** * Try to match this pattern to an action, whether via a normal pattern, a per-world pattern, or the machinations - * of a special handler. - * - * @deprecated Giving this method a true will cause it to throw if it cannot find a normal or per-world pattern match. Use the bool-less overload instead. - */ - @Deprecated(since = "0.11.4") - public static PatternShapeMatch matchPattern(HexPattern pat, CastingEnvironment environment, - boolean checkForAlternateStrokeOrders) { + * of a special handler. Will never attempt to check alternate stroke orders, which always throws. + */ + public static PatternShapeMatch matchPattern(HexPattern pat, CastingEnvironment environment) { // I am PURPOSELY checking normal actions before special handlers // This way we don't get a repeat of the phial number literal incident var sig = pat.getAngles(); @@ -106,10 +102,6 @@ public static PatternShapeMatch matchPattern(HexPattern pat, CastingEnvironment return new PatternShapeMatch.PerWorld(entry.key(), true); } - if (checkForAlternateStrokeOrders) { - throw new NotImplementedException("checking for alternate stroke orders is NYI sorry"); - } - var shMatch = matchPatternToSpecialHandler(pat, environment); if (shMatch != null) { return new PatternShapeMatch.Special(shMatch.getSecond(), shMatch.getFirst()); @@ -118,14 +110,6 @@ public static PatternShapeMatch matchPattern(HexPattern pat, CastingEnvironment return new PatternShapeMatch.Nothing(); } - /** - * Try to match this pattern to an action, whether via a normal pattern, a per-world pattern, or the machinations - * of a special handler. Will never attempt to check alternate stroke orders, which always throws. - */ - public static PatternShapeMatch matchPattern(HexPattern pattern, CastingEnvironment environment){ - return matchPattern(pattern, environment, false); - } - @Nullable public static HexPattern getCanonicalStrokesPerWorld(ResourceKey key, ServerLevel overworld) { var perWorldPatterns = ScrungledPatternsSave.open(overworld);