Skip to content

Commit

Permalink
Use processing for wrecks (again) (#699)
Browse files Browse the repository at this point in the history
* Use processing for wrecks

* Don't use Tick

* Parallelize on chunks

* Clamp y axis to 0 to avoid batching on sub-chunk

* Fix axis modifier and add annotations

* Add doc comments

* Add more efficient NONE::andThen imp

* Fix bugs

* Update total duration to match previous and use floor

* seconds to tick conversion

* Fold repeated AndThen calls

* load chunks

* Fix 1.18.2 async chunk deadlock

---------

Co-authored-by: ohnoey <[email protected]>
  • Loading branch information
TylerS1066 and oh-noey authored Aug 21, 2024
1 parent b94e352 commit 9ddedc7
Show file tree
Hide file tree
Showing 13 changed files with 344 additions and 94 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import net.countercraft.movecraft.features.contacts.ContactsCommand;
import net.countercraft.movecraft.features.contacts.ContactsManager;
import net.countercraft.movecraft.features.contacts.ContactsSign;
import net.countercraft.movecraft.features.fading.WreckManager;
import net.countercraft.movecraft.features.status.StatusManager;
import net.countercraft.movecraft.features.status.StatusSign;
import net.countercraft.movecraft.listener.*;
Expand Down Expand Up @@ -57,6 +58,7 @@ public class Movecraft extends JavaPlugin {
private WorldHandler worldHandler;
private SmoothTeleport smoothTeleport;
private AsyncManager asyncManager;
private WreckManager wreckManager;

public static synchronized Movecraft getInstance() {
return instance;
Expand Down Expand Up @@ -189,8 +191,10 @@ public void onEnable() {
asyncManager.runTaskTimer(this, 0, 1);
MapUpdateManager.getInstance().runTaskTimer(this, 0, 1);


CraftManager.initialize(datapackInitialized);
Bukkit.getScheduler().runTaskTimer(this, WorldManager.INSTANCE::run, 0,1);
wreckManager = new WreckManager(WorldManager.INSTANCE);

getServer().getPluginManager().registerEvents(new InteractListener(), this);

Expand Down Expand Up @@ -332,4 +336,8 @@ public SmoothTeleport getSmoothTeleport() {
public AsyncManager getAsyncManager() {
return asyncManager;
}

public @NotNull WreckManager getWreckManager(){
return wreckManager;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,26 @@
import net.countercraft.movecraft.MovecraftLocation;
import net.countercraft.movecraft.async.rotation.RotationTask;
import net.countercraft.movecraft.async.translation.TranslationTask;
import net.countercraft.movecraft.config.Settings;
import net.countercraft.movecraft.craft.*;
import net.countercraft.movecraft.craft.Craft;
import net.countercraft.movecraft.craft.CraftManager;
import net.countercraft.movecraft.craft.PilotedCraft;
import net.countercraft.movecraft.craft.PlayerCraft;
import net.countercraft.movecraft.craft.SinkingCraft;
import net.countercraft.movecraft.craft.type.CraftType;
import net.countercraft.movecraft.events.CraftReleaseEvent;
import net.countercraft.movecraft.mapUpdater.MapUpdateManager;
import net.countercraft.movecraft.mapUpdater.update.BlockCreateCommand;
import net.countercraft.movecraft.mapUpdater.update.UpdateCommand;
import net.countercraft.movecraft.util.hitboxes.HitBox;
import net.kyori.adventure.text.Component;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.NotNull;

import java.util.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

Expand All @@ -49,14 +51,8 @@ public class AsyncManager extends BukkitRunnable {
private final Map<AsyncTask, Craft> ownershipMap = new HashMap<>();
private final BlockingQueue<AsyncTask> finishedAlgorithms = new LinkedBlockingQueue<>();
private final Set<Craft> clearanceSet = new HashSet<>();
private final Map<HitBox, Long> wrecks = new HashMap<>();
private final Map<HitBox, World> wreckWorlds = new HashMap<>();
private final Map<HitBox, Map<Location, BlockData>> wreckPhases = new HashMap<>();
private final Map<World, Set<MovecraftLocation>> processedFadeLocs = new HashMap<>();
private final Map<Craft, Integer> cooldownCache = new WeakHashMap<>();

private long lastFadeCheck = 0;

public AsyncManager() {}

public void submitTask(AsyncTask task, Craft c) {
Expand All @@ -71,15 +67,6 @@ public void submitCompletedTask(AsyncTask task) {
finishedAlgorithms.add(task);
}

public void addWreck(Craft craft){
if(craft.getCollapsedHitBox().isEmpty() || Settings.FadeWrecksAfter == 0){
return;
}
wrecks.put(craft.getCollapsedHitBox(), System.currentTimeMillis());
wreckWorlds.put(craft.getCollapsedHitBox(), craft.getWorld());
wreckPhases.put(craft.getCollapsedHitBox(), craft.getPhaseBlocks());
}

private void processAlgorithmQueue() {
int runLength = 10;
int queueLength = finishedAlgorithms.size();
Expand Down Expand Up @@ -325,68 +312,11 @@ private void processSinking() {
}
}

private void processFadingBlocks() {
if (Settings.FadeWrecksAfter == 0)
return;
long ticksElapsed = (System.currentTimeMillis() - lastFadeCheck) / 50;
if (ticksElapsed <= Settings.FadeTickCooldown)
return;

List<HitBox> processed = new ArrayList<>();
for(Map.Entry<HitBox, Long> entry : wrecks.entrySet()){
if (Settings.FadeWrecksAfter * 1000L > System.currentTimeMillis() - entry.getValue())
continue;

final HitBox hitBox = entry.getKey();
final Map<Location, BlockData> phaseBlocks = wreckPhases.get(hitBox);
final World world = wreckWorlds.get(hitBox);
List<UpdateCommand> commands = new ArrayList<>();
int fadedBlocks = 0;
if (!processedFadeLocs.containsKey(world))
processedFadeLocs.put(world, new HashSet<>());

int maxFadeBlocks = (int) (hitBox.size() * (Settings.FadePercentageOfWreckPerCycle / 100.0));
//Iterate hitbox as a set to get more random locations
for (MovecraftLocation location : hitBox.asSet()){
if (processedFadeLocs.get(world).contains(location))
continue;

if (fadedBlocks >= maxFadeBlocks)
break;

final Location bLoc = location.toBukkit(world);
if ((Settings.FadeWrecksAfter
+ Settings.ExtraFadeTimePerBlock.getOrDefault(bLoc.getBlock().getType(), 0))
* 1000L > System.currentTimeMillis() - entry.getValue())
continue;

fadedBlocks++;
processedFadeLocs.get(world).add(location);
BlockData phaseBlock = phaseBlocks.getOrDefault(bLoc, Material.AIR.createBlockData());
commands.add(new BlockCreateCommand(world, location, phaseBlock));
}
MapUpdateManager.getInstance().scheduleUpdates(commands);
if (!processedFadeLocs.get(world).containsAll(hitBox.asSet()))
continue;

processed.add(hitBox);
processedFadeLocs.get(world).removeAll(hitBox.asSet());
}
for(HitBox hitBox : processed) {
wrecks.remove(hitBox);
wreckPhases.remove(hitBox);
wreckWorlds.remove(hitBox);
}

lastFadeCheck = System.currentTimeMillis();
}

public void run() {
clearAll();

processCruise();
processSinking();
processFadingBlocks();
processAlgorithmQueue();

// now cleanup craft that are bugged and have not moved in the past 60 seconds,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ public void release(@NotNull Craft craft, @NotNull CraftReleaseEvent.Reason reas
craft.getHitBox().getMinZ())
);
}
Movecraft.getInstance().getAsyncManager().addWreck(craft);
Movecraft.getInstance().getWreckManager().queueWreck(craft);
}

//region Craft management
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package net.countercraft.movecraft.features.fading;

import net.countercraft.movecraft.MovecraftLocation;
import net.countercraft.movecraft.processing.MovecraftWorld;
import net.countercraft.movecraft.processing.WorldManager;
import net.countercraft.movecraft.processing.effects.Effect;
import net.countercraft.movecraft.processing.effects.SetBlockEffect;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.block.data.BlockData;
import org.jetbrains.annotations.NotNull;

import java.util.Objects;
import java.util.function.Supplier;

/**
* Fades a block if the data for the intended block has not been mutated since creation.
*/
public class FadeTask implements Supplier<Effect> {
private final @NotNull BlockData compareData;
private final @NotNull BlockData setData;
private final @NotNull MovecraftWorld world;
private final @NotNull MovecraftLocation location;

public FadeTask(@NotNull BlockData compareData, @NotNull BlockData setData, @NotNull MovecraftWorld world, @NotNull MovecraftLocation location){
this.compareData = compareData;
this.setData = setData;
this.world = world;
this.location = location;
}

@Override
public Effect get() {
var testData = world.getData(location);

return () -> Objects.requireNonNull(Bukkit.getWorld(world.getWorldUUID()))
.getChunkAtAsync(location.toBukkit(null))
.thenRunAsync(() -> WorldManager.INSTANCE.submit(() -> testData.equals(compareData)
? new SetBlockEffect(world, location, setData)
: null));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package net.countercraft.movecraft.features.fading;

import net.countercraft.movecraft.config.Settings;
import net.countercraft.movecraft.craft.Craft;
import net.countercraft.movecraft.processing.WorldManager;
import net.countercraft.movecraft.util.MathUtils;
import org.jetbrains.annotations.NotNull;

import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
* Singleton for handling wreck disposal
*/
public class WreckManager {
private final @NotNull WorldManager worldManager;

public WreckManager(@NotNull WorldManager worldManager){
this.worldManager = Objects.requireNonNull(worldManager);
}

/**
* Queue a wreck to be considered terminally destroyed, and hence appropriate for systems such as fading.
*
* @param craft the craft to handle as a wreck
*/
public void queueWreck(@NotNull Craft craft){
if(craft.getCollapsedHitBox().isEmpty() || Settings.FadeWrecksAfter == 0){
return;
}

worldManager.submit(new WreckTask(
craft.getCollapsedHitBox(),
craft.getMovecraftWorld(),
craft
.getPhaseBlocks()
.entrySet()
.stream()
.collect(Collectors.toMap(
entry -> MathUtils.bukkit2MovecraftLoc(entry.getKey()),
Map.Entry::getValue
))));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package net.countercraft.movecraft.features.fading;

import net.countercraft.movecraft.MovecraftLocation;
import net.countercraft.movecraft.config.Settings;
import net.countercraft.movecraft.processing.MovecraftWorld;
import net.countercraft.movecraft.processing.WorldManager;
import net.countercraft.movecraft.processing.effects.DeferredEffect;
import net.countercraft.movecraft.processing.effects.Effect;
import net.countercraft.movecraft.util.CollectorUtils;
import net.countercraft.movecraft.util.hitboxes.HitBox;
import org.bukkit.Material;
import org.bukkit.block.data.BlockData;
import org.jetbrains.annotations.NotNull;

import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ForkJoinTask;
import java.util.function.Supplier;
import java.util.stream.Collectors;

public class WreckTask implements Supplier<Effect> {

private final @NotNull HitBox hitBox;
private final @NotNull Map<MovecraftLocation, BlockData> phaseBlocks;
private final @NotNull MovecraftWorld world;
private final int fadeDelayTicks;
private final int maximumFadeDurationTicks;

public WreckTask(@NotNull HitBox wreck, @NotNull MovecraftWorld world, @NotNull Map<MovecraftLocation, BlockData> phaseBlocks){
this.hitBox = Objects.requireNonNull(wreck);
this.phaseBlocks = Objects.requireNonNull(phaseBlocks);
this.world = Objects.requireNonNull(world);
this.fadeDelayTicks = Settings.FadeWrecksAfter * 20;
this.maximumFadeDurationTicks = (int) (Settings.FadeTickCooldown * (100.0 / Settings.FadePercentageOfWreckPerCycle));
}

@Override
public Effect get() {
var updates = hitBox
.asSet()
.stream()
.collect(Collectors.groupingBy(location -> location.scalarDivide(16).hadamardProduct(1,0,1), CollectorUtils.toHitBox()))
.values()
.stream()
.map(slice -> ForkJoinTask.adapt(() -> partialUpdate(slice)))
.toList();

return ForkJoinTask
.invokeAll(updates)
.stream()
.map(ForkJoinTask::join)
.reduce(Effect.NONE, Effect::andThen);
}

private @NotNull Effect partialUpdate(@NotNull HitBox slice){
Effect accumulator = Effect.NONE;
for (MovecraftLocation location : slice){
// Get the existing data
final BlockData data = world.getData(location);
// Determine the replacement data
BlockData replacementData = phaseBlocks.getOrDefault(location, Material.AIR.createBlockData());
// Calculate ticks until replacement
long fadeTicks = this.fadeDelayTicks;
fadeTicks += (int) (Math.random() * maximumFadeDurationTicks);
fadeTicks += 20L * Settings.ExtraFadeTimePerBlock.getOrDefault(data.getMaterial(), 0);
// Deffer replacement until time delay elapses
accumulator = accumulator.andThen(new DeferredEffect(fadeTicks, () -> WorldManager.INSTANCE.submit(new FadeTask(data, replacementData, world, location))));
}

// TODO: Determine if we need to reduce the spread of deferred effects due to runnable overhead
return accumulator;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package net.countercraft.movecraft.processing.effects;

import net.countercraft.movecraft.Movecraft;
import net.countercraft.movecraft.processing.WorldManager;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.NotNull;

import java.util.Objects;

/**
* A wrapper effect that allows delaying the execution of a provided effect by a number of ticks.
*/
public class DeferredEffect implements Effect {
private final long delayTicks;
private final @NotNull Effect effect;

public DeferredEffect(long delayTicks, @NotNull Effect effect){
this.delayTicks = delayTicks;
this.effect = Objects.requireNonNull(effect);
}

@Override
public void run() {
new BukkitRunnable(){
@Override
public void run() {
WorldManager.INSTANCE.submit(() -> effect);
}
}.runTaskLaterAsynchronously(Movecraft.getInstance(), delayTicks);
}

@Override
public boolean isAsync() {
return true;
}
}
Loading

0 comments on commit 9ddedc7

Please sign in to comment.