Improve spell lookup performance by using the Ether

This commit is contained in:
Sollace 2024-01-20 15:12:23 +00:00
parent d22dd3bdf9
commit ab3ac8cb0d
No known key found for this signature in database
GPG key ID: E52FACE7B5C773DB
12 changed files with 263 additions and 198 deletions

View file

@ -1,22 +1,17 @@
package com.minelittlepony.unicopia;
import java.util.Map;
import java.util.Optional;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.ability.magic.CasterView;
import com.minelittlepony.unicopia.entity.player.dummy.DummyPlayerEntity;
import com.minelittlepony.unicopia.server.world.Ether;
import com.mojang.authlib.GameProfile;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Identifier;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
public class InteractionManager {
@ -37,13 +32,6 @@ public class InteractionManager {
return INSTANCE;
}
public Optional<CasterView> getCasterView(BlockView view) {
if (view instanceof ServerWorld world) {
return Optional.of(Ether.get(world));
}
return Optional.empty();
}
public Map<Identifier, ?> readChapters(PacketByteBuf buf) {
throw new RuntimeException("Method not supported");
}

View file

@ -11,6 +11,7 @@ import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.entity.*;
import com.minelittlepony.unicopia.entity.damage.UDamageSources;
import com.minelittlepony.unicopia.particle.ParticleSource;
import com.minelittlepony.unicopia.server.world.Ether;
import com.minelittlepony.unicopia.server.world.ModificationType;
import com.minelittlepony.unicopia.util.SoundEmitter;
import com.minelittlepony.unicopia.util.VecHelper;
@ -99,11 +100,7 @@ public interface Caster<E extends Entity> extends
}
default boolean canCastAt(Vec3d pos) {
return findAllSpellsInRange(500, SpellType.ARCANE_PROTECTION::isOn).noneMatch(caster -> caster
.getSpellSlot().get(SpellType.ARCANE_PROTECTION, false)
.filter(spell -> spell.blocksMagicFor(caster, this, pos))
.isPresent()
);
return !Ether.get(asWorld()).anyMatch(SpellType.ARCANE_PROTECTION, (spell, caster) -> spell.blocksMagicFor(caster, this, pos));
}
static Stream<Caster<?>> stream(Stream<Entity> entities) {

View file

@ -1,44 +0,0 @@
package com.minelittlepony.unicopia.ability.magic;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.EquinePredicates;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.util.VecHelper;
import net.minecraft.entity.Entity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.*;
public interface CasterView {
EntityView getWorld();
default <S extends Spell> Stream<Map.Entry<Caster<?>, S>> findAllSpellsInRange(BlockPos pos, double radius, SpellPredicate<S> type) {
return findAllCastersInRange(pos, radius).flatMap(caster -> {
return caster.getSpellSlot().stream(type, false).map(spell -> {
return Map.entry(caster, spell);
});
});
}
default Stream<Caster<?>> findAllCastersInRange(BlockPos pos, double radius) {
return findAllCastersInRange(pos, radius, null);
}
default Stream<Caster<?>> findAllCastersInRange(BlockPos pos, double radius, @Nullable Predicate<Entity> test) {
return Caster.stream(findAllEntitiesInRange(pos, radius, test == null ? EquinePredicates.IS_CASTER : EquinePredicates.IS_CASTER.and(test)));
}
default Stream<Entity> findAllEntitiesInRange(BlockPos pos, double radius, @Nullable Predicate<Entity> test) {
return VecHelper.findInRange(null, getWorld(), Vec3d.ofCenter(pos), radius, test).stream();
}
default Stream<Entity> findAllEntitiesInRange(BlockPos pos, double radius) {
return findAllEntitiesInRange(pos, radius, null);
}
}

View file

@ -45,6 +45,12 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
*/
private final ParticleHandle particlEffect = new ParticleHandle();
/**
* ID of the placed counterpart of this spell.
*/
@Nullable
private UUID placedSpellId;
/**
* The cast spell entity
*/
@ -82,10 +88,8 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
@Override
public boolean tick(Caster<?> source, Situation situation) {
if (situation == Situation.BODY) {
if (!source.isClient()) {
if (dimension == null) {
dimension = source.asWorld().getRegistryKey();
if (source instanceof Pony) {
@ -105,8 +109,7 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
if (situation == Situation.GROUND_ENTITY) {
if (!source.isClient()) {
Ether ether = Ether.get(source.asWorld());
if (ether.getEntry(getType(), source).isEmpty()) {
if (Ether.get(source.asWorld()).get(this, source) == null) {
setDead();
return false;
}
@ -127,7 +130,7 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
}
private void checkDetachment(Caster<?> source, EntityValues<?> target) {
if (getWorld(source).map(Ether::get).flatMap(ether -> ether.getEntry(getType(), target.uuid())).isEmpty()) {
if (getWorld(source).map(Ether::get).map(ether -> ether.get(getType(), target, placedSpellId)).isEmpty()) {
setDead();
}
}
@ -143,7 +146,8 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
entity.getSpellSlot().put(copy);
entity.setCaster(source);
entity.getWorld().spawnEntity(entity);
Ether.get(entity.getWorld()).put(getType(), entity);
placedSpellId = copy.getUuid();
Ether.get(entity.getWorld()).getOrCreate(copy, entity);
castEntity.set(entity);
setDirty();
@ -174,8 +178,7 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
if (!source.isClient()) {
castEntity.getTarget().ifPresent(target -> {
getWorld(source).map(Ether::get)
.flatMap(ether -> ether.getEntry(getType(), target.uuid()))
.ifPresent(Ether.Entry::markDead);
.ifPresent(ether -> ether.remove(getType(), target.uuid()));
});
castEntity.set(null);
getSpellEntity(source).ifPresent(e -> {
@ -183,7 +186,7 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
});
if (source.asEntity() instanceof CastSpellEntity spellcast) {
Ether.get(source.asWorld()).remove(getType(), source);
Ether.get(source.asWorld()).remove(this, source);
}
}
super.onDestroyed(source);

View file

@ -9,6 +9,7 @@ import com.minelittlepony.unicopia.entity.mob.UEntities;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.item.FriendshipBraceletItem;
import com.minelittlepony.unicopia.particle.MagicParticleEffect;
import com.minelittlepony.unicopia.server.world.Ether;
import com.minelittlepony.unicopia.util.shape.Sphere;
import net.minecraft.entity.Entity;
@ -42,6 +43,8 @@ public class AreaProtectionSpell extends AbstractAreaEffectSpell {
source.addParticle(new MagicParticleEffect(getType().getColor()), pos, Vec3d.ZERO);
}
});
} else {
Ether.get(source.asWorld()).getOrCreate(this, source);
}
source.findAllSpellsInRange(radius, e -> isValidTarget(source, e)).filter(caster -> !caster.hasCommonOwner(source)).forEach(caster -> {
@ -51,6 +54,11 @@ public class AreaProtectionSpell extends AbstractAreaEffectSpell {
return !isDead();
}
@Override
protected void onDestroyed(Caster<?> caster) {
Ether.get(caster.asWorld()).remove(this, caster);
}
/**
* Calculates the maximum radius of the shield. aka The area of effect.
*/

View file

@ -12,7 +12,7 @@ import com.minelittlepony.unicopia.util.shape.Sphere;
import net.minecraft.util.math.Vec3d;
/**
* An area-effect spell that disperses illussions.
* An area-effect spell that disperses illusions.
*/
public class DisperseIllusionSpell extends AbstractAreaEffectSpell {
protected DisperseIllusionSpell(CustomisedSpellType<?> type) {

View file

@ -5,14 +5,16 @@ import java.util.Set;
import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.CasterView;
import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod;
import com.minelittlepony.unicopia.ability.magic.spell.Situation;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.advancement.UCriteria;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.particle.UParticles;
import com.minelittlepony.unicopia.projectile.MagicProjectileEntity;
import com.minelittlepony.unicopia.server.world.Ether;
import com.minelittlepony.unicopia.util.NbtSerialisable;
import com.minelittlepony.unicopia.util.shape.*;
@ -21,8 +23,10 @@ import net.minecraft.fluid.*;
import net.minecraft.nbt.*;
import net.minecraft.state.property.Properties;
import net.minecraft.registry.tag.TagKey;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
public class HydrophobicSpell extends AbstractSpell {
@ -41,16 +45,15 @@ public class HydrophobicSpell extends AbstractSpell {
}
@Override
public boolean apply(Caster<?> source) {
if (getTraits().get(Trait.GENEROSITY) > 0) {
return toPlaceable().apply(source);
public Spell prepareForCast(Caster<?> caster, CastingMethod method) {
if ((method == CastingMethod.DIRECT || method == CastingMethod.STAFF) && getTraits().get(Trait.GENEROSITY) > 0) {
return toPlaceable();
}
return super.apply(source);
return this;
}
@Override
public boolean tick(Caster<?> source, Situation situation) {
if (!source.isClient()) {
World world = source.asWorld();
@ -86,7 +89,11 @@ public class HydrophobicSpell extends AbstractSpell {
setDead();
}
source.spawnParticles(new Sphere(true, getRange(source)), 10, pos -> {
double range = getRange(source);
var entry = Ether.get(source.asWorld()).getOrCreate(this, source);
entry.radius = (float)range;
source.spawnParticles(new Sphere(true, range), 10, pos -> {
BlockPos bp = BlockPos.ofFloored(pos);
if (source.asWorld().getFluidState(bp.up()).isIn(affectedFluid)) {
source.addParticle(UParticles.RAIN_DROPS, pos, Vec3d.ZERO);
@ -107,6 +114,7 @@ public class HydrophobicSpell extends AbstractSpell {
@Override
protected void onDestroyed(Caster<?> caster) {
Ether.get(caster.asWorld()).remove(this, caster);
storedFluidPositions.removeIf(entry -> {
entry.restore(caster.asWorld());
return true;
@ -162,13 +170,20 @@ public class HydrophobicSpell extends AbstractSpell {
}
}
public boolean blocksFlow(Caster<?> caster, BlockPos pos, FluidState fluid) {
return fluid.isIn(affectedFluid) && pos.isWithinDistance(caster.getOrigin(), getRange(caster) + 1);
public boolean blocksFlow(Ether.Entry<?> entry, Vec3d center, BlockPos pos, FluidState fluid) {
return fluid.isIn(affectedFluid) && pos.isWithinDistance(center, (double)entry.radius + 1);
}
public static boolean blocksFluidFlow(CasterView world, BlockPos pos, FluidState state) {
return world.findAllSpellsInRange(pos, 500, SpellType.HYDROPHOBIC).anyMatch(pair -> {
return pair.getValue().blocksFlow(pair.getKey(), pos, state);
});
public static boolean blocksFluidFlow(BlockView world, BlockPos pos, FluidState state) {
if (world instanceof ServerWorld sw) {
return Ether.get(sw).anyMatch(SpellType.HYDROPHOBIC, entry -> {
var spell = entry.getSpell();
var target = entry.entity.getTarget().orElse(null);
return spell != null && target != null && spell.blocksFlow(entry, target.pos(), pos, state);
});
}
return false;
}
}

View file

@ -1,6 +1,9 @@
package com.minelittlepony.unicopia.ability.magic.spell.effect;
import java.util.Optional;
import java.util.UUID;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.Unicopia;
@ -34,6 +37,8 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
.build();
private static final Shape PARTICLE_AREA = new Sphere(true, 2, 1, 1, 0);
@Nullable
private UUID targetPortalId;
private final EntityReference<Entity> teleportationTarget = new EntityReference<>();
private boolean publishedPosition;
@ -63,10 +68,7 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
Vec3d origin = source.getOriginVector();
ParticleEffect effect = teleportationTarget.getTarget()
.map(target -> {
getType();
return (ParticleEffect)new FollowingParticleEffect(UParticles.HEALTH_DRAIN, target.pos(), 0.2F).withChild(ParticleTypes.ELECTRIC_SPARK);
})
.map(target -> (ParticleEffect)new FollowingParticleEffect(UParticles.HEALTH_DRAIN, target.pos(), 0.2F).withChild(ParticleTypes.ELECTRIC_SPARK))
.orElse(ParticleTypes.ELECTRIC_SPARK);
source.spawnParticles(origin, particleArea, 5, pos -> {
@ -82,7 +84,7 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
});
} else {
teleportationTarget.getTarget().ifPresent(target -> {
if (Ether.get(source.asWorld()).getEntry(getType(), target.uuid()).isEmpty()) {
if (Ether.get(source.asWorld()).get(getType(), target, targetPortalId) != null) {
Unicopia.LOGGER.debug("Lost sibling, breaking connection to " + target.uuid());
teleportationTarget.set(null);
setDirty();
@ -96,18 +98,15 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
);
}
if (!publishedPosition) {
publishedPosition = true;
Ether.Entry entry = Ether.get(source.asWorld()).put(getType(), source);
entry.pitch = pitch;
entry.yaw = yaw;
}
var entry = Ether.get(source.asWorld()).getOrCreate(this, source);
entry.pitch = pitch;
entry.yaw = yaw;
}
return !isDead();
}
private void tickWithTargetLink(Caster<?> source, Ether.Entry destination) {
private void tickWithTargetLink(Caster<?> source, Ether.Entry<?> destination) {
destination.entity.getTarget().ifPresent(target -> {
source.findAllEntitiesInRange(1).forEach(entity -> {
@ -142,20 +141,21 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
return;
}
Ether ether = Ether.get(source.asWorld());
ether.getEntries(getType())
.stream()
.filter(entry -> entry.isAvailable() && !entry.entity.referenceEquals(source.asEntity()) && entry.entity.isSet())
.findAny()
.ifPresent(entry -> {
Ether.get(source.asWorld()).anyMatch(getType(), entry -> {
if (entry.isAvailable() && !entry.entity.referenceEquals(source.asEntity()) && entry.entity.isSet()) {
entry.setTaken(true);
teleportationTarget.copyFrom(entry.entity);
targetPortalId = entry.getSpellId();
setDirty();
});
}
return false;
});
}
private Optional<Ether.Entry> getTarget(Caster<?> source) {
return teleportationTarget.getTarget().flatMap(target -> Ether.get(source.asWorld()).getEntry(getType(), target.uuid()));
@SuppressWarnings("unchecked")
private Optional<Ether.Entry<PortalSpell>> getTarget(Caster<?> source) {
return teleportationTarget.getTarget()
.map(target -> Ether.get(source.asWorld()).get((SpellType<PortalSpell>)getType(), target, targetPortalId));
}
@Override
@ -189,7 +189,7 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
protected void onDestroyed(Caster<?> caster) {
particleEffect.destroy();
Ether ether = Ether.get(caster.asWorld());
ether.remove(getType(), caster.asEntity().getUuid());
ether.remove(getType(), caster);
getTarget(caster).ifPresent(e -> e.setTaken(false));
}

View file

@ -2,7 +2,6 @@ package com.minelittlepony.unicopia.client;
import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.function.Supplier;
@ -13,14 +12,12 @@ import com.minelittlepony.unicopia.EquinePredicates;
import com.minelittlepony.unicopia.FlightType;
import com.minelittlepony.unicopia.InteractionManager;
import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.ability.magic.CasterView;
import com.minelittlepony.unicopia.client.gui.DismissSpellScreen;
import com.minelittlepony.unicopia.client.gui.spellbook.ClientChapters;
import com.minelittlepony.unicopia.client.sound.*;
import com.minelittlepony.unicopia.entity.player.PlayerPhysics;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.entity.player.dummy.DummyClientPlayerEntity;
import com.minelittlepony.unicopia.server.world.Ether;
import com.mojang.authlib.GameProfile;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
@ -37,29 +34,17 @@ import net.minecraft.entity.passive.BeeEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.vehicle.AbstractMinecartEntity;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundCategory;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
public class ClientInteractionManager extends InteractionManager {
private final MinecraftClient client = MinecraftClient.getInstance();
private final Optional<CasterView> clientWorld = Optional.of(() -> MinecraftClient.getInstance().world);
private final Int2ObjectMap<WeakReference<TickableSoundInstance>> playingSounds = new Int2ObjectOpenHashMap<>();
@Override
public Optional<CasterView> getCasterView(BlockView view) {
if (view instanceof ServerWorld world) {
return Optional.of(Ether.get(world));
}
return clientWorld;
}
@Override
public Map<Identifier, ?> readChapters(PacketByteBuf buffer) {
return buffer.readMap(PacketByteBuf::readIdentifier, ClientChapters::loadChapter);

View file

@ -5,7 +5,6 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import com.minelittlepony.unicopia.InteractionManager;
import com.minelittlepony.unicopia.ability.magic.spell.effect.HydrophobicSpell;
import net.minecraft.block.BlockState;
@ -17,7 +16,7 @@ import net.minecraft.world.BlockView;
abstract class MixinFlowableFluid {
@Inject(method = "canFill", at = @At("HEAD"), cancellable = true)
private void onCanFill(BlockView world, BlockPos pos, BlockState state, Fluid fluid, CallbackInfoReturnable<Boolean> info) {
if (InteractionManager.instance().getCasterView(world).filter(view -> HydrophobicSpell.blocksFluidFlow(view, pos, fluid.getDefaultState())).isPresent()) {
if (HydrophobicSpell.blocksFluidFlow(world, pos, fluid.getDefaultState())) {
info.setReturnValue(false);
}
}

View file

@ -1,88 +1,93 @@
package com.minelittlepony.unicopia.server.world;
import java.lang.ref.WeakReference;
import java.util.*;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.ability.magic.CasterView;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.entity.EntityReference;
import com.minelittlepony.unicopia.util.NbtSerialisable;
import net.minecraft.nbt.*;
import net.minecraft.util.Identifier;
import net.minecraft.world.PersistentState;
import net.minecraft.world.World;
public class Ether extends PersistentState implements CasterView {
public class Ether extends PersistentState {
private static final Identifier ID = Unicopia.id("ether");
public static Ether get(World world) {
return WorldOverlay.getPersistableStorage(world, ID, Ether::new, Ether::new);
}
private final Map<Identifier, Set<Entry>> advertisingEndpoints = new HashMap<>();
private final Map<Identifier, Map<UUID, Map<UUID, Entry<?>>>> endpoints;
private final Object locker = new Object();
private final World world;
Ether(World world, NbtCompound compound) {
this(world);
compound.getKeys().forEach(key -> {
Identifier typeId = Identifier.tryParse(key);
if (typeId != null) {
Set<Entry> uuids = getEntries(typeId);
compound.getList(key, NbtElement.COMPOUND_TYPE).forEach(entry -> {
Entry e = new Entry();
e.fromNBT((NbtCompound)entry);
uuids.add(e);
});
}
this.world = world;
this.endpoints = NbtSerialisable.readMap(compound.getCompound("endpoints"), Identifier::tryParse, typeNbt -> {
return NbtSerialisable.readMap((NbtCompound)typeNbt, UUID::fromString, entityNbt -> {
return NbtSerialisable.readMap((NbtCompound)entityNbt, UUID::fromString, Entry::new);
});
});
}
Ether(World world) {
this.world = world;
this.endpoints = new HashMap<>();
}
@Override
public NbtCompound writeNbt(NbtCompound compound) {
synchronized (locker) {
advertisingEndpoints.forEach((id, uuids) -> {
NbtList list = new NbtList();
uuids.forEach(uuid -> {
if (uuid.isAlive()) {
list.add(uuid.toNBT());
}
pruneNodes();
compound.put("endpoints", NbtSerialisable.writeMap(endpoints, Identifier::toString, entities -> {
return NbtSerialisable.writeMap(entities, UUID::toString, spells -> {
return NbtSerialisable.writeMap(spells, UUID::toString, Entry::toNBT);
});
compound.put(id.toString(), list);
});
}));
return compound;
}
}
public Entry put(SpellType<?> spellType, Caster<?> caster) {
@SuppressWarnings("unchecked")
public <T extends Spell> Entry<T> getOrCreate(T spell, Caster<?> caster) {
synchronized (locker) {
var entry = new Entry(caster);
getEntries(spellType.getId()).add(entry);
markDirty();
Entry<T> entry = (Entry<T>)endpoints
.computeIfAbsent(spell.getType().getId(), typeId -> new HashMap<>())
.computeIfAbsent(caster.asEntity().getUuid(), entityId -> new HashMap<>())
.computeIfAbsent(spell.getUuid(), spellid -> {
markDirty();
return new Entry<>(spell, caster);
});
if (entry.removed) {
entry.removed = false;
markDirty();
}
if (entry.spell.get() != spell) {
entry.spell = new WeakReference<>(spell);
markDirty();
}
return entry;
}
}
public void remove(SpellType<?> spellType, UUID id) {
public <T extends Spell> void remove(SpellType<T> spellType, UUID entityId) {
synchronized (locker) {
Identifier typeId = spellType.getId();
Set<Entry> refs = advertisingEndpoints.get(typeId);
if (refs != null) {
refs.removeIf(ref -> ref.isDead() || ref.entity.getTarget().filter(target -> id.equals(target.uuid())).isPresent());
if (refs.isEmpty()) {
advertisingEndpoints.remove(typeId);
endpoints.computeIfPresent(spellType.getId(), (typeId, entries) -> {
if (entries.remove(entityId) != null) {
markDirty();
}
markDirty();
}
return entries.isEmpty() ? null : entries;
});
}
}
@ -90,72 +95,123 @@ public class Ether extends PersistentState implements CasterView {
remove(spellType, caster.asEntity().getUuid());
}
public Set<Entry> getEntries(SpellType<?> spellType) {
return getEntries(spellType.getId());
public <T extends Spell> void remove(T spell, Caster<?> caster) {
Entry<T> entry = get(spell, caster);
if (entry != null) {
entry.markDead();
}
}
private Set<Entry> getEntries(Identifier typeId) {
@SuppressWarnings("unchecked")
public <T extends Spell> Entry<T> get(T spell, Caster<?> caster) {
return get((SpellType<T>)spell.getType(), caster.asEntity().getUuid(), spell.getUuid());
}
public <T extends Spell> Entry<T> get(SpellType<T> spell, EntityReference.EntityValues<?> entityId, @Nullable UUID spellId) {
return get(spell, entityId.uuid(), spellId);
}
@SuppressWarnings("unchecked")
@Nullable
private <T extends Spell> Entry<T> get(SpellType<T> spell, UUID entityId, @Nullable UUID spellId) {
if (spellId == null) {
return null;
}
synchronized (locker) {
return advertisingEndpoints.compute(typeId, (k, old) -> {
if (old == null) {
old = new HashSet<>();
} else {
old.removeIf(Entry::isDead);
Entry<?> entry = endpoints
.getOrDefault(spell.getId(), Map.of())
.getOrDefault(entityId, Map.of())
.get(spellId);
return entry == null || entry.isDead() ? null : (Entry<T>)entry;
}
}
public <T extends Spell> boolean anyMatch(SpellType<T> spellType, BiPredicate<T, Caster<?>> condition) {
return anyMatch(spellType, entry -> {
var spell = entry.getSpell();
var caster = entry.getCaster();
return spell != null && caster != null && condition.test(spell, caster);
});
}
@SuppressWarnings("unchecked")
public <T extends Spell> boolean anyMatch(SpellType<T> spellType, Predicate<Entry<T>> condition) {
synchronized (locker) {
for (var entries : endpoints.getOrDefault(spellType.getId(), Map.of()).values()) {
for (var entry : entries.values()) {
if (!entry.isDead() && condition.test((Entry<T>)entry)) {
return true;
}
}
return old;
}
}
return false;
}
private void pruneNodes() {
this.endpoints.values().removeIf(entities -> {
entities.values().removeIf(spells -> {
spells.values().removeIf(Entry::isDead);
return spells.isEmpty();
});
}
return entities.isEmpty();
});
}
public Optional<Entry> getEntry(SpellType<?> spellType, Caster<?> caster) {
synchronized (locker) {
return getEntries(spellType).stream().filter(e -> e.entity.referenceEquals(caster.asEntity())).findFirst();
}
}
public Optional<Entry> getEntry(SpellType<?> spellType, UUID uuid) {
synchronized (locker) {
return getEntries(spellType).stream().filter(e -> e.equals(uuid)).findFirst();
}
}
@Override
public World getWorld() {
return world;
}
public class Entry implements NbtSerialisable {
public class Entry<T extends Spell> implements NbtSerialisable {
public final EntityReference<?> entity;
@Nullable
private UUID spellId;
private WeakReference<T> spell;
private boolean removed;
private boolean taken;
public float pitch;
public float yaw;
public float radius;
public Entry() {
entity = new EntityReference<>();
private Entry(NbtElement nbt) {
this.entity = new EntityReference<>();
this.spell = new WeakReference<>(null);
this.fromNBT((NbtCompound)nbt);
}
public Entry(Caster<?> caster) {
entity = new EntityReference<>(caster.asEntity());
public Entry(T spell, Caster<?> caster) {
this.entity = new EntityReference<>(caster.asEntity());
this.spell = new WeakReference<>(spell);
spellId = spell.getUuid();
}
boolean isAlive() {
return !removed;
return !isDead();
}
boolean isDead() {
if (!removed) {
getSpell();
}
return removed;
}
@Nullable
public UUID getSpellId() {
return spellId;
}
public void markDead() {
Unicopia.LOGGER.debug("Marking " + entity.getTarget().orElse(null) + " as dead");
removed = true;
markDirty();
}
public boolean entityMatches(UUID uuid) {
return entity.getTarget().filter(target -> uuid.equals(target.uuid())).isPresent();
}
public boolean isAvailable() {
return !removed && !taken;
return !isDead() && !taken;
}
public void setTaken(boolean taken) {
@ -163,6 +219,43 @@ public class Ether extends PersistentState implements CasterView {
markDirty();
}
@Nullable
public T getSpell() {
if (removed) {
return null;
}
T spell = this.spell.get();
if (spell == null) {
if (spellId != null) {
spell = entity
.getOrEmpty(world)
.flatMap(Caster::of)
.flatMap(caster -> caster.getSpellSlot().<T>get(s -> s.getUuid().equals(spellId), true))
.orElse(null);
if (spell != null) {
this.spell = new WeakReference<>(spell);
}
}
}
if (spell != null && spell.isDead()) {
spellId = null;
spell = null;
markDead();
}
return spell;
}
@Nullable
public Caster<?> getCaster() {
if (removed) {
return null;
}
return Caster.of(this.entity.get(world)).orElse(null);
}
@Override
public void toNBT(NbtCompound compound) {
entity.toNBT(compound);
@ -170,6 +263,10 @@ public class Ether extends PersistentState implements CasterView {
compound.putBoolean("taken", taken);
compound.putFloat("pitch", pitch);
compound.putFloat("yaw", yaw);
compound.putFloat("radius", radius);
if (spellId != null) {
compound.putUuid("spellId", spellId);
}
}
@Override
@ -179,20 +276,24 @@ public class Ether extends PersistentState implements CasterView {
taken = compound.getBoolean("taken");
pitch = compound.getFloat("pitch");
yaw = compound.getFloat("yaw");
radius = compound.getFloat("radius");
spellId = compound.containsUuid("spellid") ? compound.getUuid("spellId") : null;
}
@Override
public boolean equals(Object other) {
return other instanceof Entry e && e.entity.referenceEquals(entity);
return other instanceof Entry<?> e
&& e.entity.referenceEquals(entity)
&& Objects.equals(e.spell.get(), spell.get());
}
public boolean equals(UUID uuid) {
return entity.referenceEquals(uuid);
public boolean equals(UUID entityId, UUID spellId) {
return entity.referenceEquals(entityId) && spellId.equals(this.spellId);
}
@Override
public int hashCode() {
return entity.hashCode();
return Objects.hash(entity, spell.get());
}
}
}

View file

@ -2,6 +2,7 @@ package com.minelittlepony.unicopia.util;
import java.util.*;
import java.util.function.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.mojang.datafixers.util.Pair;
@ -65,6 +66,18 @@ public interface NbtSerialisable {
return parent;
}
static <K, V> Map<K, V> readMap(NbtCompound nbt, Function<String, K> keyFunction, Function<NbtElement, V> valueFunction) {
return nbt.getKeys().stream().collect(Collectors.toMap(keyFunction, k -> valueFunction.apply(nbt.get(k))));
}
static <K, V> NbtCompound writeMap(Map<K, V> map, Function<K, String> keyFunction, Function<V, ? extends NbtElement> valueFunction) {
NbtCompound nbt = new NbtCompound();
map.forEach((k, v) -> {
nbt.put(keyFunction.apply(k), valueFunction.apply(v));
});
return nbt;
}
interface Serializer<T> {
T read(NbtCompound compound);