diff --git a/src/main/java/cr0s/warpdrive/config/WarpDriveConfig.java b/src/main/java/cr0s/warpdrive/config/WarpDriveConfig.java index 7a710ae30..865c74525 100644 --- a/src/main/java/cr0s/warpdrive/config/WarpDriveConfig.java +++ b/src/main/java/cr0s/warpdrive/config/WarpDriveConfig.java @@ -189,6 +189,7 @@ public class WarpDriveConfig { public static int G_REGISTRY_UPDATE_INTERVAL_TICKS = 20 * WarpDriveConfig.G_REGISTRY_UPDATE_INTERVAL_SECONDS; public static boolean G_ENFORCE_VALID_CELESTIAL_OBJECTS = true; public static int G_BLOCKS_PER_TICK = 3500; + public static int G_CHUNKS_PER_TICK = 4; public static boolean G_ENABLE_FAST_SET_BLOCKSTATE = false; public static boolean G_ENABLE_PROTECTION_CHECKS = true; public static boolean G_ENABLE_EXPERIMENTAL_REFRESH = false; @@ -861,6 +862,9 @@ public static void loadConfig(final File file) { G_BLOCKS_PER_TICK = Commons.clamp(100, 100000, config.get("general", "blocks_per_tick", G_BLOCKS_PER_TICK, "Number of blocks to move per ticks, too high will cause lag spikes on ship jumping or deployment, too low may break the ship wirings").getInt()); + G_CHUNKS_PER_TICK = Commons.clamp(1, 1000, + config.get("general", "chunks_per_tick", G_CHUNKS_PER_TICK, + "Number of target chunks to load or generate per tick before a ship jump, too high will cause lag spikes or watchdog kills when jumping to ungenerated areas, too low will delay the jump").getInt()); G_ENABLE_FAST_SET_BLOCKSTATE = config.get("general", "enable_fast_set_blockstate", G_ENABLE_FAST_SET_BLOCKSTATE, "Enable fast blockstate placement, skipping light computation. Disable if you have world implementations conflicts").getBoolean(G_ENABLE_FAST_SET_BLOCKSTATE); G_ENABLE_PROTECTION_CHECKS = config.get("general", "enable_protection_checks", G_ENABLE_PROTECTION_CHECKS, diff --git a/src/main/java/cr0s/warpdrive/data/EnumJumpSequencerState.java b/src/main/java/cr0s/warpdrive/data/EnumJumpSequencerState.java index 60b82e2eb..8fee109f7 100644 --- a/src/main/java/cr0s/warpdrive/data/EnumJumpSequencerState.java +++ b/src/main/java/cr0s/warpdrive/data/EnumJumpSequencerState.java @@ -12,6 +12,7 @@ public enum EnumJumpSequencerState implements IStringSerializable { CHECK_BORDERS ("check_borders"), SAVE_TO_DISK ("save_to_disk"), GET_INITIAL_VECTOR ("get_initial_vector"), + PRELOAD_TARGET_CHUNKS ("preload_target_chunks"), ADJUST_JUMP_VECTOR ("adjust_jump_vector"), LOAD_TARGET_CHUNKS ("load_target_chunks"), SAVE_ENTITIES ("save_entities"), diff --git a/src/main/java/cr0s/warpdrive/event/JumpSequencer.java b/src/main/java/cr0s/warpdrive/event/JumpSequencer.java index eca36eb95..15bbf0e4d 100644 --- a/src/main/java/cr0s/warpdrive/event/JumpSequencer.java +++ b/src/main/java/cr0s/warpdrive/event/JumpSequencer.java @@ -100,6 +100,9 @@ public class JumpSequencer extends AbstractSequencer { protected final JumpShip ship; private boolean betweenWorlds; private boolean isPluginCheckDone = false; + private boolean isJumpDistanceCheckPending = false; + private ArrayList chunksToPreload; + private int indexPreloadChunk = 0; private WarpDriveText firstAdjustmentReason = null; private long msCounter = 0; @@ -300,6 +303,12 @@ public boolean onUpdate() { case GET_INITIAL_VECTOR: state_getInitialVector(); if (isEnabled) { + enumJumpSequencerState = EnumJumpSequencerState.PRELOAD_TARGET_CHUNKS; + } + break; + + case PRELOAD_TARGET_CHUNKS: + if (state_preloadTargetChunks() && isEnabled) { enumJumpSequencerState = EnumJumpSequencerState.ADJUST_JUMP_VECTOR; } break; @@ -688,7 +697,8 @@ protected void state_getInitialVector() { final int rangeX = Math.abs(moveX) - (ship.maxX - ship.minX); final int rangeZ = Math.abs(moveZ) - (ship.maxZ - ship.minZ); if (Math.max(rangeX, rangeZ) < 256) { - firstAdjustmentReason = getPossibleJumpDistance(); + // delayed until the path is loaded, see state_preloadTargetChunks() + isJumpDistanceCheckPending = true; isPluginCheckDone = true; } break; @@ -706,6 +716,70 @@ protected void state_getInitialVector() { LocalProfiler.stop(); } + @Nonnull + private ArrayList getChunksToPreload() { + // Cover the target position, plus the source position when a vector adjustment is pending: + // the movement is linear, so intermediate positions are within the union of both boxes. + // A diagonal move loads corner chunks the ship won't touch, and a rotating ship may reach + // a few chunks outside the envelope which will lazy load as before: both are perf only. + final BlockPos targetMin = transformation.apply(ship.minX, ship.minY, ship.minZ); + final BlockPos targetMax = transformation.apply(ship.maxX, ship.maxY, ship.maxZ); + int minX = Math.min(targetMin.getX(), targetMax.getX()); + int maxX = Math.max(targetMin.getX(), targetMax.getX()); + int minZ = Math.min(targetMin.getZ(), targetMax.getZ()); + int maxZ = Math.max(targetMin.getZ(), targetMax.getZ()); + if (isJumpDistanceCheckPending && worldTarget == worldSource) { + minX = Math.min(minX, ship.minX); + maxX = Math.max(maxX, ship.maxX); + minZ = Math.min(minZ, ship.minZ); + maxZ = Math.max(maxZ, ship.maxZ); + } + final int chunkMinX = minX >> 4; + final int chunkMaxX = maxX >> 4; + final int chunkMinZ = minZ >> 4; + final int chunkMaxZ = maxZ >> 4; + final ArrayList chunkPositions = new ArrayList<>((chunkMaxX - chunkMinX + 1) * (chunkMaxZ - chunkMinZ + 1)); + for (int xChunk = chunkMinX; xChunk <= chunkMaxX; xChunk++) { + for (int zChunk = chunkMinZ; zChunk <= chunkMaxZ; zChunk++) { + chunkPositions.add(new ChunkPos(xChunk, zChunk)); + } + } + return chunkPositions; + } + + protected boolean state_preloadTargetChunks() { + LocalProfiler.start("Jump.preloadTargetChunks"); + + if (chunksToPreload == null) { + chunksToPreload = getChunksToPreload(); + if (WarpDriveConfig.LOGGING_JUMP) { + WarpDrive.logger.info(String.format("%s Preloading %d chunks at target, %d per tick", + this, chunksToPreload.size(), WarpDriveConfig.G_CHUNKS_PER_TICK)); + } + } + + // getChunk will load or generate the chunk: this is the expensive part, hence the tick budget + final int indexLastChunk = Math.min(chunksToPreload.size(), indexPreloadChunk + WarpDriveConfig.G_CHUNKS_PER_TICK); + for (; indexPreloadChunk < indexLastChunk; indexPreloadChunk++) { + final ChunkPos chunkPos = chunksToPreload.get(indexPreloadChunk); + worldTarget.getChunk(chunkPos.x, chunkPos.z); + } + if (indexPreloadChunk < chunksToPreload.size()) { + LocalProfiler.stop(); + return false; + } + + // run the delayed vector adjustment now that the whole path is loaded + if (isJumpDistanceCheckPending) { + isJumpDistanceCheckPending = false; + firstAdjustmentReason = getPossibleJumpDistance(); + transformation = new Transformation(ship, worldTarget, moveX, moveY, moveZ, rotationSteps); + } + + LocalProfiler.stop(); + return true; + } + protected void state_adjustJumpVector() { LocalProfiler.start("Jump.adjustJumpVector"); if (WarpDriveConfig.LOGGING_JUMP) { @@ -1490,7 +1564,7 @@ private void doCollisionDamage(final boolean atTarget) { } // inform players on board - + final double rx = Math.round(min.x + worldSource.rand.nextInt(Math.max(1, (int) (max.x - min.x)))); final double ry = Math.round(min.y + worldSource.rand.nextInt(Math.max(1, (int) (max.y - min.y)))); final double rz = Math.round(min.z + worldSource.rand.nextInt(Math.max(1, (int) (max.z - min.z))));