Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use processing for wrecks #686

Merged
merged 12 commits into from
Aug 11, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,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 All @@ -38,6 +39,7 @@
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.io.FileOutputStream;
Expand All @@ -55,6 +57,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 @@ -187,8 +190,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 @@ -330,4 +335,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