From 703e61dd5423252268551c013ec527489abd053d Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sat, 4 Oct 2025 13:39:33 +0200 Subject: [PATCH 01/42] fix preprocessors not applying reliably --- .../com/volmit/iris/engine/IrisEngine.java | 1162 +++++++++-------- .../engine/platform/BukkitChunkGenerator.java | 2 + .../scripting/kotlin/base/EngineScript.kt | 2 +- .../kotlin/base/PreprocessorScript.kt | 11 +- .../environment/IrisExecutionEnvironment.kt | 12 +- .../IrisSimpleExecutionEnvironment.kt | 4 +- .../core/scripting/kotlin/runner/Utils.kt | 5 +- 7 files changed, 610 insertions(+), 588 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/engine/IrisEngine.java b/core/src/main/java/com/volmit/iris/engine/IrisEngine.java index 3586965e7..b6e0fef08 100644 --- a/core/src/main/java/com/volmit/iris/engine/IrisEngine.java +++ b/core/src/main/java/com/volmit/iris/engine/IrisEngine.java @@ -1,578 +1,584 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.engine; - -import com.google.common.util.concurrent.AtomicDouble; -import com.google.gson.Gson; -import com.volmit.iris.Iris; -import com.volmit.iris.core.ServerConfigurator; -import com.volmit.iris.core.events.IrisEngineHotloadEvent; -import com.volmit.iris.core.gui.PregeneratorJob; -import com.volmit.iris.core.loader.ResourceLoader; -import com.volmit.iris.core.nms.container.BlockPos; -import com.volmit.iris.core.nms.container.Pair; -import com.volmit.iris.core.project.IrisProject; -import com.volmit.iris.core.scripting.environment.EngineEnvironment; -import com.volmit.iris.core.service.PreservationSVC; -import com.volmit.iris.engine.data.cache.AtomicCache; -import com.volmit.iris.engine.framework.*; -import com.volmit.iris.engine.mantle.EngineMantle; -import com.volmit.iris.engine.object.*; -import com.volmit.iris.util.atomics.AtomicRollingSequence; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.context.ChunkContext; -import com.volmit.iris.util.context.IrisContext; -import com.volmit.iris.util.documentation.BlockCoordinates; -import com.volmit.iris.util.format.C; -import com.volmit.iris.util.format.Form; -import com.volmit.iris.util.hunk.Hunk; -import com.volmit.iris.util.io.IO; -import com.volmit.iris.util.mantle.flag.MantleFlag; -import com.volmit.iris.util.math.M; -import com.volmit.iris.util.math.RNG; -import com.volmit.iris.util.matter.MatterStructurePOI; -import com.volmit.iris.util.matter.slices.container.JigsawStructureContainer; -import com.volmit.iris.util.scheduling.ChronoLatch; -import com.volmit.iris.util.scheduling.J; -import com.volmit.iris.util.scheduling.PrecisionStopwatch; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import org.bukkit.Material; -import org.bukkit.block.Biome; -import org.bukkit.block.data.BlockData; -import org.bukkit.command.CommandSender; - -import java.io.File; -import java.io.IOException; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; - -@Data -@EqualsAndHashCode(exclude = "context") -@ToString(exclude = "context") -public class IrisEngine implements Engine { - private final AtomicInteger bud; - private final AtomicInteger buds; - private final AtomicInteger generated; - private final AtomicInteger generatedLast; - private final AtomicDouble perSecond; - private final AtomicLong lastGPS; - private final EngineTarget target; - private final IrisContext context; - private final EngineMantle mantle; - private final ChronoLatch perSecondLatch; - private final ChronoLatch perSecondBudLatch; - private final EngineMetrics metrics; - private final boolean studio; - private final AtomicRollingSequence wallClock; - private final int art; - private final AtomicCache engineData = new AtomicCache<>(); - private final AtomicBoolean cleaning; - private final ChronoLatch cleanLatch; - private final SeedManager seedManager; - private CompletableFuture hash32; - private EngineMode mode; - private EngineEffects effects; - private EngineEnvironment execution; - private EngineWorldManager worldManager; - private volatile int parallelism; - private boolean failing; - private boolean closed; - private int cacheId; - private double maxBiomeObjectDensity; - private double maxBiomeLayerDensity; - private double maxBiomeDecoratorDensity; - private IrisComplex complex; - - public IrisEngine(EngineTarget target, boolean studio) { - this.studio = studio; - this.target = target; - getEngineData(); - verifySeed(); - this.seedManager = new SeedManager(target.getWorld().getRawWorldSeed()); - bud = new AtomicInteger(0); - buds = new AtomicInteger(0); - metrics = new EngineMetrics(32); - cleanLatch = new ChronoLatch(10000); - generatedLast = new AtomicInteger(0); - perSecond = new AtomicDouble(0); - perSecondLatch = new ChronoLatch(1000, false); - perSecondBudLatch = new ChronoLatch(1000, false); - wallClock = new AtomicRollingSequence(32); - lastGPS = new AtomicLong(M.ms()); - generated = new AtomicInteger(0); - mantle = new IrisEngineMantle(this); - context = new IrisContext(this); - cleaning = new AtomicBoolean(false); - context.touch(); - getData().setEngine(this); - getData().loadPrefetch(this); - Iris.info("Initializing Engine: " + target.getWorld().name() + "/" + target.getDimension().getLoadKey() + " (" + target.getDimension().getDimensionHeight() + " height) Seed: " + getSeedManager().getSeed()); - failing = false; - closed = false; - art = J.ar(this::tickRandomPlayer, 0); - setupEngine(); - Iris.debug("Engine Initialized " + getCacheID()); - } - - private void verifySeed() { - if (getEngineData().getSeed() != null && getEngineData().getSeed() != target.getWorld().getRawWorldSeed()) { - target.getWorld().setRawWorldSeed(getEngineData().getSeed()); - } - } - - private void tickRandomPlayer() { - recycle(); - if (perSecondBudLatch.flip()) { - buds.set(bud.get()); - bud.set(0); - } - - if (effects != null) { - effects.tickRandomPlayer(); - } - } - - private void prehotload() { - worldManager.close(); - complex.close(); - execution.close(); - effects.close(); - mode.close(); - - J.a(() -> new IrisProject(getData().getDataFolder()).updateWorkspace()); - } - - private void setupEngine() { - try { - Iris.debug("Setup Engine " + getCacheID()); - cacheId = RNG.r.nextInt(); - worldManager = new IrisWorldManager(this); - complex = new IrisComplex(this); - execution = EngineEnvironment.create(this); - effects = new IrisEngineEffects(this); - hash32 = new CompletableFuture<>(); - mantle.hotload(); - setupMode(); - getDimension().getEngineScripts().forEach(execution::execute); - J.a(this::computeBiomeMaxes); - J.a(() -> { - File[] roots = getData().getLoaders() - .values() - .stream() - .map(ResourceLoader::getFolderName) - .map(n -> new File(getData().getDataFolder(), n)) - .filter(File::exists) - .filter(File::isDirectory) - .toArray(File[]::new); - hash32.complete(IO.hashRecursive(roots)); - }); - } catch (Throwable e) { - Iris.error("FAILED TO SETUP ENGINE!"); - e.printStackTrace(); - } - - Iris.debug("Engine Setup Complete " + getCacheID()); - } - - private void setupMode() { - if (mode != null) { - mode.close(); - } - - mode = getDimension().getMode().create(this); - } - - @Override - public void generateMatter(int x, int z, boolean multicore, ChunkContext context) { - getMantle().generateMatter(x, z, multicore, context); - } - - @Override - public Set getObjectsAt(int x, int z) { - return getMantle().getObjectComponent().guess(x, z); - } - - @Override - public Set> getPOIsAt(int chunkX, int chunkY) { - Set> pois = new HashSet<>(); - getMantle().getMantle().iterateChunk(chunkX, chunkY, MatterStructurePOI.class, (x, y, z, d) -> pois.add(new Pair<>(d.getType(), new BlockPos(x, y, z)))); - return pois; - } - - @Override - public IrisJigsawStructure getStructureAt(int x, int z) { - return getMantle().getJigsawComponent().guess(x, z); - } - - @Override - public IrisJigsawStructure getStructureAt(int x, int y, int z) { - var container = getMantle().getMantle().get(x, y, z, JigsawStructureContainer.class); - return container == null ? null : container.load(getData()); - } - - private void warmupChunk(int x, int z) { - for (int i = 0; i < 16; i++) { - for (int j = 0; j < 16; j++) { - int xx = x + (i << 4); - int zz = z + (z << 4); - getComplex().getTrueBiomeStream().get(xx, zz); - getComplex().getHeightStream().get(xx, zz); - } - } - } - - @Override - public void hotload() { - hotloadSilently(); - Iris.callEvent(new IrisEngineHotloadEvent(this)); - } - - public void hotloadComplex() { - complex.close(); - complex = new IrisComplex(this); - } - - public void hotloadSilently() { - getData().dump(); - getData().clearLists(); - getTarget().setDimension(getData().getDimensionLoader().load(getDimension().getLoadKey())); - prehotload(); - setupEngine(); - J.a(() -> { - synchronized (ServerConfigurator.class) { - ServerConfigurator.installDataPacks(false); - } - }); - } - - @Override - public IrisEngineData getEngineData() { - return engineData.aquire(() -> { - //TODO: Method this file - File f = new File(getWorld().worldFolder(), "iris/engine-data/" + getDimension().getLoadKey() + ".json"); - IrisEngineData data = null; - - if (f.exists()) { - try { - data = new Gson().fromJson(IO.readAll(f), IrisEngineData.class); - if (data == null) { - Iris.error("Failed to read Engine Data! Corrupted File? recreating..."); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - - if (data == null) { - data = new IrisEngineData(); - data.getStatistics().setVersion(Iris.instance.getIrisVersion()); - data.getStatistics().setMCVersion(Iris.instance.getMCVersion()); - data.getStatistics().setUpgradedVersion(Iris.instance.getIrisVersion()); - if (data.getStatistics().getVersion() == -1 || data.getStatistics().getMCVersion() == -1 ) { - Iris.error("Failed to setup Engine Data!"); - } - - if (f.getParentFile().exists() || f.getParentFile().mkdirs()) { - try { - IO.writeAll(f, new Gson().toJson(data)); - } catch (IOException e) { - e.printStackTrace(); - } - } else { - Iris.error("Failed to setup Engine Data!"); - } - } - - return data; - }); - } - - @Override - public int getGenerated() { - return generated.get(); - } - - @Override - public double getGeneratedPerSecond() { - if (perSecondLatch.flip()) { - double g = generated.get() - generatedLast.get(); - generatedLast.set(generated.get()); - - if (g == 0) { - return 0; - } - - long dur = M.ms() - lastGPS.get(); - lastGPS.set(M.ms()); - perSecond.set(g / ((double) (dur) / 1000D)); - } - - return perSecond.get(); - } - - @Override - public boolean isStudio() { - return studio; - } - - private void computeBiomeMaxes() { - for (IrisBiome i : getDimension().getAllBiomes(this)) { - double density = 0; - - for (IrisObjectPlacement j : i.getObjects()) { - density += j.getDensity() * j.getChance(); - } - - maxBiomeObjectDensity = Math.max(maxBiomeObjectDensity, density); - density = 0; - - for (IrisDecorator j : i.getDecorators()) { - density += Math.max(j.getStackMax(), 1) * j.getChance(); - } - - maxBiomeDecoratorDensity = Math.max(maxBiomeDecoratorDensity, density); - density = 0; - - for (IrisBiomePaletteLayer j : i.getLayers()) { - density++; - } - - maxBiomeLayerDensity = Math.max(maxBiomeLayerDensity, density); - } - } - - @Override - public int getBlockUpdatesPerSecond() { - return buds.get(); - } - - public void printMetrics(CommandSender sender) { - KMap totals = new KMap<>(); - KMap weights = new KMap<>(); - double masterWallClock = wallClock.getAverage(); - KMap timings = getMetrics().pull(); - double totalWeight = 0; - double wallClock = getMetrics().getTotal().getAverage(); - - for (double j : timings.values()) { - totalWeight += j; - } - - for (String j : timings.k()) { - weights.put(getName() + "." + j, (wallClock / totalWeight) * timings.get(j)); - } - - totals.put(getName(), wallClock); - - double mtotals = 0; - - for (double i : totals.values()) { - mtotals += i; - } - - for (String i : totals.k()) { - totals.put(i, (masterWallClock / mtotals) * totals.get(i)); - } - - double v = 0; - - for (double i : weights.values()) { - v += i; - } - - for (String i : weights.k()) { - weights.put(i, weights.get(i) / v); - } - - sender.sendMessage("Total: " + C.BOLD + C.WHITE + Form.duration(masterWallClock, 0)); - - for (String i : totals.k()) { - sender.sendMessage(" Engine " + C.UNDERLINE + C.GREEN + i + C.RESET + ": " + C.BOLD + C.WHITE + Form.duration(totals.get(i), 0)); - } - - sender.sendMessage("Details: "); - - for (String i : weights.sortKNumber().reverse()) { - String befb = C.UNDERLINE + "" + C.GREEN + "" + i.split("\\Q[\\E")[0] + C.RESET + C.GRAY + "["; - String num = C.GOLD + i.split("\\Q[\\E")[1].split("]")[0] + C.RESET + C.GRAY + "]."; - String afb = C.ITALIC + "" + C.AQUA + i.split("\\Q]\\E")[1].substring(1) + C.RESET + C.GRAY; - - sender.sendMessage(" " + befb + num + afb + ": " + C.BOLD + C.WHITE + Form.pc(weights.get(i), 0)); - } - } - - @Override - public void close() { - PregeneratorJob.shutdownInstance(); - closed = true; - J.car(art); - getWorldManager().close(); - getTarget().close(); - saveEngineData(); - getMantle().close(); - getComplex().close(); - mode.close(); - getData().dump(); - getData().clearLists(); - Iris.service(PreservationSVC.class).dereference(); - Iris.debug("Engine Fully Shutdown!"); - complex = null; - } - - @Override - public boolean isClosed() { - return closed; - } - - @Override - public void recycle() { - if (!cleanLatch.flip()) { - return; - } - - if (cleaning.get()) { - cleanLatch.flipDown(); - return; - } - - cleaning.set(true); - - J.a(() -> { - try { - getData().getObjectLoader().clean(); - } catch (Throwable e) { - Iris.reportError(e); - Iris.error("Cleanup failed! Enable debug to see stacktrace."); - } - - cleaning.lazySet(false); - }); - } - - @BlockCoordinates - @Override - public void generate(int x, int z, Hunk vblocks, Hunk vbiomes, boolean multicore) throws WrongEngineBroException { - if (closed) { - throw new WrongEngineBroException(); - } - - context.touch(); - getEngineData().getStatistics().generatedChunk(); - try { - PrecisionStopwatch p = PrecisionStopwatch.start(); - Hunk blocks = vblocks.listen((xx, y, zz, t) -> catchBlockUpdates(x + xx, y, z + zz, t)); - - if (getDimension().isDebugChunkCrossSections() && ((x >> 4) % getDimension().getDebugCrossSectionsMod() == 0 || (z >> 4) % getDimension().getDebugCrossSectionsMod() == 0)) { - for (int i = 0; i < 16; i++) { - for (int j = 0; j < 16; j++) { - blocks.set(i, 0, j, Material.CRYING_OBSIDIAN.createBlockData()); - } - } - } else { - mode.generate(x, z, blocks, vbiomes, multicore); - } - - getMantle().getMantle().flag(x >> 4, z >> 4, MantleFlag.REAL, true); - getMetrics().getTotal().put(p.getMilliseconds()); - generated.incrementAndGet(); - - if (generated.get() == 661) { - J.a(() -> getData().savePrefetch(this)); - } - } catch (Throwable e) { - Iris.reportError(e); - fail("Failed to generate " + x + ", " + z, e); - } - } - - @Override - public void saveEngineData() { - //TODO: Method this file - File f = new File(getWorld().worldFolder(), "iris/engine-data/" + getDimension().getLoadKey() + ".json"); - f.getParentFile().mkdirs(); - try { - IO.writeAll(f, new Gson().toJson(getEngineData())); - Iris.debug("Saved Engine Data"); - } catch (IOException e) { - Iris.error("Failed to save Engine Data"); - e.printStackTrace(); - } - } - - @Override - public void blockUpdatedMetric() { - bud.incrementAndGet(); - } - - @Override - public IrisBiome getFocus() { - if (getDimension().getFocus() == null || getDimension().getFocus().trim().isEmpty()) { - return null; - } - - return getData().getBiomeLoader().load(getDimension().getFocus()); - } - - @Override - public IrisRegion getFocusRegion() { - if (getDimension().getFocusRegion() == null || getDimension().getFocusRegion().trim().isEmpty()) { - return null; - } - - return getData().getRegionLoader().load(getDimension().getFocusRegion()); - } - - @Override - public void fail(String error, Throwable e) { - failing = true; - Iris.error(error); - e.printStackTrace(); - } - - @Override - public boolean hasFailed() { - return failing; - } - - @Override - public int getCacheID() { - return cacheId; - } - - private boolean EngineSafe() { - // Todo: this has potential if done right - int EngineMCVersion = getEngineData().getStatistics().getMCVersion(); - int EngineIrisVersion = getEngineData().getStatistics().getVersion(); - int MinecraftVersion = Iris.instance.getMCVersion(); - int IrisVersion = Iris.instance.getIrisVersion(); - if (EngineIrisVersion != IrisVersion) { - return false; - } - if (EngineMCVersion != MinecraftVersion) { - return false; - } - return true; - } -} +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2022 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.volmit.iris.engine; + +import com.google.common.util.concurrent.AtomicDouble; +import com.google.gson.Gson; +import com.volmit.iris.Iris; +import com.volmit.iris.core.ServerConfigurator; +import com.volmit.iris.core.events.IrisEngineHotloadEvent; +import com.volmit.iris.core.gui.PregeneratorJob; +import com.volmit.iris.core.loader.ResourceLoader; +import com.volmit.iris.core.nms.container.BlockPos; +import com.volmit.iris.core.nms.container.Pair; +import com.volmit.iris.core.project.IrisProject; +import com.volmit.iris.core.scripting.environment.EngineEnvironment; +import com.volmit.iris.core.service.PreservationSVC; +import com.volmit.iris.engine.data.cache.AtomicCache; +import com.volmit.iris.engine.framework.*; +import com.volmit.iris.engine.mantle.EngineMantle; +import com.volmit.iris.engine.object.*; +import com.volmit.iris.util.atomics.AtomicRollingSequence; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.context.ChunkContext; +import com.volmit.iris.util.context.IrisContext; +import com.volmit.iris.util.documentation.BlockCoordinates; +import com.volmit.iris.util.format.C; +import com.volmit.iris.util.format.Form; +import com.volmit.iris.util.hunk.Hunk; +import com.volmit.iris.util.io.IO; +import com.volmit.iris.util.mantle.flag.MantleFlag; +import com.volmit.iris.util.math.M; +import com.volmit.iris.util.math.RNG; +import com.volmit.iris.util.matter.MatterStructurePOI; +import com.volmit.iris.util.matter.slices.container.JigsawStructureContainer; +import com.volmit.iris.util.scheduling.ChronoLatch; +import com.volmit.iris.util.scheduling.J; +import com.volmit.iris.util.scheduling.PrecisionStopwatch; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.bukkit.Material; +import org.bukkit.block.Biome; +import org.bukkit.block.data.BlockData; +import org.bukkit.command.CommandSender; + +import java.io.File; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +@Data +@EqualsAndHashCode(exclude = "context") +@ToString(exclude = "context") +public class IrisEngine implements Engine { + private final AtomicInteger bud; + private final AtomicInteger buds; + private final AtomicInteger generated; + private final AtomicInteger generatedLast; + private final AtomicDouble perSecond; + private final AtomicLong lastGPS; + private final EngineTarget target; + private final IrisContext context; + private final EngineMantle mantle; + private final ChronoLatch perSecondLatch; + private final ChronoLatch perSecondBudLatch; + private final EngineMetrics metrics; + private final boolean studio; + private final AtomicRollingSequence wallClock; + private final int art; + private final AtomicCache engineData = new AtomicCache<>(); + private final AtomicBoolean cleaning; + private final ChronoLatch cleanLatch; + private final SeedManager seedManager; + private CompletableFuture hash32; + private EngineMode mode; + private EngineEffects effects; + private EngineEnvironment execution; + private EngineWorldManager worldManager; + private volatile int parallelism; + private boolean failing; + private boolean closed; + private int cacheId; + private double maxBiomeObjectDensity; + private double maxBiomeLayerDensity; + private double maxBiomeDecoratorDensity; + private IrisComplex complex; + + public IrisEngine(EngineTarget target, boolean studio) { + this.studio = studio; + this.target = target; + getEngineData(); + verifySeed(); + this.seedManager = new SeedManager(target.getWorld().getRawWorldSeed()); + bud = new AtomicInteger(0); + buds = new AtomicInteger(0); + metrics = new EngineMetrics(32); + cleanLatch = new ChronoLatch(10000); + generatedLast = new AtomicInteger(0); + perSecond = new AtomicDouble(0); + perSecondLatch = new ChronoLatch(1000, false); + perSecondBudLatch = new ChronoLatch(1000, false); + wallClock = new AtomicRollingSequence(32); + lastGPS = new AtomicLong(M.ms()); + generated = new AtomicInteger(0); + mantle = new IrisEngineMantle(this); + context = new IrisContext(this); + cleaning = new AtomicBoolean(false); + execution = EngineEnvironment.create(this); + if (studio) { + getData().dump(); + getData().clearLists(); + getTarget().setDimension(getData().getDimensionLoader().load(getDimension().getLoadKey())); + } + context.touch(); + getData().setEngine(this); + getData().loadPrefetch(this); + Iris.info("Initializing Engine: " + target.getWorld().name() + "/" + target.getDimension().getLoadKey() + " (" + target.getDimension().getDimensionHeight() + " height) Seed: " + getSeedManager().getSeed()); + failing = false; + closed = false; + art = J.ar(this::tickRandomPlayer, 0); + setupEngine(); + Iris.debug("Engine Initialized " + getCacheID()); + } + + private void verifySeed() { + if (getEngineData().getSeed() != null && getEngineData().getSeed() != target.getWorld().getRawWorldSeed()) { + target.getWorld().setRawWorldSeed(getEngineData().getSeed()); + } + } + + private void tickRandomPlayer() { + recycle(); + if (perSecondBudLatch.flip()) { + buds.set(bud.get()); + bud.set(0); + } + + if (effects != null) { + effects.tickRandomPlayer(); + } + } + + private void prehotload() { + worldManager.close(); + complex.close(); + execution.close(); + effects.close(); + mode.close(); + execution = EngineEnvironment.create(this); + + J.a(() -> new IrisProject(getData().getDataFolder()).updateWorkspace()); + } + + private void setupEngine() { + try { + Iris.debug("Setup Engine " + getCacheID()); + cacheId = RNG.r.nextInt(); + worldManager = new IrisWorldManager(this); + complex = new IrisComplex(this); + effects = new IrisEngineEffects(this); + hash32 = new CompletableFuture<>(); + mantle.hotload(); + setupMode(); + getDimension().getEngineScripts().forEach(execution::execute); + J.a(this::computeBiomeMaxes); + J.a(() -> { + File[] roots = getData().getLoaders() + .values() + .stream() + .map(ResourceLoader::getFolderName) + .map(n -> new File(getData().getDataFolder(), n)) + .filter(File::exists) + .filter(File::isDirectory) + .toArray(File[]::new); + hash32.complete(IO.hashRecursive(roots)); + }); + } catch (Throwable e) { + Iris.error("FAILED TO SETUP ENGINE!"); + e.printStackTrace(); + } + + Iris.debug("Engine Setup Complete " + getCacheID()); + } + + private void setupMode() { + if (mode != null) { + mode.close(); + } + + mode = getDimension().getMode().create(this); + } + + @Override + public void generateMatter(int x, int z, boolean multicore, ChunkContext context) { + getMantle().generateMatter(x, z, multicore, context); + } + + @Override + public Set getObjectsAt(int x, int z) { + return getMantle().getObjectComponent().guess(x, z); + } + + @Override + public Set> getPOIsAt(int chunkX, int chunkY) { + Set> pois = new HashSet<>(); + getMantle().getMantle().iterateChunk(chunkX, chunkY, MatterStructurePOI.class, (x, y, z, d) -> pois.add(new Pair<>(d.getType(), new BlockPos(x, y, z)))); + return pois; + } + + @Override + public IrisJigsawStructure getStructureAt(int x, int z) { + return getMantle().getJigsawComponent().guess(x, z); + } + + @Override + public IrisJigsawStructure getStructureAt(int x, int y, int z) { + var container = getMantle().getMantle().get(x, y, z, JigsawStructureContainer.class); + return container == null ? null : container.load(getData()); + } + + private void warmupChunk(int x, int z) { + for (int i = 0; i < 16; i++) { + for (int j = 0; j < 16; j++) { + int xx = x + (i << 4); + int zz = z + (z << 4); + getComplex().getTrueBiomeStream().get(xx, zz); + getComplex().getHeightStream().get(xx, zz); + } + } + } + + @Override + public void hotload() { + hotloadSilently(); + Iris.callEvent(new IrisEngineHotloadEvent(this)); + } + + public void hotloadComplex() { + complex.close(); + complex = new IrisComplex(this); + } + + public void hotloadSilently() { + getData().dump(); + getData().clearLists(); + getTarget().setDimension(getData().getDimensionLoader().load(getDimension().getLoadKey())); + prehotload(); + setupEngine(); + J.a(() -> { + synchronized (ServerConfigurator.class) { + ServerConfigurator.installDataPacks(false); + } + }); + } + + @Override + public IrisEngineData getEngineData() { + return engineData.aquire(() -> { + //TODO: Method this file + File f = new File(getWorld().worldFolder(), "iris/engine-data/" + getDimension().getLoadKey() + ".json"); + IrisEngineData data = null; + + if (f.exists()) { + try { + data = new Gson().fromJson(IO.readAll(f), IrisEngineData.class); + if (data == null) { + Iris.error("Failed to read Engine Data! Corrupted File? recreating..."); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + if (data == null) { + data = new IrisEngineData(); + data.getStatistics().setVersion(Iris.instance.getIrisVersion()); + data.getStatistics().setMCVersion(Iris.instance.getMCVersion()); + data.getStatistics().setUpgradedVersion(Iris.instance.getIrisVersion()); + if (data.getStatistics().getVersion() == -1 || data.getStatistics().getMCVersion() == -1 ) { + Iris.error("Failed to setup Engine Data!"); + } + + if (f.getParentFile().exists() || f.getParentFile().mkdirs()) { + try { + IO.writeAll(f, new Gson().toJson(data)); + } catch (IOException e) { + e.printStackTrace(); + } + } else { + Iris.error("Failed to setup Engine Data!"); + } + } + + return data; + }); + } + + @Override + public int getGenerated() { + return generated.get(); + } + + @Override + public double getGeneratedPerSecond() { + if (perSecondLatch.flip()) { + double g = generated.get() - generatedLast.get(); + generatedLast.set(generated.get()); + + if (g == 0) { + return 0; + } + + long dur = M.ms() - lastGPS.get(); + lastGPS.set(M.ms()); + perSecond.set(g / ((double) (dur) / 1000D)); + } + + return perSecond.get(); + } + + @Override + public boolean isStudio() { + return studio; + } + + private void computeBiomeMaxes() { + for (IrisBiome i : getDimension().getAllBiomes(this)) { + double density = 0; + + for (IrisObjectPlacement j : i.getObjects()) { + density += j.getDensity() * j.getChance(); + } + + maxBiomeObjectDensity = Math.max(maxBiomeObjectDensity, density); + density = 0; + + for (IrisDecorator j : i.getDecorators()) { + density += Math.max(j.getStackMax(), 1) * j.getChance(); + } + + maxBiomeDecoratorDensity = Math.max(maxBiomeDecoratorDensity, density); + density = 0; + + for (IrisBiomePaletteLayer j : i.getLayers()) { + density++; + } + + maxBiomeLayerDensity = Math.max(maxBiomeLayerDensity, density); + } + } + + @Override + public int getBlockUpdatesPerSecond() { + return buds.get(); + } + + public void printMetrics(CommandSender sender) { + KMap totals = new KMap<>(); + KMap weights = new KMap<>(); + double masterWallClock = wallClock.getAverage(); + KMap timings = getMetrics().pull(); + double totalWeight = 0; + double wallClock = getMetrics().getTotal().getAverage(); + + for (double j : timings.values()) { + totalWeight += j; + } + + for (String j : timings.k()) { + weights.put(getName() + "." + j, (wallClock / totalWeight) * timings.get(j)); + } + + totals.put(getName(), wallClock); + + double mtotals = 0; + + for (double i : totals.values()) { + mtotals += i; + } + + for (String i : totals.k()) { + totals.put(i, (masterWallClock / mtotals) * totals.get(i)); + } + + double v = 0; + + for (double i : weights.values()) { + v += i; + } + + for (String i : weights.k()) { + weights.put(i, weights.get(i) / v); + } + + sender.sendMessage("Total: " + C.BOLD + C.WHITE + Form.duration(masterWallClock, 0)); + + for (String i : totals.k()) { + sender.sendMessage(" Engine " + C.UNDERLINE + C.GREEN + i + C.RESET + ": " + C.BOLD + C.WHITE + Form.duration(totals.get(i), 0)); + } + + sender.sendMessage("Details: "); + + for (String i : weights.sortKNumber().reverse()) { + String befb = C.UNDERLINE + "" + C.GREEN + "" + i.split("\\Q[\\E")[0] + C.RESET + C.GRAY + "["; + String num = C.GOLD + i.split("\\Q[\\E")[1].split("]")[0] + C.RESET + C.GRAY + "]."; + String afb = C.ITALIC + "" + C.AQUA + i.split("\\Q]\\E")[1].substring(1) + C.RESET + C.GRAY; + + sender.sendMessage(" " + befb + num + afb + ": " + C.BOLD + C.WHITE + Form.pc(weights.get(i), 0)); + } + } + + @Override + public void close() { + PregeneratorJob.shutdownInstance(); + closed = true; + J.car(art); + getWorldManager().close(); + getTarget().close(); + saveEngineData(); + getMantle().close(); + getComplex().close(); + mode.close(); + getData().dump(); + getData().clearLists(); + Iris.service(PreservationSVC.class).dereference(); + Iris.debug("Engine Fully Shutdown!"); + complex = null; + } + + @Override + public boolean isClosed() { + return closed; + } + + @Override + public void recycle() { + if (!cleanLatch.flip()) { + return; + } + + if (cleaning.get()) { + cleanLatch.flipDown(); + return; + } + + cleaning.set(true); + + J.a(() -> { + try { + getData().getObjectLoader().clean(); + } catch (Throwable e) { + Iris.reportError(e); + Iris.error("Cleanup failed! Enable debug to see stacktrace."); + } + + cleaning.lazySet(false); + }); + } + + @BlockCoordinates + @Override + public void generate(int x, int z, Hunk vblocks, Hunk vbiomes, boolean multicore) throws WrongEngineBroException { + if (closed) { + throw new WrongEngineBroException(); + } + + context.touch(); + getEngineData().getStatistics().generatedChunk(); + try { + PrecisionStopwatch p = PrecisionStopwatch.start(); + Hunk blocks = vblocks.listen((xx, y, zz, t) -> catchBlockUpdates(x + xx, y, z + zz, t)); + + if (getDimension().isDebugChunkCrossSections() && ((x >> 4) % getDimension().getDebugCrossSectionsMod() == 0 || (z >> 4) % getDimension().getDebugCrossSectionsMod() == 0)) { + for (int i = 0; i < 16; i++) { + for (int j = 0; j < 16; j++) { + blocks.set(i, 0, j, Material.CRYING_OBSIDIAN.createBlockData()); + } + } + } else { + mode.generate(x, z, blocks, vbiomes, multicore); + } + + getMantle().getMantle().flag(x >> 4, z >> 4, MantleFlag.REAL, true); + getMetrics().getTotal().put(p.getMilliseconds()); + generated.incrementAndGet(); + + if (generated.get() == 661) { + J.a(() -> getData().savePrefetch(this)); + } + } catch (Throwable e) { + Iris.reportError(e); + fail("Failed to generate " + x + ", " + z, e); + } + } + + @Override + public void saveEngineData() { + //TODO: Method this file + File f = new File(getWorld().worldFolder(), "iris/engine-data/" + getDimension().getLoadKey() + ".json"); + f.getParentFile().mkdirs(); + try { + IO.writeAll(f, new Gson().toJson(getEngineData())); + Iris.debug("Saved Engine Data"); + } catch (IOException e) { + Iris.error("Failed to save Engine Data"); + e.printStackTrace(); + } + } + + @Override + public void blockUpdatedMetric() { + bud.incrementAndGet(); + } + + @Override + public IrisBiome getFocus() { + if (getDimension().getFocus() == null || getDimension().getFocus().trim().isEmpty()) { + return null; + } + + return getData().getBiomeLoader().load(getDimension().getFocus()); + } + + @Override + public IrisRegion getFocusRegion() { + if (getDimension().getFocusRegion() == null || getDimension().getFocusRegion().trim().isEmpty()) { + return null; + } + + return getData().getRegionLoader().load(getDimension().getFocusRegion()); + } + + @Override + public void fail(String error, Throwable e) { + failing = true; + Iris.error(error); + e.printStackTrace(); + } + + @Override + public boolean hasFailed() { + return failing; + } + + @Override + public int getCacheID() { + return cacheId; + } + + private boolean EngineSafe() { + // Todo: this has potential if done right + int EngineMCVersion = getEngineData().getStatistics().getMCVersion(); + int EngineIrisVersion = getEngineData().getStatistics().getVersion(); + int MinecraftVersion = Iris.instance.getMCVersion(); + int IrisVersion = Iris.instance.getIrisVersion(); + if (EngineIrisVersion != IrisVersion) { + return false; + } + if (EngineMCVersion != MinecraftVersion) { + return false; + } + return true; + } +} diff --git a/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java b/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java index c16bebc3a..fe991f753 100644 --- a/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java +++ b/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java @@ -166,6 +166,8 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun return targetCache.aquire(() -> { IrisData data = IrisData.get(dataLocation); + data.dump(); + data.clearLists(); IrisDimension dimension = data.getDimensionLoader().load(dimensionKey); if (dimension == null) { diff --git a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/base/EngineScript.kt b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/base/EngineScript.kt index 768f2a9d3..8a091a50f 100644 --- a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/base/EngineScript.kt +++ b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/base/EngineScript.kt @@ -14,9 +14,9 @@ abstract class EngineScript object EngineScriptDefinition : ScriptCompilationConfiguration(listOf(DataScriptDefinition), { providedProperties( "engine" to Engine::class, - "complex" to IrisComplex::class, "seed" to Long::class, "dimension" to IrisDimension::class, + "complex" to IrisComplex::class, "biome" to BiomeLookup::class, ) }) { diff --git a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/base/PreprocessorScript.kt b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/base/PreprocessorScript.kt index f594649e1..c8941bb8d 100644 --- a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/base/PreprocessorScript.kt +++ b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/base/PreprocessorScript.kt @@ -1,6 +1,8 @@ package com.volmit.iris.core.scripting.kotlin.base import com.volmit.iris.core.loader.IrisRegistrant +import com.volmit.iris.engine.framework.Engine +import com.volmit.iris.engine.`object`.IrisDimension import kotlin.script.experimental.annotations.KotlinScript import kotlin.script.experimental.api.ScriptCompilationConfiguration import kotlin.script.experimental.api.providedProperties @@ -8,8 +10,13 @@ import kotlin.script.experimental.api.providedProperties @KotlinScript(fileExtension = "proc.kts", compilationConfiguration = PreprocessorScriptDefinition::class) abstract class PreprocessorScript -object PreprocessorScriptDefinition : ScriptCompilationConfiguration(listOf(EngineScriptDefinition), { - providedProperties("object" to IrisRegistrant::class) +object PreprocessorScriptDefinition : ScriptCompilationConfiguration(listOf(DataScriptDefinition), { + providedProperties( + "engine" to Engine::class, + "seed" to Long::class, + "dimension" to IrisDimension::class, + "object" to IrisRegistrant::class + ) }) { private fun readResolve(): Any = PreprocessorScriptDefinition } \ No newline at end of file diff --git a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisExecutionEnvironment.kt b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisExecutionEnvironment.kt index fc80c7738..2bebf8c9f 100644 --- a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisExecutionEnvironment.kt +++ b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisExecutionEnvironment.kt @@ -33,18 +33,24 @@ data class IrisExecutionEnvironment( execute(script, PostMobSpawningScript::class.java, engine.parameters("location" to location, "entity" to mob)) override fun preprocessObject(script: String, `object`: IrisRegistrant) = - execute(script, PreprocessorScript::class.java, engine.parameters("object" to `object`)) + execute(script, PreprocessorScript::class.java, engine.limitedParameters("object" to `object`)) override fun updateChunk(script: String, mantleChunk: MantleChunk, chunk: Chunk, executor: UpdateExecutor) = execute(script, ChunkUpdateScript::class.java, engine.parameters("mantleChunk" to mantleChunk, "chunk" to chunk, "executor" to executor)) - private fun Engine.parameters(vararg values: Pair): Map { + private fun Engine.limitedParameters(vararg values: Pair): Map { return mapOf( "data" to data, "engine" to this, - "complex" to complex, "seed" to seedManager.seed, "dimension" to dimension, + *values, + ) + } + + private fun Engine.parameters(vararg values: Pair): Map { + return limitedParameters( + "complex" to complex, "biome" to BiomeLookup(::getSurfaceBiome), *values, ) diff --git a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisSimpleExecutionEnvironment.kt b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisSimpleExecutionEnvironment.kt index 2896b5821..9987a0e01 100644 --- a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisSimpleExecutionEnvironment.kt +++ b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisSimpleExecutionEnvironment.kt @@ -7,7 +7,7 @@ import com.volmit.iris.core.scripting.kotlin.base.* import com.volmit.iris.core.scripting.kotlin.runner.Script import com.volmit.iris.core.scripting.kotlin.runner.ScriptRunner import com.volmit.iris.core.scripting.kotlin.runner.classpath -import com.volmit.iris.core.scripting.kotlin.runner.valueOrNull +import com.volmit.iris.core.scripting.kotlin.runner.value import com.volmit.iris.core.scripting.kotlin.runner.valueOrThrow import com.volmit.iris.util.collection.KMap import com.volmit.iris.util.data.KCache @@ -68,7 +68,7 @@ open class IrisSimpleExecutionEnvironment( return compile(name, type) .evaluate(properties) .valueOrThrow("Failed to evaluate script") - .valueOrNull() + .value() } catch (e: Throwable) { e.printStackTrace() } diff --git a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/Utils.kt b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/Utils.kt index fc0fdd1a6..88dfcf2e9 100644 --- a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/Utils.kt +++ b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/Utils.kt @@ -21,10 +21,11 @@ internal fun ResultWithDiagnostics.map(transformer: (T) -> R): ResultW is ResultWithDiagnostics.Failure -> this } -internal fun EvaluationResult.valueOrNull() = returnValue.valueOrNull() -internal fun ResultValue.valueOrNull(): Any? = +internal fun EvaluationResult.value() = returnValue.value() +internal fun ResultValue.value(): Any? = when (this) { is ResultValue.Value -> value + is ResultValue.Error -> throw error else -> null } From f3ef1ca2ae069cd9498fca4126394730c32fea77 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sat, 4 Oct 2025 13:40:07 +0200 Subject: [PATCH 02/42] fix typo in preprocessors description --- .../main/java/com/volmit/iris/core/loader/IrisRegistrant.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/volmit/iris/core/loader/IrisRegistrant.java b/core/src/main/java/com/volmit/iris/core/loader/IrisRegistrant.java index b20ac02d4..123fd6f2e 100644 --- a/core/src/main/java/com/volmit/iris/core/loader/IrisRegistrant.java +++ b/core/src/main/java/com/volmit/iris/core/loader/IrisRegistrant.java @@ -35,7 +35,7 @@ import java.io.File; @Data public abstract class IrisRegistrant { - @Desc("Preprocess this object in-memory when it's loaded, run scripts using the variable 'object' and modify properties about this object before it's used.\nFile extension: .prox.kts") + @Desc("Preprocess this object in-memory when it's loaded, run scripts using the variable 'object' and modify properties about this object before it's used.\nFile extension: .proc.kts") @RegistryListResource(IrisScript.class) @ArrayType(min = 1, type = String.class) private KList preprocessors = new KList<>(); From b46c413f6b9fc3e66193a8221df000dfcbf415b3 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sat, 4 Oct 2025 13:40:47 +0200 Subject: [PATCH 03/42] make gradle setup print to console on failure --- .../com/volmit/iris/core/project/Gradle.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/core/project/Gradle.java b/core/src/main/java/com/volmit/iris/core/project/Gradle.java index c28b28ca4..565e7d39f 100644 --- a/core/src/main/java/com/volmit/iris/core/project/Gradle.java +++ b/core/src/main/java/com/volmit/iris/core/project/Gradle.java @@ -9,9 +9,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URI; -import java.util.HashMap; -import java.util.Optional; -import java.util.Scanner; +import java.util.*; public class Gradle { private static final boolean WINDOWS = System.getProperty("os.name").toLowerCase().contains("win"); @@ -38,10 +36,15 @@ public class Gradle { cmd[0] = gradle.getAbsolutePath(); System.arraycopy(args, 0, cmd, 1, args.length); var process = Runtime.getRuntime().exec(cmd, ENVIRONMENT, projectDir); - attach(process.getInputStream()); - attach(process.getErrorStream()); + var lines = Collections.synchronizedList(new ArrayList()); + attach(process.getInputStream(), lines); + attach(process.getErrorStream(), lines); var code = process.waitFor(); - if (code == 0) return; + if (code == 0) { + lines.forEach(Iris::debug); + return; + } + lines.forEach(Iris::error); throw new RuntimeException("Gradle exited with code " + code); } @@ -91,12 +94,12 @@ public class Gradle { .orElseThrow(() -> new RuntimeException("Failed to find java home, please set java.home system property")); } - private static void attach(InputStream stream) { - Thread.ofVirtual().start(() -> { + private static void attach(InputStream stream, List list) { + Thread.ofPlatform().start(() -> { try (var in = new Scanner(stream)) { while (in.hasNextLine()) { String line = in.nextLine(); - Iris.debug("[GRADLE] " + line); + list.add(line); } } }); From 0e237aa1ad084e81ed698a910503f5dc20cee51f Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sat, 4 Oct 2025 13:41:48 +0200 Subject: [PATCH 04/42] fix generated build.gradle.kts on external dives on windows --- .../IrisSimpleExecutionEnvironment.kt | 70 ++++++++++++++++--- .../core/scripting/kotlin/runner/Utils.kt | 27 +++++++ 2 files changed, 86 insertions(+), 11 deletions(-) diff --git a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisSimpleExecutionEnvironment.kt b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisSimpleExecutionEnvironment.kt index 9987a0e01..21d65c5bc 100644 --- a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisSimpleExecutionEnvironment.kt +++ b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisSimpleExecutionEnvironment.kt @@ -4,6 +4,7 @@ import com.volmit.iris.Iris import com.volmit.iris.core.IrisSettings import com.volmit.iris.core.scripting.environment.SimpleEnvironment import com.volmit.iris.core.scripting.kotlin.base.* +import com.volmit.iris.core.scripting.kotlin.runner.FileComponents import com.volmit.iris.core.scripting.kotlin.runner.Script import com.volmit.iris.core.scripting.kotlin.runner.ScriptRunner import com.volmit.iris.core.scripting.kotlin.runner.classpath @@ -89,7 +90,7 @@ open class IrisSimpleExecutionEnvironment( } companion object { - private const val CLASSPATH = "val classpath = files(" + private const val CLASSPATH = "val classpath = mapOf(" private fun File.updateClasspath(classpath: List) { val test = if (exists()) readLines() else BASE_GRADLE @@ -97,24 +98,71 @@ open class IrisSimpleExecutionEnvironment( } private fun List.updateClasspath(classpath: List): String { - val classpath = classpath.joinToString(",", CLASSPATH, ")") { "\"${it.escapedPath}\"" } - val index = indexOfFirst { it.startsWith(CLASSPATH) } - if (index == -1) { - return "$classpath\n${joinToString("\n")}" + val components = linkedMapOf() + classpath.forEach { + val parts = it.canonicalPath.split(File.separatorChar) + if (parts.size <= 1) { + Iris.error("Invalid classpath entry: $it") + return@forEach + } + + var parent = components.computeIfAbsent(parts[0]) { FileComponents(parts[0], true) } + for (part in parts.subList(1, parts.size)) { + parent = parent.append(part) + } } + val mapped = components.values.associate { + var current = it + val root = buildString { + while (current.children.size == 1) { + append(current.segment) + append(File.separatorChar) + current = current.children.first() + } + append(current.segment) + append(File.separatorChar) + } + + val result = mutableSetOf() + val queue = ArrayDeque>>() + queue.add(null to current.children) + while (queue.isNotEmpty()) { + val pair = queue.removeFirst() + val path = pair.first?.let { p -> p + File.separatorChar } ?: "" + pair.second.forEach { child -> + val path = path + child.segment + if (child.children.isEmpty()) result.add(path) + else queue.add(path to child.children) + } + } + + root to result + } + + + val classpath = mapped.entries.joinToString(",", CLASSPATH, ")") { + "\"${it.key}\" to setOf(${it.value.joinToString(", ") { f -> "\"$f\"" }})" + } + + val mod = toMutableList() - mod[index] = classpath + val index = indexOfFirst { it.startsWith(CLASSPATH) } + if (index == -1) { + mod.clear() + mod.addAll(BASE_GRADLE) + } + + mod[if (index == -1) 0 else index] = classpath return mod.joinToString("\n") } private val File.escapedPath get() = absolutePath.replace("\\", "\\\\").replace("\"", "\\\"") - private const val ARTIFACT_ID = $$"local:${it.relativeTo(home).path.substringBeforeLast(\".jar\")}:1.0.0" + private const val ARTIFACT_ID = $$"local:${it.substringBeforeLast(\".jar\")}:1.0.0" private val BASE_GRADLE = """ - val classpath = files() - val home = file(System.getProperty("user.home")) + val classpath = mapOf() plugins { kotlin("jvm") version("2.2.0") @@ -123,7 +171,7 @@ open class IrisSimpleExecutionEnvironment( repositories { mavenCentral() flatDir { - dirs(home) + dirs(classpath.keys) } } @@ -135,7 +183,7 @@ open class IrisSimpleExecutionEnvironment( configurations.kotlinCompilerPluginClasspath { extendsFrom(script) } dependencies { - classpath.forEach { script("$ARTIFACT_ID") } + classpath.values.flatMap { it }.forEach { script("$ARTIFACT_ID") } }""".trimIndent().split("\n") } } \ No newline at end of file diff --git a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/Utils.kt b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/Utils.kt index 88dfcf2e9..c4f1f6e12 100644 --- a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/Utils.kt +++ b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/Utils.kt @@ -29,6 +29,33 @@ internal fun ResultValue.value(): Any? = else -> null } +internal class FileComponents( + val segment: String, + val root: Boolean = false, +) { + private val children0 = mutableMapOf() + val children get() = children0.values + + fun append(segment: String): FileComponents = + children0.computeIfAbsent(segment) { FileComponents(segment) } + + override fun hashCode(): Int { + var result = segment.hashCode() + result = 31 * result + children0.hashCode() + return result + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is FileComponents) return false + + if (segment != other.segment) return false + if (children0 != other.children0) return false + + return true + } +} + private val workDir = File(".").normalize() internal fun createResolver(baseDir: File = workDir) = CompoundDependenciesResolver(baseDir) From 51a7bef18ed2ed117c36a713893986e365aff2c4 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sat, 4 Oct 2025 17:19:11 +0200 Subject: [PATCH 05/42] whoops forgot escaping the path --- .../kotlin/environment/IrisSimpleExecutionEnvironment.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisSimpleExecutionEnvironment.kt b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisSimpleExecutionEnvironment.kt index 21d65c5bc..4b984b9c0 100644 --- a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisSimpleExecutionEnvironment.kt +++ b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisSimpleExecutionEnvironment.kt @@ -122,7 +122,7 @@ open class IrisSimpleExecutionEnvironment( } append(current.segment) append(File.separatorChar) - } + }.escapedPath val result = mutableSetOf() val queue = ArrayDeque>>() @@ -132,7 +132,7 @@ open class IrisSimpleExecutionEnvironment( val path = pair.first?.let { p -> p + File.separatorChar } ?: "" pair.second.forEach { child -> val path = path + child.segment - if (child.children.isEmpty()) result.add(path) + if (child.children.isEmpty()) result.add(path.escapedPath) else queue.add(path to child.children) } } @@ -157,8 +157,8 @@ open class IrisSimpleExecutionEnvironment( return mod.joinToString("\n") } - private val File.escapedPath - get() = absolutePath.replace("\\", "\\\\").replace("\"", "\\\"") + private val String.escapedPath + get() = replace("\\", "\\\\").replace("\"", "\\\"") private const val ARTIFACT_ID = $$"local:${it.substringBeforeLast(\".jar\")}:1.0.0" private val BASE_GRADLE = """ From c5738433141196d4764e7a0d81082ca6d2b404db Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sun, 5 Oct 2025 00:17:57 +0200 Subject: [PATCH 06/42] fix kts dependency resolver --- .../com/volmit/iris/core/scripting/kotlin/runner/Utils.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/Utils.kt b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/Utils.kt index c4f1f6e12..0252ce39e 100644 --- a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/Utils.kt +++ b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/runner/Utils.kt @@ -167,7 +167,7 @@ internal fun ResultWithDiagnostics.valueOrThrow(message: CharSequence): R } internal val ScriptCompilationConfigurationKeys.dependencyResolver by PropertiesCollection.key(resolver, true) -internal val ScriptCompilationConfigurationKeys.packDirectory by PropertiesCollection.key(workDir, true) +internal val ScriptCompilationConfigurationKeys.packDirectory by PropertiesCollection.key(null, true) internal val ScriptCompilationConfigurationKeys.sharedClassloader by PropertiesCollection.key(loader, true) private fun File.addPack(resolver: CompoundDependenciesResolver) = resolver.addPack(this) From b86d7f303e0a4cd30a1b4448ea2f22591efa690f Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sun, 5 Oct 2025 00:20:55 +0200 Subject: [PATCH 07/42] restructure the shared kts classloader to be more consistent --- .../com/volmit/iris/core/loader/IrisData.java | 9 ++++++--- .../scripting/environment/PackEnvironment.java | 3 +++ .../scripting/environment/SimpleEnvironment.java | 4 ---- .../java/com/volmit/iris/engine/IrisEngine.java | 5 ++--- .../environment/IrisExecutionEnvironment.kt | 11 ++++++++--- .../environment/IrisPackExecutionEnvironment.kt | 14 +++++++++++--- .../IrisSimpleExecutionEnvironment.kt | 14 ++++++-------- .../core/scripting/kotlin/runner/ScriptRunner.kt | 16 +++++----------- 8 files changed, 41 insertions(+), 35 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/core/loader/IrisData.java b/core/src/main/java/com/volmit/iris/core/loader/IrisData.java index aaf1a49de..fc038ecec 100644 --- a/core/src/main/java/com/volmit/iris/core/loader/IrisData.java +++ b/core/src/main/java/com/volmit/iris/core/loader/IrisData.java @@ -58,8 +58,8 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { private static final KMap dataLoaders = new KMap<>(); private final File dataFolder; private final int id; - private final PackEnvironment environment; private boolean closed = false; + private PackEnvironment environment; private ResourceLoader biomeLoader; private ResourceLoader lootLoader; private ResourceLoader regionLoader; @@ -92,7 +92,6 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { this.engine = null; this.dataFolder = dataFolder; this.id = RNG.r.imax(); - this.environment = PackEnvironment.create(this); hotloaded(); } @@ -350,7 +349,6 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { public synchronized void hotloaded() { closed = false; - environment.close(); possibleSnippets = new KMap<>(); builder = new GsonBuilder() .addDeserializationExclusionStrategy(this) @@ -382,6 +380,7 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { this.imageLoader = registerLoader(IrisImage.class); this.scriptLoader = registerLoader(IrisScript.class); this.matterObjectLoader = registerLoader(IrisMatterObject.class); + this.environment = PackEnvironment.create(this); builder.registerTypeAdapterFactory(KeyedType::createTypeAdapter); gson = builder.create(); @@ -389,6 +388,10 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { .map(IrisDimension::getDataScripts) .flatMap(KList::stream) .forEach(environment::execute); + + if (engine != null) { + engine.hotload(); + } } public void dump() { diff --git a/core/src/main/java/com/volmit/iris/core/scripting/environment/PackEnvironment.java b/core/src/main/java/com/volmit/iris/core/scripting/environment/PackEnvironment.java index c5dbccf22..4f0dfd6bb 100644 --- a/core/src/main/java/com/volmit/iris/core/scripting/environment/PackEnvironment.java +++ b/core/src/main/java/com/volmit/iris/core/scripting/environment/PackEnvironment.java @@ -2,6 +2,7 @@ package com.volmit.iris.core.scripting.environment; import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.scripting.kotlin.environment.IrisPackExecutionEnvironment; +import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.util.math.RNG; import lombok.NonNull; import org.jetbrains.annotations.Nullable; @@ -16,4 +17,6 @@ public interface PackEnvironment extends SimpleEnvironment { @Nullable Object createNoise(@NonNull String script, @NonNull RNG rng); + + EngineEnvironment with(@NonNull Engine engine); } \ No newline at end of file diff --git a/core/src/main/java/com/volmit/iris/core/scripting/environment/SimpleEnvironment.java b/core/src/main/java/com/volmit/iris/core/scripting/environment/SimpleEnvironment.java index 4eb7e0ce8..6d543cdca 100644 --- a/core/src/main/java/com/volmit/iris/core/scripting/environment/SimpleEnvironment.java +++ b/core/src/main/java/com/volmit/iris/core/scripting/environment/SimpleEnvironment.java @@ -27,8 +27,4 @@ public interface SimpleEnvironment { @Nullable Object evaluate(@NonNull String script, @NonNull Class type, @Nullable Map<@NonNull String, Object> vars); - - default void close() { - - } } \ No newline at end of file diff --git a/core/src/main/java/com/volmit/iris/engine/IrisEngine.java b/core/src/main/java/com/volmit/iris/engine/IrisEngine.java index b6e0fef08..9f570d642 100644 --- a/core/src/main/java/com/volmit/iris/engine/IrisEngine.java +++ b/core/src/main/java/com/volmit/iris/engine/IrisEngine.java @@ -125,7 +125,7 @@ public class IrisEngine implements Engine { mantle = new IrisEngineMantle(this); context = new IrisContext(this); cleaning = new AtomicBoolean(false); - execution = EngineEnvironment.create(this); + execution = getData().getEnvironment().with(this); if (studio) { getData().dump(); getData().clearLists(); @@ -163,10 +163,9 @@ public class IrisEngine implements Engine { private void prehotload() { worldManager.close(); complex.close(); - execution.close(); effects.close(); mode.close(); - execution = EngineEnvironment.create(this); + execution = getData().getEnvironment().with(this); J.a(() -> new IrisProject(getData().getDataFolder()).updateWorkspace()); } diff --git a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisExecutionEnvironment.kt b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisExecutionEnvironment.kt index 2bebf8c9f..2114f29d4 100644 --- a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisExecutionEnvironment.kt +++ b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisExecutionEnvironment.kt @@ -9,15 +9,20 @@ import com.volmit.iris.core.scripting.kotlin.base.EngineScript import com.volmit.iris.core.scripting.kotlin.base.MobSpawningScript import com.volmit.iris.core.scripting.kotlin.base.PostMobSpawningScript import com.volmit.iris.core.scripting.kotlin.base.PreprocessorScript +import com.volmit.iris.core.scripting.kotlin.environment.IrisSimpleExecutionEnvironment +import com.volmit.iris.core.scripting.kotlin.runner.ScriptRunner import com.volmit.iris.engine.framework.Engine import com.volmit.iris.util.mantle.MantleChunk import org.bukkit.Chunk import org.bukkit.Location import org.bukkit.entity.Entity +import java.io.File -data class IrisExecutionEnvironment( - private val engine: Engine -) : IrisPackExecutionEnvironment(engine.data), EngineEnvironment { +class IrisExecutionEnvironment internal constructor( + private val engine: Engine, + parent: ScriptRunner?, +) : IrisPackExecutionEnvironment(engine.data, parent), EngineEnvironment { + constructor(engine: Engine) : this(engine, null) override fun getEngine() = engine override fun execute(script: String) = diff --git a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisPackExecutionEnvironment.kt b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisPackExecutionEnvironment.kt index 86832c963..412695f46 100644 --- a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisPackExecutionEnvironment.kt +++ b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisPackExecutionEnvironment.kt @@ -1,17 +1,22 @@ package com.volmit.iris.core.scripting.kotlin.environment import com.volmit.iris.core.loader.IrisData +import com.volmit.iris.core.scripting.environment.EngineEnvironment import com.volmit.iris.core.scripting.environment.PackEnvironment import com.volmit.iris.core.scripting.kotlin.base.DataScript import com.volmit.iris.core.scripting.kotlin.base.NoiseScript import com.volmit.iris.core.scripting.kotlin.runner.Script +import com.volmit.iris.core.scripting.kotlin.runner.ScriptRunner import com.volmit.iris.core.scripting.kotlin.runner.valueOrThrow +import com.volmit.iris.engine.framework.Engine import com.volmit.iris.util.math.RNG import kotlin.reflect.KClass -open class IrisPackExecutionEnvironment( - private val data: IrisData -) : IrisSimpleExecutionEnvironment(data.dataFolder), PackEnvironment { +open class IrisPackExecutionEnvironment internal constructor( + private val data: IrisData, + parent: ScriptRunner? +) : IrisSimpleExecutionEnvironment(data.dataFolder, parent), PackEnvironment { + constructor(data: IrisData) : this(data, null) override fun getData() = data @@ -31,6 +36,9 @@ open class IrisPackExecutionEnvironment( override fun createNoise(script: String, rng: RNG) = evaluate(script, NoiseScript::class.java, data.parameters("rng" to rng)) + override fun with(engine: Engine) = + IrisExecutionEnvironment(engine, runner) + private fun IrisData.parameters(vararg values: Pair): Map { return mapOf( "data" to this, diff --git a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisSimpleExecutionEnvironment.kt b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisSimpleExecutionEnvironment.kt index 4b984b9c0..1525dfeef 100644 --- a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisSimpleExecutionEnvironment.kt +++ b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisSimpleExecutionEnvironment.kt @@ -19,11 +19,14 @@ import kotlin.script.experimental.annotations.KotlinScript import kotlin.script.experimental.api.ResultWithDiagnostics import kotlin.text.split -open class IrisSimpleExecutionEnvironment( - baseDir: File = File(".").absoluteFile +open class IrisSimpleExecutionEnvironment internal constructor( + baseDir: File, + parent: ScriptRunner? ) : SimpleEnvironment { + @JvmOverloads + constructor(baseDir: File = File(".").absoluteFile) : this(baseDir, null) protected val compileCache = KCache, ResultWithDiagnostics